Composite UI Pattern And Enterprise Settings

Composite UI is a pattern that enables us to divide the user interface to separate, loosely coupled modules. The pattern entails many features and a lot of smaller patterns, but it lacks an enterprise solution to target the settings management in an enterprise application.  In this article, I propose a framework that allows the modules to have their own virtual settings and configuration in such a way that the real settings data have been saved in the main settings in the shell.

Introduction


One of the features that many applications have to support is the ability to support settings and configurations in such a way that settings and configuration items can be changed outside the application. Most of the applications in the .NET framework do this using the SettingsBase class. While using the SettingsBase class is easy, it is not suitable for situations where we need to save the settings of different independent modules in a single configuration file. One of the patterns that need to have several settings and configuration files is Composite Application UI Block (CAB) pattern. Saving all of the configuration files in one media while keeping them logically independent in different modules could be quite interesting.

Composite Application UI Block (CAB) is a well known and well defined user interface pattern that targets large, complicated and enterprise applications. It is base is a well known and well defined user interface pattern that targets large, complicated and enterprise applications. It is based on an idea to divide the user interface to independence loosely coupled modules. Each module contains several Views and they can be designed, implemented and tested independently. The main window of the user interface in the pattern is called Shell. The shell has been divided to several parts called Regions. Each region is a simple place that can host a view from a module. Modules can attach their views to the regions. The pattern has several mechanisms for communication among the modules. The following figures provided by CAB team illustrate the whole story.



The pattern based on several smaller patterns like MVVM, Composite Command, Composite Events, Unity Container and etc. Interested reader can find more resources about the CAB here.

One of the missing things in the CAB pattern that strikes me is the lack of an enterprise solution for handling the settings files. Obviously, modules need to save some of its data in the settings files. The simplest solution could be having a settings file per module, but it is not a good practice to have several configuration and settings files. On the other hand, some of the configuration items are common among the modules. The ideal solution would be a framework that allows the modules to have their own virtual settings files and the framework saves the configurations data of all modules together in one media. In fact, we expect the following requirements:

  • Saving all of the Settings data in one media or file in the main application. The module or classes or services in the lower tier should not access that directly.
  • Each module should have its own virtual settings/ configuration; however their settings data should be saved/load in the main settings in the main UI. In other words, the infrastructure should provide a mechanism for the settings in the modules to save load their data in the main settings without accessing the settings of the other modules.
  • The infrastructure should provide a mechanism to avoid conflicts among the settings items of different modules.
  • The infrastructure should be transparent from the developers and they should be able to use the framework easily without any special efforts.

In the following sections I first have a quick view on the Settings Architecture in the .NET framework, then I will illustrate the proposed solution.

SettingsBase

When we talk about the settings and configuration infrastructure, we mean an infrastructure that allows us to save/load user preferences in a media that can be changed outside the application. .NET framework provides a simple approach for supporting settings and configuration. SettingsBase class is an abstract class in the.NET Framework to support settings. It is common among the ASP.NET and Windows application and it allows the user to save/load the configuration data. The customized implementation of the SettingBase for Windows applications (Windows Forms & WPF) is ApplicationSettingsBase class that provides validation events and higher level Save/Load events. Using the class is easy and it covers the settings requirements of many applications. The process of adding a Settings to the application is simple and as follows:

1- Right click on the Project in the Solution explorer and selects Add New Item.

2- In the New Item Window, Select settings and Click OK.

3- Double click on the new Settings file in order to open the Settings window.

4- The Settings Window is a designer for add/edit/delete settings item. For any added item, the designer adds a strongly typed property in the designer code section. The ApplicationBaseSettings uses reflection to extract the items from the code. If you add an item in the settings called Test with data type as string, then the designer code of the settings would be like this.

The following class diagram includes all of the classes that involved in the saving/loading of the settings items.

SettingsBase has two members for keeping the settings items and their values. ( Properties and PropertyValues). The Properties contains a list of SettingsProperty instances and it contains the definition of all of the settings items that must be persisted. The PropertyValues is a collection of SettingsPropertyValue instances and it contains the values of the properties. The SettingsProvider class is responsible for handling the Save/Load process of the properties in the settings class. The two methods of the class (SetPropertyValues and GetPropertyValues) are responsible for saving/loading data respectively. In the loading process, the SettingsProvider gets a collection of SettingsProperties and returns a collection of SettingsPropertyValues. In the saving process, it gets a collection of SettingsPropertyValues and it persists them. Here is the signature of the class.

public abstract class SettingsProvider : ProviderBase

{

// Summary:

// Initializes an instance of the System.Configuration.SettingsProvider class.

protected SettingsProvider();

// Summary:

// Gets or sets the name of the currently running application.

//

// Returns:

// A System.String that contains the application's shortened name, which does

// not contain a full path or extension, for example, SimpleAppSettings.

public abstract string ApplicationName { get; set; }

// Summary:

// Returns the collection of settings property values for the specified application

// instance and settings property group.

//

// Parameters:

// context:

// A System.Configuration.SettingsContext describing the current application

// use.

//

// collection:

// A System.Configuration.SettingsPropertyCollection containing the settings

// property group whose values are to be retrieved.

//

// Returns:

// A System.Configuration.SettingsPropertyValueCollection containing the values

// for the specified settings property group.

public abstract SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection);

//

// Summary:

// Sets the values of the specified group of property settings.

//

// Parameters:

// context:

// A System.Configuration.SettingsContext describing the current application

// usage.

//

// collection:

// A System.Configuration.SettingsPropertyValueCollection representing the group

// of property settings to set.

public abstract void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection);

}


GetPtoprtyValues has been called by SettingsBase to load the values from the persisted media and SetPropertyValues has been called to persist the PropertyValues in the media. The SettingsContext parameter which is used in both methods contains the meta data that can be used during the load/save process.

LocalFileSettingsProvider is a build in class in the .NET framework that implements the abstract SettingsProvider class. ApplicationSettingsBase uses LocalFileSettingsProvider for persisting data.

Custom SettingsProvider

In order to have a solution that covers our requirements, we should implement our own SettingsProvider. The custom SettingsProvider should cover the following requirements.

1. Gets a reference of main settings and persists the local settings items in the main settings.

2. Provide a mechanism to avoid conflicts in the main settings. (Since all of the settings save their items in the main settings, we should provide a mechanism to avoid conflicts).

I implement a simple approach to avoid conflicts. In the saving scenario, my custom SettingsProvider adds the module name to the beginning of the names of settings items before saving them in the main settings. In the loading scenario, my custom SettingsProvider removes the module name from the beginning of the names of settings items and then forwards them to the SettinngsBase. Here is the code of my provider.

[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]

[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")]

public class CompositeSettingProvider : SettingsProvider

{

public SettingsProvider OriginalSettingsProvider { get; set; }

public CompositeSettingProvider()

{

}

public override void Initialize(string name, NameValueCollection config)

{

base.Initialize("test", new NameValueCollection());

}

public string ModuleName { get; set; }

#region SettingsProvider

public override string Name

{

get

{

return "test";

}

}

public override string ApplicationName

{

get

{

return applicationName;

}

set

{

applicationName = value;

}

}

private string applicationName;

private SettingsProvider getOriginalSettingsProvider(SettingsContext context)

{

return null;

}

public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)

{

return CompositeSettingHelper.GetPropertyValues(context, collection);

}

public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)

{

CompositeSettingHelper.SetPropertyValues(context, collection);

}

#endregion

}

public class CompositeSettingHelper

{

public const string ModuleNameKey = "ModuleName";

public const string OriginalSettingsProviderKey = "OriginalSettingsProvider";

public const string ApplicationBaseKey = "ApplicationBase";

internal const string CompositeSettingsProviderKey = "CompositeSettingsProvider";

public static SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)

{

string ModuleName = context[ModuleNameKey].ToString();

SettingsProvider OriginalSettingsProvider = (SettingsProvider)context[OriginalSettingsProviderKey];

ApplicationSettingsBase app = (ApplicationSettingsBase)context[ApplicationBaseKey];

SettingsPropertyCollection renamedCollection = new SettingsPropertyCollection();

foreach (SettingsProperty property in collection)

{

SettingsProperty newProperty = new SettingsProperty(

String.Format("{0}.{1}", ModuleName, property.Name),

property.PropertyType,

OriginalSettingsProvider,

property.IsReadOnly,

property.DefaultValue,

property.SerializeAs,

property.Attributes,

property.ThrowOnErrorDeserializing,

property.ThrowOnErrorSerializing);

renamedCollection.Add(newProperty);

}

SettingsPropertyValueCollection settingsPropertyValueCollection = OriginalSettingsProvider.GetPropertyValues(context, renamedCollection);

SettingsPropertyValueCollection output = new SettingsPropertyValueCollection();

foreach (SettingsPropertyValue propertyValue in settingsPropertyValueCollection)

{

string[] nameParts = propertyValue.Property.Name.Split(new char[] { '.' });

SettingsProperty newProperty = new SettingsProperty(

nameParts[nameParts.Length - 1],

propertyValue.Property.PropertyType,

OriginalSettingsProvider,

propertyValue.Property.IsReadOnly,

propertyValue.Property.DefaultValue,

propertyValue.Property.SerializeAs,

propertyValue.Property.Attributes,

propertyValue.Property.ThrowOnErrorDeserializing,

propertyValue.Property.ThrowOnErrorSerializing);

SettingsPropertyValue newItem = new SettingsPropertyValue(newProperty);

newItem.PropertyValue = propertyValue.PropertyValue;

newItem.IsDirty = propertyValue.IsDirty;

output.Add(newItem);

}

return output;

}

public static void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)

{

string ModuleName = context[ModuleNameKey].ToString();

SettingsProvider OriginalSettingsProvider = (SettingsProvider)context[OriginalSettingsProviderKey];

ApplicationSettingsBase app = (ApplicationSettingsBase)context[ApplicationBaseKey];

SettingsPropertyValueCollection renamedCollection = new SettingsPropertyValueCollection();

foreach (SettingsPropertyValue propertyValue in collection)

{

SettingsProperty newProperty = new SettingsProperty(

String.Format("{0}.{1}", ModuleName, propertyValue.Property.Name),

propertyValue.Property.PropertyType,

OriginalSettingsProvider,

propertyValue.Property.IsReadOnly,

propertyValue.Property.DefaultValue,

propertyValue.Property.SerializeAs,

propertyValue.Property.Attributes,

propertyValue.Property.ThrowOnErrorDeserializing,

propertyValue.Property.ThrowOnErrorSerializing);

SettingsPropertyValue newItem = new SettingsPropertyValue(newProperty);

newItem.PropertyValue = propertyValue.PropertyValue;

renamedCollection.Add(newItem);

}

OriginalSettingsProvider.SetPropertyValues(context, renamedCollection);

}

}

[SettingsProvider(typeof(CompositeSettingProvider))]

public partial class CompositeSettings : System.Configuration.ApplicationSettingsBase

{

public override System.Configuration.SettingsProviderCollection Providers

{

get

{

return new System.Configuration.SettingsProviderCollection();

}

}

public override object this[string propertyName]

{

get

{

return base[propertyName];

}

set

{

if (this.Properties[propertyName] != null)

{

base[propertyName] = value;

}

else

{

SettingsProperty settingsProperty = new SettingsProperty(propertyName);

this.Properties.Add(settingsProperty);

SettingsPropertyValue settingsPropertyValue = new SettingsPropertyValue(settingsProperty);

settingsPropertyValue.PropertyValue = value;

this.PropertyValues.Add(settingsPropertyValue);

}

}

}

public override void Save()

{

CompositeSettingHelper.SetPropertyValues(this.Context, this.PropertyValues);

base.Save();

}

public void Reload()

{

CompositeSettingHelper.GetPropertyValues(this.Context, this.Properties);

}

}

Using the code

In order to use the code, the developer should create an instance of the CompositeSettings class and sets its properties. Here is the code.

[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]

[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")]

[SettingsProvider(typeof(CompositeSettingProvider))]

public class HelloWorldSettings : CompositeSettings

{

private static HelloWorldSettings defaultInstance = ((HelloWorldSettings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new HelloWorldSettings())));

public static HelloWorldSettings Default

{

get

{

return defaultInstance;

}

}

[global::System.Configuration.UserScopedSettingAttribute()]

[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]

[global::System.Configuration.DefaultSettingValueAttribute("")]

public string Test

{

get

{

return ((string)(this["Test"]));

}

set

{

this["Test"] = value;

}

}

}

In the definition of the module, a reference of the main SettingsProvider and main AppSettingsBase should be passed to the custom provider.

public class HelloWorldModule : IModule

{

private readonly IRegionViewRegistry regionViewRegistry;

public HelloWorldModule(IRegionViewRegistry registry, IUnityContainer container)

{

this.regionViewRegistry = registry;

SettingsProvider provider = container.Resolve<SettingsProvider>();

ApplicationSettingsBase app = container.Resolve<ApplicationSettingsBase>();

HelloWorldSettings.Default.Context.Add(CompositeSettingHelper.OriginalSettingsProviderKey, provider);

HelloWorldSettings.Default.Context.Add(CompositeSettingHelper.ModuleNameKey, "HelloWorld");

HelloWorldSettings.Default.Context.Add(CompositeSettingHelper.ApplicationBaseKey, app);

HelloWorldSettings.Default.Reload();

}

public void Initialize()

{

regionViewRegistry.RegisterViewWithRegion("MainRegion", typeof(Views.HelloWorldView));

}

}

In this post, I showed a mechanism to saves all of the configuration data in one media in the Composite UI Application Block. However the solution does not restricted to the Composite UI pattern and it can be used in any multi tier applications too.

The sample code can be downloaded from here.

By Siyamand Ayubi   Popularity  (2572 Views)