//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ 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; /// /// 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; /// /// 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); } /// public override bool IsEmpty { get { return ((_extractedState == null) || (_extractedState.Count == 0)); } } /// /// private bool IsPostRequest { get { return _isPostRequest; } } /// /// private PersonalizationScope PersonalizationScope { get { return WebPartManager.Personalization.Scope; } } /// /// This is for symmetry with the UserState property. /// private IDictionary SharedState { get { return _sharedState; } } /// /// User state is always loaded even if the WebPartManager is in shared /// scope. So we on-demand deserialize the bytes. /// private IDictionary UserState { get { if (_rawUserData != null) { _userState = DeserializeData(_rawUserData); _rawUserData = null; } if (_userState == null) { _userState = new HybridDictionary(/* caseInsensitive */ false); } return _userState; } } /// /// Does the work of applying personalization data into a control /// 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; } /// 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); } } /// public override void ApplyWebPartManagerPersonalization() { ApplyPersonalization(WebPartManager, WebPartManagerPersonalizationID, /* isWebPartManager */ true, PersonalizationScope, /* genericWebPart */ null); } /// /// Returns false if the set of new properties are the same as old ones; true if there /// are differences. /// 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); } /// /// Deserializes personalization data packed as a blob of binary data /// into a dictionary with personalization IDs mapped to /// PersonalizationInfo objects. /// 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; } /// /// Does the actual work of extracting personalizated data from a control /// 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); } } } } /// 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); } } /// 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; } /// /// internal static IDictionary GetPersonalizedProperties(Control control, PersonalizationScope scope) { IDictionary personalizableProperties = PersonalizableAttribute.GetPersonalizablePropertyEntries(control.GetType()); return GetPersonalizedProperties(control, personalizableProperties, null, null, scope); } /// /// 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. /// 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; } /// /// 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]; } } } /// /// public byte[] SaveDataBlob() { return SerializeData(_extractedState); } /// /// Serializes a dictionary of IDs mapped to PersonalizationInfo /// objects into a binary blob. /// 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; } /// /// 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). /// 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; } } /// /// Called by WebPartPersonalization to copy the personalized values from one control /// to another. /// 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); } /// /// Does the work of setting personalized properties /// 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; } /// 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); } } /// public override void SetWebPartManagerDirty() { SetControlDirty(WebPartManager, WebPartManagerPersonalizationID, /* isWebPartManager */ true, /* forceSetDirty */ false); } /// /// Used to track personalization information, i.e. the data, /// and the associated object type and ID. /// 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()); } } } /// /// Used to track personalization information for a Control instance. /// 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, } } }