1 //------------------------------------------------------------------------------ 2 // <copyright file="StateManagedCollection.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 //------------------------------------------------------------------------------ 6 7 namespace System.Web.UI { 8 9 using System; 10 using System.Collections; 11 using System.Collections.Specialized; 12 using System.ComponentModel; 13 using System.ComponentModel.Design; 14 using System.Drawing.Design; 15 using System.Reflection; 16 using System.Web.UI; 17 using System.Web.UI.WebControls; 18 using System.Web.Util; 19 20 /// <devdoc> 21 /// Manages state for an arbitrary collection of items that implement IStateManager. 22 /// The collection differentiates between known types and unknown types. 23 /// Known types take up less space in ViewState because only an index needs to be stored instead of a fully qualified type name. 24 /// Unknown types need to have their fully qualified type name stored in ViewState so they take up more space. 25 /// </devdoc> 26 public abstract class StateManagedCollection : IList, IStateManager { 27 28 private ArrayList _collectionItems; 29 30 private bool _tracking; 31 private bool _saveAll; 32 33 // We want to know if the collection had items to begin with 34 // so we don't put empty collections in the ViewState unnecessarily 35 private bool _hadItems; 36 37 38 39 /// <devdoc> 40 /// Creates a new instance of StateManagedCollection. 41 /// </devdoc> StateManagedCollection()42 protected StateManagedCollection() { 43 _collectionItems = new ArrayList(); 44 } 45 46 47 48 /// <devdoc> 49 /// Returns the number of items in the collection. 50 /// </devdoc> 51 public int Count { 52 get { 53 return _collectionItems.Count; 54 } 55 } 56 57 58 /// <devdoc> 59 /// Removes all the items from the collection. 60 /// </devdoc> Clear()61 public void Clear() { 62 OnClear(); 63 _collectionItems.Clear(); 64 OnClearComplete(); 65 66 if (_tracking) { 67 _saveAll = true; 68 } 69 } 70 71 CopyTo(Array array, int index)72 public void CopyTo(Array array, int index) { 73 _collectionItems.CopyTo(array, index); 74 } 75 76 77 /// <devdoc> 78 /// Creates an object of a known type based on an index into an array of types. 79 /// Indexes passed into CreateKnownType() must mach the indexes of the ArrayList returned 80 /// by GetKnownTypes(). 81 /// </devdoc> CreateKnownType(int index)82 protected virtual object CreateKnownType(int index) { 83 throw new InvalidOperationException(SR.GetString(SR.StateManagedCollection_NoKnownTypes)); 84 } 85 86 87 /// <devdoc> 88 /// Returns the IEnumerator for the collection. 89 /// </devdoc> GetEnumerator()90 public IEnumerator GetEnumerator() { 91 return _collectionItems.GetEnumerator(); 92 } 93 94 95 /// <devdoc> 96 /// Returns an ordered list of known types. 97 /// </devdoc> GetKnownTypes()98 protected virtual Type[] GetKnownTypes() { 99 return null; 100 } 101 102 /// <devdoc> 103 /// Returns the number of known types. 104 /// </devdoc> GetKnownTypeCount()105 private int GetKnownTypeCount() { 106 Type[] types = GetKnownTypes(); 107 108 if (types == null) 109 return 0; 110 111 return types.Length; 112 } 113 114 /// <devdoc> 115 /// Inserts a new object into the collection at a given index. 116 /// If the index is -1 then the object is appended to the end of the collection. 117 /// </devdoc> InsertInternal(int index, object o)118 private void InsertInternal(int index, object o) { 119 Debug.Assert(index >= -1 && index <= Count, "Expected index to be at least -1 and less than or equal to Count."); 120 if (o == null) { 121 throw new ArgumentNullException("o"); 122 } 123 124 if (((IStateManager)this).IsTrackingViewState) { 125 ((IStateManager)o).TrackViewState(); 126 SetDirtyObject(o); 127 } 128 129 OnInsert(index, o); 130 131 int trueIndex; 132 133 if (index == -1) { 134 trueIndex = _collectionItems.Add(o); 135 } 136 else { 137 trueIndex = index; 138 _collectionItems.Insert(index, o); 139 } 140 141 try { 142 OnInsertComplete(index, o); 143 } 144 catch { 145 _collectionItems.RemoveAt(trueIndex); 146 throw; 147 } 148 } 149 150 /// <devdoc> 151 /// Loads all items from view state. 152 /// </devdoc> LoadAllItemsFromViewState(object savedState)153 private void LoadAllItemsFromViewState(object savedState) { 154 Debug.Assert(savedState != null); 155 Debug.Assert(savedState is Pair); 156 157 Pair p1 = (Pair)savedState; 158 159 if (p1.Second is Pair) { 160 Pair p2 = (Pair)p1.Second; 161 162 // save all mode; some objects are typed 163 object[] states = (object[])p1.First; 164 int[] typeIndices = (int[])p2.First; 165 ArrayList typedObjectTypeNames = (ArrayList)p2.Second; 166 167 Clear(); 168 169 for (int i = 0; i < states.Length; i++) { 170 object o; 171 172 // If there is only one known type, we don't need type indices 173 if (typeIndices == null) { 174 // Create known type 175 o = CreateKnownType(0); 176 } 177 else { 178 int typeIndex = typeIndices[i]; 179 180 if (typeIndex < GetKnownTypeCount()) { 181 // Create known type 182 o = CreateKnownType(typeIndex); 183 } 184 else { 185 string typeName = (string)typedObjectTypeNames[typeIndex - GetKnownTypeCount()]; 186 Type type = Type.GetType(typeName); 187 188 o = Activator.CreateInstance(type); 189 } 190 } 191 192 ((IStateManager)o).TrackViewState(); 193 ((IStateManager)o).LoadViewState(states[i]); 194 195 ((IList)this).Add(o); 196 } 197 } 198 else { 199 Debug.Assert(p1.First is object[]); 200 201 // save all mode; all objects are instances of known types 202 object[] states = (object[])p1.First; 203 int[] typeIndices = (int[])p1.Second; 204 205 Clear(); 206 207 for (int i = 0; i < states.Length; i++) { 208 // Create known type 209 int typeIndex = 0; 210 if (typeIndices != null) { 211 typeIndex = (int)typeIndices[i]; 212 } 213 object o = CreateKnownType(typeIndex); 214 215 ((IStateManager)o).TrackViewState(); 216 ((IStateManager)o).LoadViewState(states[i]); 217 218 ((IList)this).Add(o); 219 } 220 } 221 } 222 223 /// <devdoc> 224 /// Loads only changed items from view state. 225 /// </devdoc> LoadChangedItemsFromViewState(object savedState)226 private void LoadChangedItemsFromViewState(object savedState) { 227 Debug.Assert(savedState != null); 228 Debug.Assert(savedState is Triplet); 229 230 Triplet t = (Triplet)savedState; 231 232 if (t.Third is Pair) { 233 // save some mode; some new objects are typed 234 Pair p = (Pair)t.Third; 235 236 ArrayList indices = (ArrayList)t.First; 237 ArrayList states = (ArrayList)t.Second; 238 ArrayList typeIndices = (ArrayList)p.First; 239 ArrayList typedObjectTypeNames = (ArrayList)p.Second; 240 241 for (int i = 0; i < indices.Count; i++) { 242 int index = (int)indices[i]; 243 244 if (index < Count) { 245 ((IStateManager)((IList)this)[index]).LoadViewState(states[i]); 246 } 247 else { 248 object o; 249 250 // If there is only one known type, we don't need type indices 251 if (typeIndices == null) { 252 // Create known type 253 o = CreateKnownType(0); 254 } 255 else { 256 int typeIndex = (int)typeIndices[i]; 257 258 if (typeIndex < GetKnownTypeCount()) { 259 // Create known type 260 o = CreateKnownType(typeIndex); 261 } 262 else { 263 string typeName = (string)typedObjectTypeNames[typeIndex - GetKnownTypeCount()]; 264 Type type = Type.GetType(typeName); 265 266 o = Activator.CreateInstance(type); 267 } 268 } 269 270 ((IStateManager)o).TrackViewState(); 271 ((IStateManager)o).LoadViewState(states[i]); 272 273 ((IList)this).Add(o); 274 } 275 } 276 } 277 else { 278 // save some mode; all new objects are instances of known types 279 ArrayList indices = (ArrayList)t.First; 280 ArrayList states = (ArrayList)t.Second; 281 ArrayList typeIndices = (ArrayList)t.Third; 282 283 for (int i = 0; i < indices.Count; i++) { 284 int index = (int)indices[i]; 285 286 if (index < Count) { 287 ((IStateManager)((IList)this)[index]).LoadViewState(states[i]); 288 } 289 else { 290 // Create known type 291 int typeIndex = 0; 292 if (typeIndices != null) { 293 typeIndex = (int)typeIndices[i]; 294 } 295 object o = CreateKnownType(typeIndex); 296 297 ((IStateManager)o).TrackViewState(); 298 ((IStateManager)o).LoadViewState(states[i]); 299 300 ((IList)this).Add(o); 301 } 302 } 303 } 304 } 305 306 307 /// <devdoc> 308 /// Called when the Clear() method is starting. 309 /// </devdoc> OnClear()310 protected virtual void OnClear() { 311 } 312 313 314 /// <devdoc> 315 /// Called when the Clear() method is complete. 316 /// </devdoc> OnClearComplete()317 protected virtual void OnClearComplete() { 318 } 319 320 321 /// <devdoc> 322 /// Called when an object must be validated. 323 /// </devdoc> OnValidate(object value)324 protected virtual void OnValidate(object value) { 325 if (value == null) throw new ArgumentNullException("value"); 326 } 327 328 329 /// <devdoc> 330 /// Called when the Insert() method is starting. 331 /// </devdoc> OnInsert(int index, object value)332 protected virtual void OnInsert(int index, object value) { 333 } 334 335 336 /// <devdoc> 337 /// Called when the Insert() method is complete. 338 /// </devdoc> OnInsertComplete(int index, object value)339 protected virtual void OnInsertComplete(int index, object value) { 340 } 341 342 343 /// <devdoc> 344 /// Called when the Remove() method is starting. 345 /// </devdoc> OnRemove(int index, object value)346 protected virtual void OnRemove(int index, object value) { 347 } 348 349 350 /// <devdoc> 351 /// Called when the Remove() method is complete. 352 /// </devdoc> OnRemoveComplete(int index, object value)353 protected virtual void OnRemoveComplete(int index, object value) { 354 } 355 356 /// <devdoc> 357 /// Saves all items in the collection to view state. 358 /// </devdoc> SaveAllItemsToViewState()359 private object SaveAllItemsToViewState() { 360 Debug.Assert(_saveAll); 361 362 bool hasState = false; 363 364 int count = _collectionItems.Count; 365 366 int[] typeIndices = new int[count]; 367 object[] states = new object[count]; 368 369 ArrayList typedObjectTypeNames = null; 370 IDictionary typedObjectTracker = null; 371 372 int knownTypeCount = GetKnownTypeCount(); 373 374 375 for (int i = 0; i < count; i++) { 376 object o = _collectionItems[i]; 377 SetDirtyObject(o); 378 379 states[i] = ((IStateManager)o).SaveViewState(); 380 381 if (states[i] != null) 382 hasState = true; 383 384 Type objectType = o.GetType(); 385 386 int knownTypeIndex = -1; 387 388 // If there are known types, find index 389 if (knownTypeCount != 0) { 390 knownTypeIndex = ((IList)GetKnownTypes()).IndexOf(objectType); 391 } 392 393 if (knownTypeIndex != -1) { 394 // Type is known 395 typeIndices[i] = knownTypeIndex; 396 } 397 else { 398 // Type is unknown 399 if (typedObjectTypeNames == null) { 400 typedObjectTypeNames = new ArrayList(); 401 typedObjectTracker = new HybridDictionary(); 402 } 403 404 // Get index of type 405 object index = typedObjectTracker[objectType]; 406 407 // If type is not in list, add it to the list 408 if (index == null) { 409 typedObjectTypeNames.Add(objectType.AssemblyQualifiedName); 410 411 // Offset the index by the known type count 412 index = typedObjectTypeNames.Count + knownTypeCount - 1; 413 typedObjectTracker[objectType] = index; 414 } 415 416 typeIndices[i] = (int)index; 417 } 418 } 419 420 // If the collection didn't have items to begin with don't save the state 421 if (!_hadItems && !hasState) { 422 return null; 423 } 424 425 if (typedObjectTypeNames == null) { 426 // all objects are instances known types 427 428 // If there is only one known type, then all objects are of that type so the indices are not needed 429 if (knownTypeCount == 1) 430 typeIndices = null; 431 432 return new Pair(states, typeIndices); 433 } 434 else { 435 return new Pair(states, new Pair(typeIndices, typedObjectTypeNames)); 436 } 437 } 438 439 /// <devdoc> 440 /// Saves changed items to view state. 441 /// </devdoc> SaveChangedItemsToViewState()442 private object SaveChangedItemsToViewState() { 443 Debug.Assert(_saveAll == false); 444 445 bool hasState = false; 446 447 int count = _collectionItems.Count; 448 449 ArrayList indices = new ArrayList(); 450 ArrayList states = new ArrayList(); 451 ArrayList typeIndices = new ArrayList(); 452 453 ArrayList typedObjectTypeNames = null; 454 IDictionary typedObjectTracker = null; 455 456 int knownTypeCount = GetKnownTypeCount(); 457 458 459 for (int i = 0; i < count; i++) { 460 object o = _collectionItems[i]; 461 462 object state = ((IStateManager)o).SaveViewState(); 463 if (state != null) { 464 hasState = true; 465 466 indices.Add(i); 467 states.Add(state); 468 469 Type objectType = o.GetType(); 470 471 int knownTypeIndex = -1; 472 473 // If there are known types, find index 474 if (knownTypeCount != 0) { 475 knownTypeIndex = ((IList)GetKnownTypes()).IndexOf(objectType); 476 } 477 478 if (knownTypeIndex != -1) { 479 // Type is known 480 typeIndices.Add(knownTypeIndex); 481 } 482 else { 483 // Type is unknown 484 if (typedObjectTypeNames == null) { 485 typedObjectTypeNames = new ArrayList(); 486 typedObjectTracker = new HybridDictionary(); 487 } 488 489 object index = typedObjectTracker[objectType]; 490 if (index == null) { 491 typedObjectTypeNames.Add(objectType.AssemblyQualifiedName); 492 493 // Offset the index by the known type count 494 index = typedObjectTypeNames.Count + knownTypeCount - 1; 495 typedObjectTracker[objectType] = index; 496 } 497 498 typeIndices.Add(index); 499 } 500 } 501 } 502 503 // If the collection didn't have items to begin with don't save the state 504 if (!_hadItems && !hasState) { 505 return null; 506 } 507 508 if (typedObjectTypeNames == null) { 509 // all objects are instances known types 510 511 // If there is only one known type, then all objects are of that type so the indices are not needed 512 if (knownTypeCount == 1) 513 typeIndices = null; 514 515 return new Triplet(indices, states, typeIndices); 516 } 517 else { 518 return new Triplet(indices, states, new Pair(typeIndices, typedObjectTypeNames)); 519 } 520 } 521 522 /// <devdoc> 523 /// Forces the entire collection to be serialized into viewstate, not just 524 /// the change-information. This is useful when a collection has changed in 525 /// a significant way and change information would be insufficient to 526 /// recreate the object. 527 /// </devdoc> SetDirty()528 public void SetDirty() { 529 _saveAll = true; 530 } 531 532 533 /// <devdoc> 534 /// Flags an object to record its entire state instead of just changed parts. 535 /// </devdoc> SetDirtyObject(object o)536 protected abstract void SetDirtyObject(object o); 537 538 539 540 /// <internalonly/> IEnumerable.GetEnumerator()541 IEnumerator IEnumerable.GetEnumerator() { 542 return GetEnumerator(); 543 } 544 545 546 547 /// <internalonly/> 548 int ICollection.Count { 549 get { 550 return Count; 551 } 552 } 553 554 555 /// <internalonly/> 556 bool ICollection.IsSynchronized { 557 get { 558 return false; 559 } 560 } 561 562 563 /// <internalonly/> 564 object ICollection.SyncRoot { 565 get { 566 return null; 567 } 568 } 569 570 571 /// <internalonly/> 572 bool IList.IsFixedSize { 573 get { 574 return false; 575 } 576 } 577 578 579 /// <internalonly/> 580 bool IList.IsReadOnly { 581 get { 582 return _collectionItems.IsReadOnly; 583 } 584 } 585 586 587 /// <internalonly/> 588 object IList.this[int index] { 589 get { 590 return _collectionItems[index]; 591 } 592 set { 593 if (index < 0 || index >= Count) { 594 throw new ArgumentOutOfRangeException("index", SR.GetString(SR.StateManagedCollection_InvalidIndex)); 595 } 596 ((IList)this).RemoveAt(index); 597 ((IList)this).Insert(index, value); 598 } 599 } 600 601 602 /// <internalonly/> IList.Add(object value)603 int IList.Add(object value) { 604 OnValidate(value); 605 606 InsertInternal(-1, value); 607 608 return _collectionItems.Count - 1; 609 } 610 611 612 /// <internalonly/> IList.Clear()613 void IList.Clear() { 614 Clear(); 615 } 616 617 618 /// <internalonly/> IList.Contains(object value)619 bool IList.Contains(object value) { 620 if (value == null) { 621 return false; 622 } 623 624 OnValidate(value); 625 626 return _collectionItems.Contains(value); 627 } 628 629 630 /// <internalonly/> IList.IndexOf(object value)631 int IList.IndexOf(object value) { 632 if (value == null) { 633 return -1; 634 } 635 636 OnValidate(value); 637 638 return _collectionItems.IndexOf(value); 639 } 640 641 642 /// <internalonly/> IList.Insert(int index, object value)643 void IList.Insert(int index, object value) { 644 if (value == null) { 645 throw new ArgumentNullException("value"); 646 } 647 if (index < 0 || index > Count) { 648 throw new ArgumentOutOfRangeException("index", SR.GetString(SR.StateManagedCollection_InvalidIndex)); 649 } 650 651 OnValidate(value); 652 653 InsertInternal(index, value); 654 655 if (_tracking) { 656 _saveAll = true; 657 } 658 } 659 660 661 /// <internalonly/> IList.Remove(object value)662 void IList.Remove(object value) { 663 if (value == null) { 664 return; 665 } 666 667 OnValidate(value); 668 669 ((IList)this).RemoveAt(((IList)this).IndexOf(value)); 670 } 671 672 673 /// <internalonly/> IList.RemoveAt(int index)674 void IList.RemoveAt(int index) { 675 object o = _collectionItems[index]; 676 677 OnRemove(index, o); 678 _collectionItems.RemoveAt(index); 679 try { 680 OnRemoveComplete(index, o); 681 } 682 catch { 683 _collectionItems.Insert(index, o); 684 throw; 685 } 686 687 if (_tracking) { 688 _saveAll = true; 689 } 690 } 691 692 693 694 /// <internalonly/> 695 bool IStateManager.IsTrackingViewState { 696 get { 697 return _tracking; 698 } 699 } 700 701 702 /// <internalonly/> IStateManager.LoadViewState(object savedState)703 void IStateManager.LoadViewState(object savedState) { 704 705 if (savedState != null) { 706 if (savedState is Triplet) { 707 LoadChangedItemsFromViewState(savedState); 708 } 709 else { 710 LoadAllItemsFromViewState(savedState); 711 } 712 } 713 } 714 715 716 /// <internalonly/> IStateManager.SaveViewState()717 object IStateManager.SaveViewState() { 718 719 if (_saveAll) { 720 return SaveAllItemsToViewState(); 721 } 722 else { 723 return SaveChangedItemsToViewState(); 724 } 725 } 726 727 728 /// <internalonly/> IStateManager.TrackViewState()729 void IStateManager.TrackViewState() { 730 if (!((IStateManager)this).IsTrackingViewState) { 731 _hadItems = Count > 0; 732 733 _tracking = true; 734 735 foreach (IStateManager o in _collectionItems) { 736 o.TrackViewState(); 737 } 738 } 739 } 740 } 741 } 742 743