|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
namespace System.Configuration
{
/// <summary>
/// Base settings class for client applications.
/// </summary>
public abstract class ApplicationSettingsBase : SettingsBase, INotifyPropertyChanged
{
private bool _explicitSerializeOnClass;
private object[] _classAttributes;
private readonly IComponent _owner;
private PropertyChangedEventHandler _onPropertyChanged;
private SettingsContext _context;
private SettingsProperty _init;
private SettingsPropertyCollection _settings;
private SettingsProviderCollection _providers;
private SettingChangingEventHandler _onSettingChanging;
private SettingsLoadedEventHandler _onSettingsLoaded;
private SettingsSavingEventHandler _onSettingsSaving;
private string _settingsKey = string.Empty;
private bool _initialized;
/// <summary>
/// Default constructor without a concept of "owner" component.
/// </summary>
protected ApplicationSettingsBase() : base()
{
}
/// <summary>
/// Constructor that takes an IComponent. The IComponent acts as the "owner" of this settings class. One
/// of the things we do is query the component's site to see if it has a SettingsProvider service. If it
/// does, we allow it to override the providers specified in the metadata.
/// </summary>
protected ApplicationSettingsBase(IComponent owner) : this(owner, string.Empty)
{
}
/// <summary>
/// Convenience overload that takes the settings key
/// </summary>
protected ApplicationSettingsBase(string settingsKey)
{
_settingsKey = settingsKey;
}
/// <summary>
/// Convenience overload that takes the owner component and settings key.
/// </summary>
protected ApplicationSettingsBase(IComponent owner, string settingsKey) : this(settingsKey)
{
if (owner is null)
{
throw new ArgumentNullException(nameof(owner));
}
_owner = owner;
if (owner.Site != null)
{
ISettingsProviderService providerService = owner.Site.GetService(typeof(ISettingsProviderService)) as ISettingsProviderService;
if (providerService != null)
{
// The component's site has a settings provider service. We pass each SettingsProperty to it
// to see if it wants to override the current provider.
foreach (SettingsProperty property in Properties)
{
SettingsProvider provider = providerService.GetSettingsProvider(property);
if (provider != null)
{
property.Provider = provider;
}
}
ResetProviders();
}
}
}
/// <summary>
/// The Context to pass on to the provider. Currently, this will just contain the settings group name.
/// </summary>
[Browsable(false)]
public override SettingsContext Context
{
get
{
if (_context == null)
{
if (IsSynchronized)
{
lock (this)
{
if (_context == null)
{
_context = new SettingsContext();
EnsureInitialized();
}
}
}
else
{
_context = new SettingsContext();
EnsureInitialized();
}
}
return _context;
}
}
/// <summary>
/// The SettingsBase class queries this to get the collection of SettingsProperty objects. We reflect over
/// the properties defined on the current object's type and use the metadata on those properties to form
/// this collection.
/// </summary>
[Browsable(false)]
public override SettingsPropertyCollection Properties
{
get
{
if (_settings == null)
{
if (IsSynchronized)
{
lock (this)
{
if (_settings == null)
{
_settings = new SettingsPropertyCollection();
EnsureInitialized();
}
}
}
else
{
_settings = new SettingsPropertyCollection();
EnsureInitialized();
}
}
return _settings;
}
}
/// <summary>
/// Just overriding to add attributes.
/// </summary>
[Browsable(false)]
public override SettingsPropertyValueCollection PropertyValues
{
get
{
return base.PropertyValues;
}
}
/// <summary>
/// Provider collection
/// </summary>
[Browsable(false)]
public override SettingsProviderCollection Providers
{
get
{
if (_providers == null)
{
if (IsSynchronized)
{
lock (this)
{
if (_providers == null)
{
_providers = new SettingsProviderCollection();
EnsureInitialized();
}
}
}
else
{
_providers = new SettingsProviderCollection();
EnsureInitialized();
}
}
return _providers;
}
}
/// <summary>
/// Derived classes should use this to uniquely identify separate instances of settings classes.
/// </summary>
[Browsable(false)]
public string SettingsKey
{
get
{
return _settingsKey;
}
set
{
_settingsKey = value;
Context["SettingsKey"] = _settingsKey;
}
}
/// <summary>
/// Fires when the value of a setting is changed. (INotifyPropertyChanged implementation.)
/// </summary>
public event PropertyChangedEventHandler PropertyChanged
{
add
{
_onPropertyChanged += value;
}
remove
{
_onPropertyChanged -= value;
}
}
/// <summary>
/// Fires when the value of a setting is about to change. This is a cancellable event.
/// </summary>
public event SettingChangingEventHandler SettingChanging
{
add
{
_onSettingChanging += value;
}
remove
{
_onSettingChanging -= value;
}
}
/// <summary>
/// Fires when settings are retrieved from a provider. It fires once for each provider.
/// </summary>
public event SettingsLoadedEventHandler SettingsLoaded
{
add
{
_onSettingsLoaded += value;
}
remove
{
_onSettingsLoaded -= value;
}
}
/// <summary>
/// Fires when Save() is called. This is a cancellable event.
/// </summary>
public event SettingsSavingEventHandler SettingsSaving
{
add
{
_onSettingsSaving += value;
}
remove
{
_onSettingsSaving -= value;
}
}
/// <summary>
/// Used in conjunction with Upgrade - retrieves the previous value of a setting from the provider.
/// Provider must implement IApplicationSettingsProvider to support this.
/// </summary>
public object GetPreviousVersion(string propertyName)
{
if (Properties.Count == 0)
throw new SettingsPropertyNotFoundException();
SettingsProperty sp = Properties[propertyName];
SettingsPropertyValue value = null;
if (sp == null)
throw new SettingsPropertyNotFoundException();
IApplicationSettingsProvider clientProv = sp.Provider as IApplicationSettingsProvider;
if (clientProv != null)
{
value = clientProv.GetPreviousVersion(Context, sp);
}
if (value != null)
{
return value.PropertyValue;
}
return null;
}
/// <summary>
/// Fires the PropertyChanged event.
/// </summary>
protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
_onPropertyChanged?.Invoke(this, e);
}
/// <summary>
/// Fires the SettingChanging event.
/// </summary>
protected virtual void OnSettingChanging(object sender, SettingChangingEventArgs e)
{
_onSettingChanging?.Invoke(this, e);
}
/// <summary>
/// Fires the SettingsLoaded event.
/// </summary>
protected virtual void OnSettingsLoaded(object sender, SettingsLoadedEventArgs e)
{
_onSettingsLoaded?.Invoke(this, e);
}
/// <summary>
/// Fires the SettingsSaving event.
/// </summary>
protected virtual void OnSettingsSaving(object sender, CancelEventArgs e)
{
_onSettingsSaving?.Invoke(this, e);
}
/// <summary>
/// Causes a reload to happen on next setting access, by clearing the cached values.
/// </summary>
public void Reload()
{
PropertyValues?.Clear();
foreach (SettingsProperty sp in Properties)
{
PropertyChangedEventArgs pe = new PropertyChangedEventArgs(sp.Name);
OnPropertyChanged(this, pe);
}
}
/// <summary>
/// Calls Reset on the providers.
/// Providers must implement IApplicationSettingsProvider to support this.
/// </summary>
public void Reset()
{
if (Properties != null)
{
foreach (SettingsProvider provider in Providers)
{
(provider as IApplicationSettingsProvider)?.Reset(Context);
}
}
Reload();
}
/// <summary>
/// Overridden from SettingsBase to support validation event.
/// </summary>
public override void Save()
{
CancelEventArgs e = new CancelEventArgs(false);
OnSettingsSaving(this, e);
if (!e.Cancel)
{
base.Save();
}
}
/// <summary>
/// Overridden from SettingsBase to support validation event.
/// </summary>
public override object this[string propertyName]
{
get
{
if (IsSynchronized)
{
lock (this)
{
return GetPropertyValue(propertyName);
}
}
else
{
return GetPropertyValue(propertyName);
}
}
set
{
SettingChangingEventArgs e = new SettingChangingEventArgs(propertyName, this.GetType().FullName, SettingsKey, value, false);
OnSettingChanging(this, e);
if (!e.Cancel)
{
base[propertyName] = value;
// CONSIDER: Should we call this even if canceled?
PropertyChangedEventArgs pe = new PropertyChangedEventArgs(propertyName);
OnPropertyChanged(this, pe);
}
}
}
/// <summary>
/// Called when the app is upgraded so that we can instruct the providers to upgrade their settings.
/// Providers must implement IApplicationSettingsProvider to support this.
/// </summary>
public virtual void Upgrade()
{
if (Properties != null)
{
foreach (SettingsProvider provider in Providers)
{
(provider as IApplicationSettingsProvider)?.Upgrade(Context, GetPropertiesForProvider(provider));
}
}
Reload();
}
/// <summary>
/// Creates a SettingsProperty object using the metadata on the given property
/// and returns it.
/// </summary>
private SettingsProperty CreateSetting(PropertyInfo propertyInfo)
{
// Initialization method -
// be careful not to access properties here to prevent stack overflow.
object[] attributes = propertyInfo.GetCustomAttributes(false);
SettingsProperty settingsProperty = new SettingsProperty(Initializer);
bool explicitSerialize = _explicitSerializeOnClass;
settingsProperty.Name = propertyInfo.Name;
settingsProperty.PropertyType = propertyInfo.PropertyType;
for (int i = 0; i < attributes.Length; i++)
{
Attribute attribute = attributes[i] as Attribute;
if (attribute == null)
continue;
if (attribute is DefaultSettingValueAttribute)
{
settingsProperty.DefaultValue = ((DefaultSettingValueAttribute)attribute).Value;
}
else if (attribute is ReadOnlyAttribute)
{
settingsProperty.IsReadOnly = true;
}
else if (attribute is SettingsProviderAttribute)
{
string providerTypeName = ((SettingsProviderAttribute)attribute).ProviderTypeName;
Type providerType = Type.GetType(providerTypeName);
if (providerType == null)
{
throw new ConfigurationErrorsException(SR.Format(SR.ProviderTypeLoadFailed, providerTypeName));
}
SettingsProvider settingsProvider = TypeUtil.CreateInstance(providerType) as SettingsProvider;
if (settingsProvider == null)
{
throw new ConfigurationErrorsException(SR.Format(SR.ProviderInstantiationFailed, providerTypeName));
}
settingsProvider.Initialize(null, null);
settingsProvider.ApplicationName = ConfigurationManagerInternalFactory.Instance.ExeProductName;
// See if we already have a provider of the same name in our collection. If so,
// re-use the existing instance, since we cannot have multiple providers of the same name.
SettingsProvider existing = _providers[settingsProvider.Name];
if (existing != null)
{
settingsProvider = existing;
}
settingsProperty.Provider = settingsProvider;
}
else if (attribute is SettingsSerializeAsAttribute)
{
settingsProperty.SerializeAs = ((SettingsSerializeAsAttribute)attribute).SerializeAs;
explicitSerialize = true;
}
else
{
// This isn't an attribute we care about, so simply pass it on
// to the SettingsProvider.
//
// NOTE: The key is the type. So if an attribute was found at class
// level and also property level, the latter overrides the former
// for a given setting. This is exactly the behavior we want.
settingsProperty.Attributes[attribute.GetType()] = attribute;
}
}
if (!explicitSerialize)
{
// Serialization method was not explicitly attributed.
TypeConverter tc = TypeDescriptor.GetConverter(propertyInfo.PropertyType);
if (tc.CanConvertTo(typeof(string)) && tc.CanConvertFrom(typeof(string)))
{
// We can use string
settingsProperty.SerializeAs = SettingsSerializeAs.String;
}
else
{
// Fallback is Xml
settingsProperty.SerializeAs = SettingsSerializeAs.Xml;
}
}
return settingsProperty;
}
/// <summary>
/// Ensures this class is initialized. Initialization involves reflecting over properties and building
/// a list of SettingsProperty's.
/// </summary>
private void EnsureInitialized()
{
// Initialization method -
// be careful not to access properties here to prevent stack overflow.
if (!_initialized)
{
_initialized = true;
Type type = GetType();
_context ??= new SettingsContext();
_context["GroupName"] = type.FullName;
_context["SettingsKey"] = SettingsKey;
_context["SettingsClassType"] = type;
PropertyInfo[] properties = SettingsFilter(type.GetProperties(BindingFlags.Instance | BindingFlags.Public));
_classAttributes = type.GetCustomAttributes(false);
_settings ??= new SettingsPropertyCollection();
_providers ??= new SettingsProviderCollection();
for (int i = 0; i < properties.Length; i++)
{
SettingsProperty sp = CreateSetting(properties[i]);
if (sp != null)
{
_settings.Add(sp);
if (sp.Provider != null && _providers[sp.Provider.Name] == null)
{
_providers.Add(sp.Provider);
}
}
}
}
}
/// <summary>
/// Returns a SettingsProperty used to initialize settings. We initialize a setting with values
/// derived from class level attributes, if present. Otherwise, we initialize to
/// reasonable defaults.
/// </summary>
private SettingsProperty Initializer
{
// Initialization method -
// be careful not to access properties here to prevent stack overflow.
get
{
if (_init == null)
{
_init = new SettingsProperty("");
_init.DefaultValue = null;
_init.IsReadOnly = false;
_init.PropertyType = null;
SettingsProvider provider = new LocalFileSettingsProvider();
if (_classAttributes != null)
{
for (int i = 0; i < _classAttributes.Length; i++)
{
Attribute attr = _classAttributes[i] as Attribute;
if (attr != null)
{
if (attr is ReadOnlyAttribute)
{
_init.IsReadOnly = true;
}
else if (attr is SettingsGroupNameAttribute)
{
_context ??= new SettingsContext();
_context["GroupName"] = ((SettingsGroupNameAttribute)attr).GroupName;
}
else if (attr is SettingsProviderAttribute)
{
string providerTypeName = ((SettingsProviderAttribute)attr).ProviderTypeName;
Type providerType = Type.GetType(providerTypeName);
if (providerType != null)
{
SettingsProvider spdr = TypeUtil.CreateInstance(providerType) as SettingsProvider;
if (spdr != null)
{
provider = spdr;
}
else
{
throw new ConfigurationErrorsException(SR.Format(SR.ProviderInstantiationFailed, providerTypeName));
}
}
else
{
throw new ConfigurationErrorsException(SR.Format(SR.ProviderTypeLoadFailed, providerTypeName));
}
}
else if (attr is SettingsSerializeAsAttribute)
{
_init.SerializeAs = ((SettingsSerializeAsAttribute)attr).SerializeAs;
_explicitSerializeOnClass = true;
}
else
{
// This isn't an attribute we care about, so simply pass it on
// to the SettingsProvider.
// NOTE: The key is the type. So if an attribute was found at class
// level and also property level, the latter overrides the former
// for a given setting. This is exactly the behavior we want.
_init.Attributes.Add(attr.GetType(), attr);
}
}
}
}
//Initialize the SettingsProvider
provider.Initialize(null, null);
provider.ApplicationName = ConfigurationManagerInternalFactory.Instance.ExeProductName;
_init.Provider = provider;
}
return _init;
}
}
/// <summary>
/// Gets all the settings properties for this provider.
/// </summary>
private SettingsPropertyCollection GetPropertiesForProvider(SettingsProvider provider)
{
SettingsPropertyCollection properties = new SettingsPropertyCollection();
foreach (SettingsProperty sp in Properties)
{
if (sp.Provider == provider)
{
properties.Add(sp);
}
}
return properties;
}
/// <summary>
/// Retrieves the value of a setting. We need this method so we can fire the SettingsLoaded event
/// when settings are loaded from the providers.Ideally, this should be fired from SettingsBase,
/// but unfortunately that will not happen in Whidbey. Instead, we check to see if the value has already
/// been retrieved. If not, we fire the load event, since we expect SettingsBase to load all the settings
/// from this setting's provider.
/// </summary>
private object GetPropertyValue(string propertyName)
{
if (PropertyValues[propertyName] == null)
{
// we query the value first so that we initialize all values from value providers and so that we don't end up
// on an infinite recursion when calling Properties[propertyName] as that calls this.
_ = base[propertyName];
SettingsProperty setting = Properties[propertyName];
SettingsProvider provider = setting?.Provider;
Debug.Assert(provider != null, "Could not determine provider from which settings were loaded");
SettingsLoadedEventArgs e = new SettingsLoadedEventArgs(provider);
OnSettingsLoaded(this, e);
// Note: we need to requery the value here in case someone changed it while
// handling SettingsLoaded.
return base[propertyName];
}
else
{
return base[propertyName];
}
}
/// <summary>
/// Only those settings class properties that have a SettingAttribute on them are
/// treated as settings. This routine filters out other properties.
/// </summary>
private static PropertyInfo[] SettingsFilter(PropertyInfo[] allProps)
{
var settingProps = new List<PropertyInfo>();
object[] attributes;
Attribute attr;
for (int i = 0; i < allProps.Length; i++)
{
attributes = allProps[i].GetCustomAttributes(false);
for (int j = 0; j < attributes.Length; j++)
{
attr = attributes[j] as Attribute;
if (attr is SettingAttribute)
{
settingProps.Add(allProps[i]);
break;
}
}
}
return settingProps.ToArray();
}
/// <summary>
/// Resets the provider collection. This needs to be called when providers change after
/// first being set.
/// </summary>
private void ResetProviders()
{
Providers.Clear();
foreach (SettingsProperty sp in Properties)
{
if (Providers[sp.Provider.Name] == null)
{
Providers.Add(sp.Provider);
}
}
}
}
}
|