e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
743 lines
22 KiB
C#
743 lines
22 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="StateManagedCollection.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace System.Web.UI {
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Specialized;
|
|
using System.ComponentModel;
|
|
using System.ComponentModel.Design;
|
|
using System.Drawing.Design;
|
|
using System.Reflection;
|
|
using System.Web.UI;
|
|
using System.Web.UI.WebControls;
|
|
using System.Web.Util;
|
|
|
|
/// <devdoc>
|
|
/// Manages state for an arbitrary collection of items that implement IStateManager.
|
|
/// The collection differentiates between known types and unknown types.
|
|
/// Known types take up less space in ViewState because only an index needs to be stored instead of a fully qualified type name.
|
|
/// Unknown types need to have their fully qualified type name stored in ViewState so they take up more space.
|
|
/// </devdoc>
|
|
public abstract class StateManagedCollection : IList, IStateManager {
|
|
|
|
private ArrayList _collectionItems;
|
|
|
|
private bool _tracking;
|
|
private bool _saveAll;
|
|
|
|
// We want to know if the collection had items to begin with
|
|
// so we don't put empty collections in the ViewState unnecessarily
|
|
private bool _hadItems;
|
|
|
|
|
|
|
|
/// <devdoc>
|
|
/// Creates a new instance of StateManagedCollection.
|
|
/// </devdoc>
|
|
protected StateManagedCollection() {
|
|
_collectionItems = new ArrayList();
|
|
}
|
|
|
|
|
|
|
|
/// <devdoc>
|
|
/// Returns the number of items in the collection.
|
|
/// </devdoc>
|
|
public int Count {
|
|
get {
|
|
return _collectionItems.Count;
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// Removes all the items from the collection.
|
|
/// </devdoc>
|
|
public void Clear() {
|
|
OnClear();
|
|
_collectionItems.Clear();
|
|
OnClearComplete();
|
|
|
|
if (_tracking) {
|
|
_saveAll = true;
|
|
}
|
|
}
|
|
|
|
|
|
public void CopyTo(Array array, int index) {
|
|
_collectionItems.CopyTo(array, index);
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// Creates an object of a known type based on an index into an array of types.
|
|
/// Indexes passed into CreateKnownType() must mach the indexes of the ArrayList returned
|
|
/// by GetKnownTypes().
|
|
/// </devdoc>
|
|
protected virtual object CreateKnownType(int index) {
|
|
throw new InvalidOperationException(SR.GetString(SR.StateManagedCollection_NoKnownTypes));
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// Returns the IEnumerator for the collection.
|
|
/// </devdoc>
|
|
public IEnumerator GetEnumerator() {
|
|
return _collectionItems.GetEnumerator();
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// Returns an ordered list of known types.
|
|
/// </devdoc>
|
|
protected virtual Type[] GetKnownTypes() {
|
|
return null;
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Returns the number of known types.
|
|
/// </devdoc>
|
|
private int GetKnownTypeCount() {
|
|
Type[] types = GetKnownTypes();
|
|
|
|
if (types == null)
|
|
return 0;
|
|
|
|
return types.Length;
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Inserts a new object into the collection at a given index.
|
|
/// If the index is -1 then the object is appended to the end of the collection.
|
|
/// </devdoc>
|
|
private void InsertInternal(int index, object o) {
|
|
Debug.Assert(index >= -1 && index <= Count, "Expected index to be at least -1 and less than or equal to Count.");
|
|
if (o == null) {
|
|
throw new ArgumentNullException("o");
|
|
}
|
|
|
|
if (((IStateManager)this).IsTrackingViewState) {
|
|
((IStateManager)o).TrackViewState();
|
|
SetDirtyObject(o);
|
|
}
|
|
|
|
OnInsert(index, o);
|
|
|
|
int trueIndex;
|
|
|
|
if (index == -1) {
|
|
trueIndex = _collectionItems.Add(o);
|
|
}
|
|
else {
|
|
trueIndex = index;
|
|
_collectionItems.Insert(index, o);
|
|
}
|
|
|
|
try {
|
|
OnInsertComplete(index, o);
|
|
}
|
|
catch {
|
|
_collectionItems.RemoveAt(trueIndex);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Loads all items from view state.
|
|
/// </devdoc>
|
|
private void LoadAllItemsFromViewState(object savedState) {
|
|
Debug.Assert(savedState != null);
|
|
Debug.Assert(savedState is Pair);
|
|
|
|
Pair p1 = (Pair)savedState;
|
|
|
|
if (p1.Second is Pair) {
|
|
Pair p2 = (Pair)p1.Second;
|
|
|
|
// save all mode; some objects are typed
|
|
object[] states = (object[])p1.First;
|
|
int[] typeIndices = (int[])p2.First;
|
|
ArrayList typedObjectTypeNames = (ArrayList)p2.Second;
|
|
|
|
Clear();
|
|
|
|
for (int i = 0; i < states.Length; i++) {
|
|
object o;
|
|
|
|
// If there is only one known type, we don't need type indices
|
|
if (typeIndices == null) {
|
|
// Create known type
|
|
o = CreateKnownType(0);
|
|
}
|
|
else {
|
|
int typeIndex = typeIndices[i];
|
|
|
|
if (typeIndex < GetKnownTypeCount()) {
|
|
// Create known type
|
|
o = CreateKnownType(typeIndex);
|
|
}
|
|
else {
|
|
string typeName = (string)typedObjectTypeNames[typeIndex - GetKnownTypeCount()];
|
|
Type type = Type.GetType(typeName);
|
|
|
|
o = Activator.CreateInstance(type);
|
|
}
|
|
}
|
|
|
|
((IStateManager)o).TrackViewState();
|
|
((IStateManager)o).LoadViewState(states[i]);
|
|
|
|
((IList)this).Add(o);
|
|
}
|
|
}
|
|
else {
|
|
Debug.Assert(p1.First is object[]);
|
|
|
|
// save all mode; all objects are instances of known types
|
|
object[] states = (object[])p1.First;
|
|
int[] typeIndices = (int[])p1.Second;
|
|
|
|
Clear();
|
|
|
|
for (int i = 0; i < states.Length; i++) {
|
|
// Create known type
|
|
int typeIndex = 0;
|
|
if (typeIndices != null) {
|
|
typeIndex = (int)typeIndices[i];
|
|
}
|
|
object o = CreateKnownType(typeIndex);
|
|
|
|
((IStateManager)o).TrackViewState();
|
|
((IStateManager)o).LoadViewState(states[i]);
|
|
|
|
((IList)this).Add(o);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Loads only changed items from view state.
|
|
/// </devdoc>
|
|
private void LoadChangedItemsFromViewState(object savedState) {
|
|
Debug.Assert(savedState != null);
|
|
Debug.Assert(savedState is Triplet);
|
|
|
|
Triplet t = (Triplet)savedState;
|
|
|
|
if (t.Third is Pair) {
|
|
// save some mode; some new objects are typed
|
|
Pair p = (Pair)t.Third;
|
|
|
|
ArrayList indices = (ArrayList)t.First;
|
|
ArrayList states = (ArrayList)t.Second;
|
|
ArrayList typeIndices = (ArrayList)p.First;
|
|
ArrayList typedObjectTypeNames = (ArrayList)p.Second;
|
|
|
|
for (int i = 0; i < indices.Count; i++) {
|
|
int index = (int)indices[i];
|
|
|
|
if (index < Count) {
|
|
((IStateManager)((IList)this)[index]).LoadViewState(states[i]);
|
|
}
|
|
else {
|
|
object o;
|
|
|
|
// If there is only one known type, we don't need type indices
|
|
if (typeIndices == null) {
|
|
// Create known type
|
|
o = CreateKnownType(0);
|
|
}
|
|
else {
|
|
int typeIndex = (int)typeIndices[i];
|
|
|
|
if (typeIndex < GetKnownTypeCount()) {
|
|
// Create known type
|
|
o = CreateKnownType(typeIndex);
|
|
}
|
|
else {
|
|
string typeName = (string)typedObjectTypeNames[typeIndex - GetKnownTypeCount()];
|
|
Type type = Type.GetType(typeName);
|
|
|
|
o = Activator.CreateInstance(type);
|
|
}
|
|
}
|
|
|
|
((IStateManager)o).TrackViewState();
|
|
((IStateManager)o).LoadViewState(states[i]);
|
|
|
|
((IList)this).Add(o);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// save some mode; all new objects are instances of known types
|
|
ArrayList indices = (ArrayList)t.First;
|
|
ArrayList states = (ArrayList)t.Second;
|
|
ArrayList typeIndices = (ArrayList)t.Third;
|
|
|
|
for (int i = 0; i < indices.Count; i++) {
|
|
int index = (int)indices[i];
|
|
|
|
if (index < Count) {
|
|
((IStateManager)((IList)this)[index]).LoadViewState(states[i]);
|
|
}
|
|
else {
|
|
// Create known type
|
|
int typeIndex = 0;
|
|
if (typeIndices != null) {
|
|
typeIndex = (int)typeIndices[i];
|
|
}
|
|
object o = CreateKnownType(typeIndex);
|
|
|
|
((IStateManager)o).TrackViewState();
|
|
((IStateManager)o).LoadViewState(states[i]);
|
|
|
|
((IList)this).Add(o);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// Called when the Clear() method is starting.
|
|
/// </devdoc>
|
|
protected virtual void OnClear() {
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// Called when the Clear() method is complete.
|
|
/// </devdoc>
|
|
protected virtual void OnClearComplete() {
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// Called when an object must be validated.
|
|
/// </devdoc>
|
|
protected virtual void OnValidate(object value) {
|
|
if (value == null) throw new ArgumentNullException("value");
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// Called when the Insert() method is starting.
|
|
/// </devdoc>
|
|
protected virtual void OnInsert(int index, object value) {
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// Called when the Insert() method is complete.
|
|
/// </devdoc>
|
|
protected virtual void OnInsertComplete(int index, object value) {
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// Called when the Remove() method is starting.
|
|
/// </devdoc>
|
|
protected virtual void OnRemove(int index, object value) {
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// Called when the Remove() method is complete.
|
|
/// </devdoc>
|
|
protected virtual void OnRemoveComplete(int index, object value) {
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Saves all items in the collection to view state.
|
|
/// </devdoc>
|
|
private object SaveAllItemsToViewState() {
|
|
Debug.Assert(_saveAll);
|
|
|
|
bool hasState = false;
|
|
|
|
int count = _collectionItems.Count;
|
|
|
|
int[] typeIndices = new int[count];
|
|
object[] states = new object[count];
|
|
|
|
ArrayList typedObjectTypeNames = null;
|
|
IDictionary typedObjectTracker = null;
|
|
|
|
int knownTypeCount = GetKnownTypeCount();
|
|
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
object o = _collectionItems[i];
|
|
SetDirtyObject(o);
|
|
|
|
states[i] = ((IStateManager)o).SaveViewState();
|
|
|
|
if (states[i] != null)
|
|
hasState = true;
|
|
|
|
Type objectType = o.GetType();
|
|
|
|
int knownTypeIndex = -1;
|
|
|
|
// If there are known types, find index
|
|
if (knownTypeCount != 0) {
|
|
knownTypeIndex = ((IList)GetKnownTypes()).IndexOf(objectType);
|
|
}
|
|
|
|
if (knownTypeIndex != -1) {
|
|
// Type is known
|
|
typeIndices[i] = knownTypeIndex;
|
|
}
|
|
else {
|
|
// Type is unknown
|
|
if (typedObjectTypeNames == null) {
|
|
typedObjectTypeNames = new ArrayList();
|
|
typedObjectTracker = new HybridDictionary();
|
|
}
|
|
|
|
// Get index of type
|
|
object index = typedObjectTracker[objectType];
|
|
|
|
// If type is not in list, add it to the list
|
|
if (index == null) {
|
|
typedObjectTypeNames.Add(objectType.AssemblyQualifiedName);
|
|
|
|
// Offset the index by the known type count
|
|
index = typedObjectTypeNames.Count + knownTypeCount - 1;
|
|
typedObjectTracker[objectType] = index;
|
|
}
|
|
|
|
typeIndices[i] = (int)index;
|
|
}
|
|
}
|
|
|
|
// If the collection didn't have items to begin with don't save the state
|
|
if (!_hadItems && !hasState) {
|
|
return null;
|
|
}
|
|
|
|
if (typedObjectTypeNames == null) {
|
|
// all objects are instances known types
|
|
|
|
// If there is only one known type, then all objects are of that type so the indices are not needed
|
|
if (knownTypeCount == 1)
|
|
typeIndices = null;
|
|
|
|
return new Pair(states, typeIndices);
|
|
}
|
|
else {
|
|
return new Pair(states, new Pair(typeIndices, typedObjectTypeNames));
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Saves changed items to view state.
|
|
/// </devdoc>
|
|
private object SaveChangedItemsToViewState() {
|
|
Debug.Assert(_saveAll == false);
|
|
|
|
bool hasState = false;
|
|
|
|
int count = _collectionItems.Count;
|
|
|
|
ArrayList indices = new ArrayList();
|
|
ArrayList states = new ArrayList();
|
|
ArrayList typeIndices = new ArrayList();
|
|
|
|
ArrayList typedObjectTypeNames = null;
|
|
IDictionary typedObjectTracker = null;
|
|
|
|
int knownTypeCount = GetKnownTypeCount();
|
|
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
object o = _collectionItems[i];
|
|
|
|
object state = ((IStateManager)o).SaveViewState();
|
|
if (state != null) {
|
|
hasState = true;
|
|
|
|
indices.Add(i);
|
|
states.Add(state);
|
|
|
|
Type objectType = o.GetType();
|
|
|
|
int knownTypeIndex = -1;
|
|
|
|
// If there are known types, find index
|
|
if (knownTypeCount != 0) {
|
|
knownTypeIndex = ((IList)GetKnownTypes()).IndexOf(objectType);
|
|
}
|
|
|
|
if (knownTypeIndex != -1) {
|
|
// Type is known
|
|
typeIndices.Add(knownTypeIndex);
|
|
}
|
|
else {
|
|
// Type is unknown
|
|
if (typedObjectTypeNames == null) {
|
|
typedObjectTypeNames = new ArrayList();
|
|
typedObjectTracker = new HybridDictionary();
|
|
}
|
|
|
|
object index = typedObjectTracker[objectType];
|
|
if (index == null) {
|
|
typedObjectTypeNames.Add(objectType.AssemblyQualifiedName);
|
|
|
|
// Offset the index by the known type count
|
|
index = typedObjectTypeNames.Count + knownTypeCount - 1;
|
|
typedObjectTracker[objectType] = index;
|
|
}
|
|
|
|
typeIndices.Add(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the collection didn't have items to begin with don't save the state
|
|
if (!_hadItems && !hasState) {
|
|
return null;
|
|
}
|
|
|
|
if (typedObjectTypeNames == null) {
|
|
// all objects are instances known types
|
|
|
|
// If there is only one known type, then all objects are of that type so the indices are not needed
|
|
if (knownTypeCount == 1)
|
|
typeIndices = null;
|
|
|
|
return new Triplet(indices, states, typeIndices);
|
|
}
|
|
else {
|
|
return new Triplet(indices, states, new Pair(typeIndices, typedObjectTypeNames));
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// Forces the entire collection to be serialized into viewstate, not just
|
|
/// the change-information. This is useful when a collection has changed in
|
|
/// a significant way and change information would be insufficient to
|
|
/// recreate the object.
|
|
/// </devdoc>
|
|
public void SetDirty() {
|
|
_saveAll = true;
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// Flags an object to record its entire state instead of just changed parts.
|
|
/// </devdoc>
|
|
protected abstract void SetDirtyObject(object o);
|
|
|
|
|
|
|
|
/// <internalonly/>
|
|
IEnumerator IEnumerable.GetEnumerator() {
|
|
return GetEnumerator();
|
|
}
|
|
|
|
|
|
|
|
/// <internalonly/>
|
|
int ICollection.Count {
|
|
get {
|
|
return Count;
|
|
}
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
bool ICollection.IsSynchronized {
|
|
get {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
object ICollection.SyncRoot {
|
|
get {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
bool IList.IsFixedSize {
|
|
get {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
bool IList.IsReadOnly {
|
|
get {
|
|
return _collectionItems.IsReadOnly;
|
|
}
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
object IList.this[int index] {
|
|
get {
|
|
return _collectionItems[index];
|
|
}
|
|
set {
|
|
if (index < 0 || index >= Count) {
|
|
throw new ArgumentOutOfRangeException("index", SR.GetString(SR.StateManagedCollection_InvalidIndex));
|
|
}
|
|
((IList)this).RemoveAt(index);
|
|
((IList)this).Insert(index, value);
|
|
}
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
int IList.Add(object value) {
|
|
OnValidate(value);
|
|
|
|
InsertInternal(-1, value);
|
|
|
|
return _collectionItems.Count - 1;
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
void IList.Clear() {
|
|
Clear();
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
bool IList.Contains(object value) {
|
|
if (value == null) {
|
|
return false;
|
|
}
|
|
|
|
OnValidate(value);
|
|
|
|
return _collectionItems.Contains(value);
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
int IList.IndexOf(object value) {
|
|
if (value == null) {
|
|
return -1;
|
|
}
|
|
|
|
OnValidate(value);
|
|
|
|
return _collectionItems.IndexOf(value);
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
void IList.Insert(int index, object value) {
|
|
if (value == null) {
|
|
throw new ArgumentNullException("value");
|
|
}
|
|
if (index < 0 || index > Count) {
|
|
throw new ArgumentOutOfRangeException("index", SR.GetString(SR.StateManagedCollection_InvalidIndex));
|
|
}
|
|
|
|
OnValidate(value);
|
|
|
|
InsertInternal(index, value);
|
|
|
|
if (_tracking) {
|
|
_saveAll = true;
|
|
}
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
void IList.Remove(object value) {
|
|
if (value == null) {
|
|
return;
|
|
}
|
|
|
|
OnValidate(value);
|
|
|
|
((IList)this).RemoveAt(((IList)this).IndexOf(value));
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
void IList.RemoveAt(int index) {
|
|
object o = _collectionItems[index];
|
|
|
|
OnRemove(index, o);
|
|
_collectionItems.RemoveAt(index);
|
|
try {
|
|
OnRemoveComplete(index, o);
|
|
}
|
|
catch {
|
|
_collectionItems.Insert(index, o);
|
|
throw;
|
|
}
|
|
|
|
if (_tracking) {
|
|
_saveAll = true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <internalonly/>
|
|
bool IStateManager.IsTrackingViewState {
|
|
get {
|
|
return _tracking;
|
|
}
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
void IStateManager.LoadViewState(object savedState) {
|
|
|
|
if (savedState != null) {
|
|
if (savedState is Triplet) {
|
|
LoadChangedItemsFromViewState(savedState);
|
|
}
|
|
else {
|
|
LoadAllItemsFromViewState(savedState);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
object IStateManager.SaveViewState() {
|
|
|
|
if (_saveAll) {
|
|
return SaveAllItemsToViewState();
|
|
}
|
|
else {
|
|
return SaveChangedItemsToViewState();
|
|
}
|
|
}
|
|
|
|
|
|
/// <internalonly/>
|
|
void IStateManager.TrackViewState() {
|
|
if (!((IStateManager)this).IsTrackingViewState) {
|
|
_hadItems = Count > 0;
|
|
|
|
_tracking = true;
|
|
|
|
foreach (IStateManager o in _collectionItems) {
|
|
o.TrackViewState();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|