File: UI\WebParts\BlobPersonalizationState.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="BlobPersonalizationState.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Web.UI.WebControls.WebParts {
 
    using System;
    using System.Collections;
    using System.Collections.Specialized;
    using System.Globalization;
    using System.IO;
    using System.Reflection;
    using System.Web;
    using System.Web.UI;
    using System.Web.Util;
 
    /// <devdoc>
    /// </devdoc>
    internal sealed class BlobPersonalizationState : PersonalizationState {
 
        private const int PersonalizationVersion = (int)PersonalizationVersions.WhidbeyRTM;
        private const string WebPartManagerPersonalizationID = "__wpm";
 
        private bool _isPostRequest;
        private IDictionary _personalizedControls;
 
        private IDictionary _sharedState;
        private IDictionary _userState;
        private byte[] _rawUserData;
 
        private IDictionary _extractedState;
 
        /// <devdoc>
        /// </devdoc>
        public BlobPersonalizationState(WebPartManager webPartManager) : base(webPartManager) {
            // 
 
 
            // Note that we don't use the IsPostBack property of Page because that
            // is based on the presence of view state, which could be on the query string
            // in a non-POST request as well. Instead we use the actual verb associated
            // with the request.
            // Note that there are other types of HttpVerb besides GET and POST.  We only
            // save personalization data for POST requests.  (VSWhidbey 423433)
            _isPostRequest = (webPartManager.Page.Request.HttpVerb == HttpVerb.POST);
        }
 
        /// <internalonly />
        public override bool IsEmpty {
            get {
                return ((_extractedState == null) || (_extractedState.Count == 0));
            }
        }
 
        /// <devdoc>
        /// </devdoc>
        private bool IsPostRequest {
            get {
                return _isPostRequest;
            }
        }
 
        /// <devdoc>
        /// </devdoc>
        private PersonalizationScope PersonalizationScope {
            get {
                return WebPartManager.Personalization.Scope;
            }
        }
 
        /// <devdoc>
        /// This is for symmetry with the UserState property.
        /// </devdoc>
        private IDictionary SharedState {
            get {
                return _sharedState;
            }
        }
 
        /// <devdoc>
        /// User state is always loaded even if the WebPartManager is in shared
        /// scope. So we on-demand deserialize the bytes.
        /// </devdoc>
        private IDictionary UserState {
            get {
                if (_rawUserData != null) {
                    _userState = DeserializeData(_rawUserData);
                    _rawUserData = null;
                }
 
                if (_userState == null) {
                    _userState = new HybridDictionary(/* caseInsensitive */ false);
                }
 
                return _userState;
            }
        }
 
        /// <devdoc>
        /// Does the work of applying personalization data into a control
        /// </devdoc>
        private void ApplyPersonalization(Control control, string personalizationID, bool isWebPartManager,
                                          PersonalizationScope extractScope, GenericWebPart genericWebPart) {
            Debug.Assert(control != null);
            Debug.Assert(!String.IsNullOrEmpty(personalizationID));
 
            if (_personalizedControls == null) {
                _personalizedControls = new HybridDictionary(/* caseInsensitive */ false);
            }
            else {
                // We shouldn't be applying personalization to the same control more than once
                if (_personalizedControls.Contains(personalizationID)) {
                    throw new InvalidOperationException(SR.GetString(SR.BlobPersonalizationState_CantApply, personalizationID));
                }
            }
 
            IDictionary personalizableProperties =
                PersonalizableAttribute.GetPersonalizablePropertyEntries(control.GetType());
 
            if (SharedState == null) {
                throw new InvalidOperationException(SR.GetString(SR.BlobPersonalizationState_NotLoaded));
            }
 
            PersonalizationInfo sharedInfo = (PersonalizationInfo)SharedState[personalizationID];
            PersonalizationInfo userInfo = null;
            IDictionary defaultProperties = null;
            IDictionary initialProperties = null;
            PersonalizationDictionary customInitialProperties = null;
 
            // WebPart.SetPersonalizationDirty() should only mark a control as dirty in the following circumstances:
            // 1. During its IPersonalizable.Load() method
            // 2. During its IVersioningPersonalizable.Load() method
            // 3. During or after its ITrackingPersonalizable.EndLoad() method
            // By exclusion, WebPart.SetPersonalizationDirty() should be a no-op in the following circumstances:
            // 1. Before its IPersonalizable.Load() method
            // 2. While we are setting the values of its [Personalizable] properties
            // (VSWhidbey 392533)
            ControlInfo ci = new ControlInfo();
            ci._allowSetDirty = false;
            _personalizedControls[personalizationID] = ci;
 
            if (sharedInfo != null && sharedInfo._isStatic && !sharedInfo.IsMatchingControlType(control)) {
                // Mismatch in saved data, so ignore it
                sharedInfo = null;
                if (PersonalizationScope == PersonalizationScope.Shared) {
                    SetControlDirty(control, personalizationID, isWebPartManager, true);
                }
            }
 
            IPersonalizable customPersonalizable = control as IPersonalizable;
            ITrackingPersonalizable trackingPersonalizable = control as ITrackingPersonalizable;
 
            // The WebPart on which to set HasSharedData and HasUserData
            WebPart hasDataWebPart = null;
            if (!isWebPartManager) {
                if (genericWebPart != null) {
                    hasDataWebPart = genericWebPart;
                }
                else {
                    Debug.Assert(control is WebPart);
                    hasDataWebPart = (WebPart)control;
                }
            }
 
            try {
                if (trackingPersonalizable != null) {
                    trackingPersonalizable.BeginLoad();
                }
 
                if (PersonalizationScope == PersonalizationScope.User) {
                    if (UserState == null) {
                        throw new InvalidOperationException(SR.GetString(SR.BlobPersonalizationState_NotLoaded));
                    }
 
                    userInfo = (PersonalizationInfo)UserState[personalizationID];
 
                    if (userInfo != null && userInfo._isStatic && !userInfo.IsMatchingControlType(control)) {
                        // Mismatch in saved data, so ignore it
                        userInfo = null;
                        SetControlDirty(control, personalizationID, isWebPartManager, true);
                    }
 
                    if (customPersonalizable != null) {
                        PersonalizationDictionary customProperties = MergeCustomProperties(
                            sharedInfo, userInfo, isWebPartManager, hasDataWebPart, ref customInitialProperties);
                        if (customProperties != null) {
                            ci._allowSetDirty = true;
                            customPersonalizable.Load(customProperties);
                            ci._allowSetDirty = false;
                        }
                    }
 
                    if (!isWebPartManager) {
                        // Properties do not apply to the WebPartManager
 
                        IDictionary unusedSharedProperties = null;
                        IDictionary unusedUserProperties = null;
 
                        // To compute default properties in user scope, we must first
                        // apply the shared properties. Only differences detected from
                        // shared scope are to be persisted.
                        if (sharedInfo != null) {
                            IDictionary properties = sharedInfo._properties;
 
                            if ((properties != null) && (properties.Count != 0)) {
                                hasDataWebPart.SetHasSharedData(true);
                                unusedSharedProperties = SetPersonalizedProperties(control, personalizableProperties,
                                                                                   properties, PersonalizationScope.Shared);
                            }
                        }
                        defaultProperties = GetPersonalizedProperties(control, personalizableProperties, null, null,
                                                                      extractScope);
 
                        // Now apply the user properties and hang on to the initial values
                        if (userInfo != null) {
                            IDictionary properties = userInfo._properties;
 
                            if ((properties != null) && (properties.Count != 0)) {
                                hasDataWebPart.SetHasUserData(true);
                                // We pass the extractScope as the PersonalizationScope in which to set the properties.  For
                                // a shared WebPart, we want to only apply the user values to user properties, and not to
                                // shared properties.  However, for an unshared WebPart, we want to apply the user values
                                // to both user and shared properties, since there is effectively no difference for an
                                // unshared WebPart. (VSWhidbey 349356)
                                unusedUserProperties = SetPersonalizedProperties(control, personalizableProperties,
                                                                                 properties, extractScope);
                            }
 
                            if ((trackingPersonalizable == null) || (trackingPersonalizable.TracksChanges == false)) {
                                initialProperties = properties;
                            }
                        }
 
                        bool hasUnusedProperties = ((unusedSharedProperties != null) || (unusedUserProperties != null));
                        if (hasUnusedProperties) {
                            IVersioningPersonalizable versioningPersonalizable = control as IVersioningPersonalizable;
                            if (versioningPersonalizable != null) {
                                IDictionary unusedProperties = null;
 
                                // Merge any unused properties, so they can be handed off to the
                                // control via IVersioningPersonalizable
                                if (unusedSharedProperties != null) {
                                    unusedProperties = unusedSharedProperties;
                                    if (unusedUserProperties != null) {
                                        foreach (DictionaryEntry entry in unusedUserProperties) {
                                            unusedProperties[entry.Key] = entry.Value;
                                        }
                                    }
                                }
                                else {
                                    unusedProperties = unusedUserProperties;
                                }
 
                                ci._allowSetDirty = true;
                                versioningPersonalizable.Load(unusedProperties);
                                ci._allowSetDirty = false;
                            }
                            else {
                                // There were some unused properties, and they couldn't be loaded.
                                // Mark this control as dirty, so we clean up its personalization
                                // state later...
                                SetControlDirty(control, personalizationID, isWebPartManager, true);
                            }
                        }
                    }
                }
                else {
                    // Shared Personalization Scope
 
                    if (customPersonalizable != null) {
                        PersonalizationDictionary customProperties = MergeCustomProperties(
                            sharedInfo, userInfo, isWebPartManager, hasDataWebPart, ref customInitialProperties);
                        if (customProperties != null) {
                            ci._allowSetDirty = true;
                            customPersonalizable.Load(customProperties);
                            ci._allowSetDirty = false;
                        }
                    }
 
                    if (!isWebPartManager) {
                        IDictionary unusedProperties = null;
 
                        // Compute default properties. These are basically what was persisted
                        // in the markup
                        defaultProperties = GetPersonalizedProperties(control, personalizableProperties, null, null,
                                                                      extractScope);
 
                        // Now apply shared properties and hang on to the initial values
                        if (sharedInfo != null) {
                            IDictionary properties = sharedInfo._properties;
 
                            if ((properties != null) && (properties.Count != 0)) {
                                hasDataWebPart.SetHasSharedData(true);
                                unusedProperties = SetPersonalizedProperties(control, personalizableProperties,
                                                                             properties, PersonalizationScope.Shared);
                            }
 
                            if ((trackingPersonalizable == null) ||
                                (trackingPersonalizable.TracksChanges == false)) {
                                initialProperties = properties;
                            }
                        }
 
                        if (unusedProperties != null) {
                            IVersioningPersonalizable versioningPersonalizable = control as IVersioningPersonalizable;
                            if (versioningPersonalizable != null) {
                                ci._allowSetDirty = true;
                                versioningPersonalizable.Load(unusedProperties);
                                ci._allowSetDirty = false;
                            }
                            else {
                                // There were some unused properties, and they couldn't be loaded.
                                // Mark this control as dirty, so we clean up its personalization
                                // state later...
                                SetControlDirty(control, personalizationID, isWebPartManager, true);
                            }
                        }
                    }
                }
            }
            finally {
                ci._allowSetDirty = true;
                if (trackingPersonalizable != null) {
                    trackingPersonalizable.EndLoad();
                }
            }
 
            // Track this as one of the personalized controls
            ci._control = control;
            ci._personalizableProperties = personalizableProperties;
            ci._defaultProperties = defaultProperties;
            ci._initialProperties = initialProperties;
            ci._customInitialProperties = customInitialProperties;
        }
 
        /// <internalonly />
        public override void ApplyWebPartPersonalization(WebPart webPart) {
            ValidateWebPart(webPart);
 
            // Do not apply personalization to the UnauthorizedWebPart.  It is never rendered
            // in the page, so there is no point to applying the personalization to it.
            // The personalization data from the original WebPart will be round-tripped in
            // ExtractWebPartPersonalization().  We do apply personalization to the ErrorWebPart,
            // because we want it to render with many of the personalized property values of the
            // original WebPart.
            if (webPart is UnauthorizedWebPart) {
                return;
            }
 
            string personalizationID = CreatePersonalizationID(webPart, null);
 
            // In ApplyPersonalization(), we need to extract the default properites in the same scope we will
            // extract the properties in ExtractPersonalization().
            PersonalizationScope extractScope = PersonalizationScope;
            if ((extractScope == PersonalizationScope.User) && (!webPart.IsShared)) {
                // This implies a user owned WebPart in User mode, so extract all
                // the properties
                extractScope = PersonalizationScope.Shared;
            }
 
            ApplyPersonalization(webPart, personalizationID, /* isWebPartManager */ false, extractScope,
                                 /* genericWebPart */ null);
 
            GenericWebPart genericWebPart = webPart as GenericWebPart;
            if (genericWebPart != null) {
                Control containedControl = genericWebPart.ChildControl;
                personalizationID = CreatePersonalizationID(containedControl, genericWebPart);
 
                ApplyPersonalization(containedControl, personalizationID, /* isWebPartManager */ false, extractScope,
                                     genericWebPart);
            }
        }
 
        /// <internalonly />
        public override void ApplyWebPartManagerPersonalization() {
            ApplyPersonalization(WebPartManager, WebPartManagerPersonalizationID, /* isWebPartManager */ true,
                                 PersonalizationScope, /* genericWebPart */ null);
        }
 
        /// <devdoc>
        /// Returns false if the set of new properties are the same as old ones; true if there
        /// are differences.
        /// </devdoc>
        private bool CompareProperties(IDictionary newProperties, IDictionary oldProperties) {
            int newCount = 0;
            int oldCount = 0;
 
            if (newProperties != null) {
                newCount = newProperties.Count;
            }
            if (oldProperties != null) {
                oldCount = oldProperties.Count;
            }
 
            if (newCount != oldCount) {
                return true;
            }
 
            if (newCount != 0) {
                foreach (DictionaryEntry entry in newProperties) {
                    object name = entry.Key;
                    object newValue = entry.Value;
 
                    if (oldProperties.Contains(name)) {
                        object oldValue = oldProperties[name];
 
                        if (Object.Equals(newValue, oldValue) == false) {
                            return true;
                        }
                    }
                    else {
                        return true;
                    }
                }
            }
 
            return false;
        }
 
        private string CreatePersonalizationID(string ID, string genericWebPartID) {
            Debug.Assert(!String.IsNullOrEmpty(ID));
            if (!String.IsNullOrEmpty(genericWebPartID)) {
                return ID + Control.ID_SEPARATOR + genericWebPartID;
            }
            else {
                return ID;
            }
        }
 
        private string CreatePersonalizationID(Control control, WebPart associatedGenericWebPart) {
            if (associatedGenericWebPart != null) {
                return CreatePersonalizationID(control.ID, associatedGenericWebPart.ID);
            }
 
            return CreatePersonalizationID(control.ID, null);
        }
 
        /// <devdoc>
        /// Deserializes personalization data packed as a blob of binary data
        /// into a dictionary with personalization IDs mapped to
        /// PersonalizationInfo objects.
        /// </devdoc>
        private static IDictionary DeserializeData(byte[] data) {
            IDictionary deserializedData = null;
 
            if ((data != null) && (data.Length > 0)) {
                Exception deserializationException = null;
                int version = -1;
 
                object[] items = null;
                int offset = 0;
 
                // Deserialize the data
                try {
                    ObjectStateFormatter formatter =
                        new ObjectStateFormatter(null /* Page(used to determine encryption mode) */, false /*throwOnErrorDeserializing*/);
 
                    if (!HttpRuntime.DisableProcessRequestInApplicationTrust) {
                        // This is more of a consistency and defense-in-depth fix.  Currently we believe
                        // only user code or code with restricted permissions will be running on the stack.
                        // However, to mirror the fix for Session State, and also to hedge against future
                        // scenarios where our current assumptions may change, we should restrict the running
                        // thread to only the permission set currently defined for the app domain.
                        // VSWhidbey 427533
                        if (HttpRuntime.NamedPermissionSet != null && HttpRuntime.ProcessRequestInApplicationTrust) {
                            HttpRuntime.NamedPermissionSet.PermitOnly();
                        }
                    }
 
                    items = (object[])formatter.DeserializeWithAssert(new MemoryStream(data));
                    if (items != null && items.Length != 0) {
                        version = (int)items[offset++];
                    }
                }
                catch (Exception e) {
                    deserializationException = e;
                }
 
                if (version == (int)PersonalizationVersions.WhidbeyBeta2 || version == (int)PersonalizationVersions.WhidbeyRTM) {
                    try {
                        // Build up the dictionary of PersonalizationInfo objects
                        int infoListCount = (int)items[offset++];
 
                        if (infoListCount > 0) {
                            deserializedData = new HybridDictionary(infoListCount, /* caseInsensitive */ false);
                        }
 
                        for (int i = 0; i < infoListCount; i++) {
                            string controlID;
                            bool isStatic;
                            Type controlType = null;
                            VirtualPath controlVPath = null;
 
                            // If this is a dynamic WebPart or control, the Type is not saved in personalization,
                            // so the first item is the controlID.  If this is a static WebPart or control, the
                            // first item is the control Type.
                            object item = items[offset++];
                            if (item is string) {
                                controlID = (string)item;
                                isStatic = false;
                            }
                            else {
                                controlType = (Type)item;
                                if (controlType == typeof(UserControl)) {
                                    controlVPath = VirtualPath.CreateNonRelativeAllowNull((string)items[offset++]);
                                }
                                controlID = (string)items[offset++];
                                isStatic = true;
                            }
 
                            IDictionary properties = null;
                            int propertyCount = (int)items[offset++];
                            if (propertyCount > 0) {
                                properties = new HybridDictionary(propertyCount, /* caseInsensitive */ false);
                                for (int j = 0; j < propertyCount; j++) {
                                    string propertyName = ((IndexedString)items[offset++]).Value;
                                    object propertyValue = items[offset++];
 
                                    properties[propertyName] = propertyValue;
                                }
                            }
 
                            PersonalizationDictionary customProperties = null;
                            int customPropertyCount = (int)items[offset++];
                            if (customPropertyCount > 0) {
                                customProperties = new PersonalizationDictionary(customPropertyCount);
                                for (int j = 0; j < customPropertyCount; j++) {
                                    string propertyName = ((IndexedString)items[offset++]).Value;
                                    object propertyValue = items[offset++];
                                    PersonalizationScope propertyScope =
                                        (bool)items[offset++] ? PersonalizationScope.Shared : PersonalizationScope.User;
                                    bool isSensitive = false;
                                    if (version == (int)PersonalizationVersions.WhidbeyRTM) {
                                        isSensitive = (bool)items[offset++];
                                    }
 
                                    customProperties[propertyName] =
                                        new PersonalizationEntry(propertyValue, propertyScope, isSensitive);
                                }
                            }
 
                            PersonalizationInfo info = new PersonalizationInfo();
                            info._controlID = controlID;
                            info._controlType = controlType;
                            info._controlVPath = controlVPath;
                            info._isStatic = isStatic;
                            info._properties = properties;
                            info._customProperties = customProperties;
 
                            deserializedData[controlID] = info;
                        }
                    }
                    catch (Exception e) {
                        deserializationException = e;
                    }
                }
 
                // Check that there was no deserialization error, and that
                // the data conforms to our known version
                if ((deserializationException != null) ||
                    (version != (int)PersonalizationVersions.WhidbeyBeta2 && version != (int)PersonalizationVersions.WhidbeyRTM)) {
                    throw new ArgumentException(SR.GetString(SR.BlobPersonalizationState_DeserializeError),
                                                "data", deserializationException);
                }
            }
 
            if (deserializedData == null) {
                deserializedData = new HybridDictionary(/* caseInsensitive */ false);
            }
 
            return deserializedData;
        }
 
        /// <devdoc>
        /// Does the actual work of extracting personalizated data from a control
        /// </devdoc>
        private void ExtractPersonalization(Control control, string personalizationID, bool isWebPartManager,
                                            PersonalizationScope scope, bool isStatic, GenericWebPart genericWebPart) {
            Debug.Assert(control != null);
            Debug.Assert(!String.IsNullOrEmpty(personalizationID));
 
            if (_extractedState == null) {
                _extractedState = new HybridDictionary(/* caseInsensitive */ false);
            }
 
            if (_personalizedControls == null) {
                throw new InvalidOperationException(SR.GetString(SR.BlobPersonalizationState_NotApplied));
            }
 
            ControlInfo ci = (ControlInfo)_personalizedControls[personalizationID];
            // The ControlInfo should always have been already created in ApplyPersonalization().
            // However, it  will be null if the Control's ID has changed since we loaded personalization data.
            // This is not supported, but we should throw a helpful exception. (VSWhidbey 372354)
            if (ci == null) {
                throw new InvalidOperationException(SR.GetString(SR.BlobPersonalizationState_CantExtract, personalizationID));
            }
 
            ITrackingPersonalizable trackingPersonalizable = control as ITrackingPersonalizable;
            IPersonalizable customPersonalizable = control as IPersonalizable;
 
            IDictionary properties = ci._initialProperties;
            PersonalizationDictionary customProperties = ci._customInitialProperties;
            bool changed = false;
 
            try {
                if (trackingPersonalizable != null) {
                    trackingPersonalizable.BeginSave();
                }
 
                if (!IsPostRequest) {
                    // In non-POST requests, we only save those WebParts that indicated explicitely that
                    // they have changed. For other WebParts, we just round-trip the initial state
                    // that was loaded.
                    if (ci._dirty) {
                        // Always save IPersonalizable data if the WebPart has indicated that it is dirty
                        if (customPersonalizable != null) {
                            PersonalizationDictionary tempCustomProperties = new PersonalizationDictionary();
 
                            customPersonalizable.Save(tempCustomProperties);
                            if ((tempCustomProperties.Count != 0) ||
                                ((customProperties != null) && (customProperties.Count != 0))) {
                                if (scope == PersonalizationScope.User) {
                                    tempCustomProperties.RemoveSharedProperties();
                                }
                                customProperties = tempCustomProperties;
                            }
                        }
 
                        if (!isWebPartManager) {
                            // WebPartManager does not have personalizable properties
                            properties =
                                GetPersonalizedProperties(control, ci._personalizableProperties,
                                                          ci._defaultProperties, ci._initialProperties, scope);
                        }
                        changed = true;
                    }
                }
                else {
                    bool extractProperties = true;
                    bool diffWithInitialProperties = true;
 
                    if (ci._dirty) {
                        // WebPart is indicating that it is dirty, so there is no need
                        // for us to perform a diff
                        diffWithInitialProperties = false;
                    }
                    else if ((trackingPersonalizable != null) &&
                             (trackingPersonalizable.TracksChanges) &&
                             (ci._dirty == false)) {
                        // WebPart is indicating that it is not dirty, and since it
                        // tracks dirty-ness, theres no need to do additional work.
                        extractProperties = false;
                    }
 
                    if (extractProperties) {
                        // Always save IPersonalizable data if the WebPart has indicated that it is dirty
                        if (customPersonalizable != null && (ci._dirty || customPersonalizable.IsDirty)) {
                            PersonalizationDictionary tempCustomProperties = new PersonalizationDictionary();
                            customPersonalizable.Save(tempCustomProperties);
 
                            // The new custom properties should be used either if they are
                            // non-empty, or they are, but the original ones weren't, since
                            // that implies a change as well.
                            if ((tempCustomProperties.Count != 0) ||
                                ((customProperties != null) && (customProperties.Count != 0))) {
                                if (tempCustomProperties.Count != 0) {
                                    if (scope == PersonalizationScope.User) {
                                        tempCustomProperties.RemoveSharedProperties();
                                    }
                                    customProperties = tempCustomProperties;
                                }
                                else {
                                    customProperties = null;
                                }
 
                                // No point doing the diff, since we've already determined that the
                                // custom properties are dirty.
                                diffWithInitialProperties = false;
                                changed = true;
                            }
                        }
 
                        if (!isWebPartManager) {
                            // WebPartManager does not have personalizable properties
 
                            IDictionary newProperties =
                                GetPersonalizedProperties(control, ci._personalizableProperties,
                                                          ci._defaultProperties, ci._initialProperties, scope);
 
                            if (diffWithInitialProperties) {
                                bool different = CompareProperties(newProperties, ci._initialProperties);
                                if (different == false) {
                                    extractProperties = false;
                                }
                            }
 
                            if (extractProperties) {
                                properties = newProperties;
                                changed = true;
                            }
                        }
                    }
                }
            }
            finally {
                if (trackingPersonalizable != null) {
                    trackingPersonalizable.EndSave();
                }
            }
 
            PersonalizationInfo extractedInfo = new PersonalizationInfo();
            extractedInfo._controlID = personalizationID;
            if (isStatic) {
                UserControl uc = control as UserControl;
                if (uc != null) {
                    extractedInfo._controlType = typeof(UserControl);
                    extractedInfo._controlVPath = uc.TemplateControlVirtualPath;
                }
                else {
                    extractedInfo._controlType = control.GetType();
                }
            }
            extractedInfo._isStatic = isStatic;
            extractedInfo._properties = properties;
            extractedInfo._customProperties = customProperties;
            _extractedState[personalizationID] = extractedInfo;
 
            if (changed) {
                SetDirty();
            }
 
            if ((properties != null && properties.Count > 0) ||
                (customProperties != null && customProperties.Count > 0)) {
 
                // The WebPart on which to set HasSharedData and HasUserData
                WebPart hasDataWebPart = null;
                if (!isWebPartManager) {
                    if (genericWebPart != null) {
                        hasDataWebPart = genericWebPart;
                    }
                    else {
                        Debug.Assert(control is WebPart);
                        hasDataWebPart = (WebPart)control;
                    }
                }
 
                if (hasDataWebPart != null) {
                    if (PersonalizationScope == PersonalizationScope.Shared) {
                        hasDataWebPart.SetHasSharedData(true);
                    }
                    else {
                        hasDataWebPart.SetHasUserData(true);
                    }
                }
            }
        }
 
        /// <internalonly />
        public override void ExtractWebPartPersonalization(WebPart webPart) {
            ValidateWebPart(webPart);
 
            // Round-trip the personalization data for a ProxyWebPart
            ProxyWebPart proxyWebPart = webPart as ProxyWebPart;
            if (proxyWebPart != null) {
                RoundTripWebPartPersonalization(proxyWebPart.OriginalID, proxyWebPart.GenericWebPartID);
                return;
            }
 
            PersonalizationScope extractScope = PersonalizationScope;
            if ((extractScope == PersonalizationScope.User) && (!webPart.IsShared)) {
                // This implies a user owned WebPart in User mode, so save all
                // the properties
                extractScope = PersonalizationScope.Shared;
            }
 
            bool isStatic = webPart.IsStatic;
            string personalizationID = CreatePersonalizationID(webPart, null);
            ExtractPersonalization(webPart, personalizationID, /* isWebPartManager */ false, extractScope, isStatic,
                                   /* genericWebPart */ null);
 
            GenericWebPart genericWebPart = webPart as GenericWebPart;
            if (genericWebPart != null) {
                Control containedControl = genericWebPart.ChildControl;
                personalizationID = CreatePersonalizationID(containedControl, genericWebPart);
                ExtractPersonalization(containedControl, personalizationID, /* isWebPartManager */ false,
                                       extractScope, isStatic, genericWebPart);
            }
        }
 
        /// <internalonly />
        public override void ExtractWebPartManagerPersonalization() {
            ExtractPersonalization(WebPartManager, WebPartManagerPersonalizationID, /* isWebPartManager */ true,
                                   PersonalizationScope, /* isStatic */ true, /* genericWebPart */ null);
        }
 
        // Returns the AuthorizationFilter string for a WebPart before it is instantiated.
        // Returns null if there is no personalized value for AuthorizationFilter, or if the
        // personalized value has a type other than string.
        public override string GetAuthorizationFilter(string webPartID) {
            if (String.IsNullOrEmpty(webPartID)) {
                throw ExceptionUtil.ParameterNullOrEmpty("webPartID");
            }
 
            return GetPersonalizedValue(webPartID, "AuthorizationFilter") as string;
        }
 
        /// <devdoc>
        /// </devdoc>
        internal static IDictionary GetPersonalizedProperties(Control control, PersonalizationScope scope) {
            IDictionary personalizableProperties =
                PersonalizableAttribute.GetPersonalizablePropertyEntries(control.GetType());
 
            return GetPersonalizedProperties(control, personalizableProperties, null, null, scope);
        }
 
        /// <devdoc>
        /// Does the work of retrieving personalized properties. If the scope is User, the shared
        /// personalizable properties are not retrieved. If a non-null defaultPropertyState is
        /// handed in, only the properties that are different from the default values are retrieved.
        /// </devdoc>
        private static IDictionary GetPersonalizedProperties(Control control,
                                                             IDictionary personalizableProperties,
                                                             IDictionary defaultPropertyState,
                                                             IDictionary initialPropertyState,
                                                             PersonalizationScope scope) {
            Debug.Assert(control != null);
 
            if (personalizableProperties.Count == 0) {
                return null;
            }
 
            bool ignoreSharedProperties = (scope == PersonalizationScope.User);
            IDictionary properties = null;
 
            foreach (DictionaryEntry entry in personalizableProperties) {
                PersonalizablePropertyEntry property = (PersonalizablePropertyEntry)entry.Value;
 
                if (ignoreSharedProperties && (property.Scope == PersonalizationScope.Shared)) {
                    continue;
                }
 
                PropertyInfo pi = property.PropertyInfo;
                Debug.Assert(pi != null);
 
                // 
                string name = (string)entry.Key;
                object value = FastPropertyAccessor.GetProperty(control, name, control.DesignMode);
                bool saveProperty = true;
 
                // Only compare to default value if there is no initial value.
                if ((initialPropertyState == null || !initialPropertyState.Contains(name)) && defaultPropertyState != null) {
                    object defaultValue = defaultPropertyState[name];
                    if (Object.Equals(value, defaultValue)) {
                        saveProperty = false;
                    }
                }
 
                if (saveProperty) {
                    if (properties == null) {
                        properties = new HybridDictionary(personalizableProperties.Count, /* caseInsensitive */ false);
                    }
 
                    properties[name] = value;
                }
            }
 
            return properties;
        }
 
        // Returns the value of a personalized property on a control
        // Returns null if there is no personalized value for the property
        private object GetPersonalizedValue(string personalizationID, string propertyName) {
            Debug.Assert(!String.IsNullOrEmpty(personalizationID));
            Debug.Assert(!String.IsNullOrEmpty(propertyName));
 
            if (SharedState == null) {
                throw new InvalidOperationException(SR.GetString(SR.BlobPersonalizationState_NotLoaded));
            }
 
            PersonalizationInfo sharedInfo = (PersonalizationInfo)SharedState[personalizationID];
 
            IDictionary sharedProperties = (sharedInfo != null) ? sharedInfo._properties : null;
            if (PersonalizationScope == PersonalizationScope.Shared) {
                if (sharedProperties != null) {
                    return sharedProperties[propertyName];
                }
            }
            else {
                if (UserState == null) {
                    throw new InvalidOperationException(SR.GetString(SR.BlobPersonalizationState_NotLoaded));
                }
 
                PersonalizationInfo userInfo = (PersonalizationInfo)UserState[personalizationID];
                IDictionary userProperties = (userInfo != null) ? userInfo._properties : null;
                if (userProperties != null && userProperties.Contains(propertyName)) {
                    return userProperties[propertyName];
                }
                else if (sharedProperties != null) {
                    return sharedProperties[propertyName];
                }
            }
 
            return null;
        }
 
        /// <devdoc>
        /// </devdoc>
        public void LoadDataBlobs(byte[] sharedData, byte[] userData) {
            _sharedState = DeserializeData(sharedData);
            _rawUserData = userData;
        }
 
        // Returns a PersonalizationDictionary containing a merged view of the custom properties
        // in both the sharedInfo and the userInfo.
        private PersonalizationDictionary MergeCustomProperties(PersonalizationInfo sharedInfo,
                                                                PersonalizationInfo userInfo,
                                                                bool isWebPartManager, WebPart hasDataWebPart,
                                                                ref PersonalizationDictionary customInitialProperties) {
            PersonalizationDictionary customProperties = null;
 
            bool hasSharedCustomProperties = (sharedInfo != null && sharedInfo._customProperties != null);
            bool hasUserCustomProperties = (userInfo != null && userInfo._customProperties != null);
 
            // Fill or set the customProperties dictionary
            if (hasSharedCustomProperties && hasUserCustomProperties) {
                customProperties = new PersonalizationDictionary();
                foreach (DictionaryEntry entry in sharedInfo._customProperties) {
                    customProperties[(string)entry.Key] = (PersonalizationEntry)entry.Value;
                }
                foreach (DictionaryEntry entry in userInfo._customProperties) {
                    customProperties[(string)entry.Key] = (PersonalizationEntry)entry.Value;
                }
            }
            else if (hasSharedCustomProperties) {
                customProperties = sharedInfo._customProperties;
            }
            else if (hasUserCustomProperties) {
                customProperties = userInfo._customProperties;
            }
 
            // Set the customInitialProperties dictionary
            if (PersonalizationScope == PersonalizationScope.Shared && hasSharedCustomProperties) {
                customInitialProperties = sharedInfo._customProperties;
            }
            else if (PersonalizationScope == PersonalizationScope.User && hasUserCustomProperties) {
                customInitialProperties = userInfo._customProperties;
            }
 
            // Set the HasSharedData and HasUserData flags
            if (hasSharedCustomProperties && !isWebPartManager) {
                hasDataWebPart.SetHasSharedData(true);
            }
            if (hasUserCustomProperties && !isWebPartManager) {
                hasDataWebPart.SetHasUserData(true);
            }
 
            return customProperties;
        }
 
 
        private void RoundTripWebPartPersonalization(string ID, string genericWebPartID) {
            if (String.IsNullOrEmpty(ID)) {
                throw ExceptionUtil.ParameterNullOrEmpty("ID");
            }
 
            // Round-trip personalization for control/WebPart
            string personalizationID = CreatePersonalizationID(ID, genericWebPartID);
            RoundTripWebPartPersonalization(personalizationID);
 
            // Round-trip personalization for GenericWebPart, if necessary
            if (!String.IsNullOrEmpty(genericWebPartID)) {
                string genericPersonalizationID = CreatePersonalizationID(genericWebPartID, null);
                RoundTripWebPartPersonalization(genericPersonalizationID);
            }
        }
 
        private void RoundTripWebPartPersonalization(string personalizationID) {
            Debug.Assert(personalizationID != null);
            // Can't check that personalizationID is valid, since there may be no data
            // for even a valid ID.
 
            if (PersonalizationScope == PersonalizationScope.Shared) {
                if (SharedState == null) {
                    throw new InvalidOperationException(SR.GetString(SR.BlobPersonalizationState_NotLoaded));
                }
                if (SharedState.Contains(personalizationID)) {
                    _extractedState[personalizationID] = (PersonalizationInfo)SharedState[personalizationID];
                }
            }
            else {
                if (UserState == null) {
                    throw new InvalidOperationException(SR.GetString(SR.BlobPersonalizationState_NotLoaded));
                }
                if (UserState.Contains(personalizationID)) {
                    _extractedState[personalizationID] = (PersonalizationInfo)UserState[personalizationID];
                }
            }
        }
 
        /// <devdoc>
        /// </devdoc>
        public byte[] SaveDataBlob() {
            return SerializeData(_extractedState);
        }
 
        /// <devdoc>
        /// Serializes a dictionary of IDs mapped to PersonalizationInfo
        /// objects into a binary blob.
        /// </devdoc>
        private static byte[] SerializeData(IDictionary data) {
            byte[] serializedData = null;
 
            if ((data == null) || (data.Count == 0)) {
                return serializedData;
            }
 
            ArrayList infoList = new ArrayList();
            foreach (DictionaryEntry entry in data) {
                PersonalizationInfo info = (PersonalizationInfo)entry.Value;
 
                if (((info._properties != null) && (info._properties.Count != 0)) ||
                    ((info._customProperties != null) && (info._customProperties.Count != 0))){
                    infoList.Add(info);
                }
            }
 
            if (infoList.Count != 0) {
                ArrayList items = new ArrayList();
 
                items.Add(PersonalizationVersion);
                items.Add(infoList.Count);
 
                foreach (PersonalizationInfo info in infoList) {
                    // Only need to save the type information for static WebParts
                    if (info._isStatic) {
                        items.Add(info._controlType);
                        if (info._controlVPath != null) {
                            items.Add(info._controlVPath.AppRelativeVirtualPathString);
                        }
                    }
 
                    items.Add(info._controlID);
 
                    int propertyCount = 0;
                    if (info._properties != null) {
                        propertyCount = info._properties.Count;
                    }
                    items.Add(propertyCount);
                    if (propertyCount != 0) {
                        foreach (DictionaryEntry propertyEntry in info._properties) {
                            items.Add(new IndexedString((string)propertyEntry.Key));
                            items.Add(propertyEntry.Value);
                        }
                    }
 
                    int customPropertyCount = 0;
                    if (info._customProperties != null) {
                        customPropertyCount = info._customProperties.Count;
                    }
                    items.Add(customPropertyCount);
                    if (customPropertyCount != 0) {
                        foreach (DictionaryEntry customPropertyEntry in info._customProperties) {
                            items.Add(new IndexedString((string)customPropertyEntry.Key));
                            PersonalizationEntry personalizationEntry = (PersonalizationEntry)customPropertyEntry.Value;
                            items.Add(personalizationEntry.Value);
                            // PERF: Add a boolean instead of the Enum value
                            items.Add(personalizationEntry.Scope == PersonalizationScope.Shared);
                            // The IsSensitive property was added between Whidbey Beta2 and Whidbey RTM.
                            // VSWhidbey 502554 and 536907
                            items.Add(personalizationEntry.IsSensitive);
                        }
                    }
                }
 
                if (items.Count != 0) {
                    ObjectStateFormatter formatter = new ObjectStateFormatter(null, false);
                    MemoryStream ms = new MemoryStream(1024);
                    object[] state = items.ToArray();
 
                    if (!HttpRuntime.DisableProcessRequestInApplicationTrust){ 
                        // This is more of a consistency and defense-in-depth fix.  Currently we believe
                        // only user code or code with restricted permissions will be running on the stack.
                        // However, to mirror the fix for Session State, and also to hedge against future
                        // scenarios where our current assumptions may change, we should restrict the running
                        // thread to only the permission set currently defined for the app domain.
                        // VSWhidbey 491449
                        if (HttpRuntime.NamedPermissionSet != null && HttpRuntime.ProcessRequestInApplicationTrust) {
                            HttpRuntime.NamedPermissionSet.PermitOnly();
                        }
                    }
 
                    formatter.SerializeWithAssert(ms, state);
 
                    serializedData = ms.ToArray();
                }
            }
 
            return serializedData;
        }
 
        /// <devdoc>
        /// Only actually sets the control as dirty if we have already started applying personalization
        /// data (info != null), and we are forcing the control to be dirty (forceSetDirty), or the control
        /// has called SetPersonalizationDirty() at the right time (info._allowSetDirty).
        /// </devdoc>
        private void SetControlDirty(Control control, string personalizationID, bool isWebPartManager,
                                     bool forceSetDirty) {
            Debug.Assert(control != null);
            Debug.Assert(!String.IsNullOrEmpty(personalizationID));
 
            if (_personalizedControls == null) {
                throw new InvalidOperationException(SR.GetString(SR.BlobPersonalizationState_NotApplied));
            }
 
            ControlInfo info = (ControlInfo)_personalizedControls[personalizationID];
            if (info != null && (forceSetDirty || info._allowSetDirty)) {
                info._dirty = true;
            }
        }
 
        /// <devdoc>
        /// Called by WebPartPersonalization to copy the personalized values from one control
        /// to another.
        /// </devdoc>
        internal static IDictionary SetPersonalizedProperties(Control control, IDictionary propertyState) {
            IDictionary personalizableProperties =
                PersonalizableAttribute.GetPersonalizablePropertyEntries(control.GetType());
 
            // We pass PersonalizationScope.Shared, since we want to apply all values to their properties.
            return SetPersonalizedProperties(control, personalizableProperties, propertyState, PersonalizationScope.Shared);
        }
 
        /// <devdoc>
        /// Does the work of setting personalized properties
        /// </devdoc>
        private static IDictionary SetPersonalizedProperties(Control control, IDictionary personalizableProperties,
                                                             IDictionary propertyState, PersonalizationScope scope) {
            if (personalizableProperties.Count == 0) {
                // all properties were not used
                return propertyState;
            }
 
            if ((propertyState == null) || (propertyState.Count == 0)) {
                return null;
            }
 
            IDictionary unusedProperties = null;
 
            foreach (DictionaryEntry entry in propertyState) {
                string name = (string)entry.Key;
                object value = entry.Value;
 
                PersonalizablePropertyEntry property = (PersonalizablePropertyEntry)personalizableProperties[name];
                bool propertySet = false;
 
                // Do not apply a user value to a shared property.  This scenario can happen if there
                // is already User data for a property, then the property is changed from Personalizable(User)
                // to Personalizable(Shared). (VSWhidbey 349456)
                if (property != null &&
                    (scope == PersonalizationScope.Shared || property.Scope == PersonalizationScope.User)) {
 
                    PropertyInfo pi = property.PropertyInfo;
                    Debug.Assert(pi != null);
 
                    // If SetProperty() throws an exception, the property will be added to the unusedProperties collection
                    try {
                        FastPropertyAccessor.SetProperty(control, name, value, control.DesignMode);
                        propertySet = true;
                    }
                    catch {
                    }
                }
 
                if (!propertySet) {
                    if (unusedProperties == null) {
                        unusedProperties = new HybridDictionary(propertyState.Count, /* caseInsensitive */ false);
                    }
 
                    unusedProperties[name] = value;
                }
            }
 
            return unusedProperties;
        }
 
        /// <internalonly />
        public override void SetWebPartDirty(WebPart webPart) {
            ValidateWebPart(webPart);
 
            string personalizationID;
 
            personalizationID = CreatePersonalizationID(webPart, null);
            SetControlDirty(webPart, personalizationID, /* isWebPartManager */ false, /* forceSetDirty */ false);
 
            GenericWebPart genericWebPart = webPart as GenericWebPart;
            if (genericWebPart != null) {
                Control containedControl = genericWebPart.ChildControl;
                personalizationID = CreatePersonalizationID(containedControl, genericWebPart);
 
                SetControlDirty(containedControl, personalizationID, /* isWebPartManager */ false, /* forceSetDirty */ false);
            }
        }
 
        /// <internalonly />
        public override void SetWebPartManagerDirty() {
            SetControlDirty(WebPartManager, WebPartManagerPersonalizationID, /* isWebPartManager */ true,
                            /* forceSetDirty */ false);
        }
 
 
        /// <devdoc>
        /// Used to track personalization information, i.e. the data,
        /// and the associated object type and ID.
        /// </devdoc>
        private sealed class PersonalizationInfo {
            public Type _controlType;
            public VirtualPath _controlVPath;
            public string _controlID;
            public bool _isStatic;
 
            public IDictionary _properties;
            public PersonalizationDictionary _customProperties;
 
            public bool IsMatchingControlType(Control c) {
                if (c is ProxyWebPart) {
                    // This code path is currently never hit, since we only load personalization data
                    // for ErrorWebPart, and we only replace dynamic WebParts with the ErrorWebPart,
                    // and we only check IsMatchingControlType() for static WebParts.  However, if this
                    // ever changes in the future, we will want to return true for ProxyWebParts.
                    return true;
                }
                else if (_controlType == null) {
                    // _controlType will be null if there is no longer a Type on the system with the
                    // saved type name.
                    return false;
                }
                else if (_controlType == typeof(UserControl)) {
                    UserControl uc = c as UserControl;
                    if (uc != null) {
                        return uc.TemplateControlVirtualPath == _controlVPath;
                    }
                    return false;
                }
                else {
                    return _controlType.IsAssignableFrom(c.GetType());
                }
            }
        }
 
 
        /// <devdoc>
        /// Used to track personalization information for a Control instance.
        /// </devdoc>
        private sealed class ControlInfo {
            public Control _control;
            public IDictionary _personalizableProperties;
            public bool _dirty;
            public bool _allowSetDirty;
 
            public IDictionary _defaultProperties;
            public IDictionary _initialProperties;
            public PersonalizationDictionary _customInitialProperties;
        }
 
        private enum PersonalizationVersions {
            WhidbeyBeta2 = 1,
            WhidbeyRTM = 2,
        }
    }
}