📜 ⬆️ ⬇️

Using the Managed Extensibility Framework (MEF) to Develop Modular Silverlight Applications

The MEF library appeared relatively recently, but quickly gained popularity among .Net developers for its ease of use and efficiency. It allows you to build modular applications with a minimum level of connectivity parts (parts) of the application. This library includes not only the Dependency Injection container, but a large amount of infrastructure: a variety of mechanisms for searching for composition elements in assemblies, remote XAP files, a mechanism for marking composition elements using .Net attributes, etc.

There is a version of the MEF for Silverlight, which is different from the desktop version. We will talk about the features of using MEF for Silverlight applications in this article.

Differences from the desktop version

Several specific classes have been added to MEF for Siverlight.
CompositionInitializer
In Silverlight, MEF developers propose to use the CompositionInitializer class (the System.ComponentModel.CompositionInitialization.dll library), which allows you to create a composition for a specific object, initializing all imports of this object and other dependent entities. This functionality is especially important for Silverlight applications, where the decentralization of the component elements of an application is particularly pronounced.
')
The first call to the SatisfyImports() method of this class creates a global container that will be used in all further calls to SatisfyImports() . SatisfyImports produces a composition of all objects that are found in the current assembly and all dependent assemblies (that is, within the entire XAP file). Objects instantiated during composition will be in the container until the latter is destroyed, i.e. until the end of the program.

There are several features of using this class:
  1. Classes that call the SatisfyImports() method cannot have the [Export] attribute;
  2. Objects are instantiated only once and stored in a container;
  3. By default, only the current XAP file is subject to composition, which is easy to fix.
Example:
public partial class Shell : UserControl<br>{<br> public MainPage()<br> {<br> ComposeContainer()<br> }<br><br> private void ComposeContainer()<br> {<br> CompositionInitializer.SatisfyImports( this );<br> }<br>} <br><br> * This source code was highlighted with Source Code Highlighter .
DeploymentCatalog
This class is designed to dynamically download XAP files, which allows you to even more decentralize development, reduce the size of the main XAP file, increase the download speed of the application, etc. XAP files are loaded in asynchronous mode, and the developer can subscribe to a notification that the file has finished downloading and handle errors if they exist.

This class is part of the idea of ​​recomposition (re-composing a composition from existing and added parts), carrying out the latter, if there are parts that allow recomposition.

Features of using DeploymentCatalog :
  1. The browser cache is used when the application is offline.
  2. The CompositionHost class must be used (see below).
  3. If there are identical assemblies in different XAP files, DeploymentCatalog will try to add them all to the catalog, which can lead to an exception (exception) if recomposition is not allowed. You should set CopyLocal=False to duplicate assemblies of the entire application or use my extension for VS2010 XapsMinifier
  4. Only the assemblies that are specified in the manifest file are loaded from the XAP directory.
  5. In Out of Browser mode, downloadable assemblies are not cached in the file system.
Example:
private void CancelLoading()<br>{<br> catalog.CancelAsync();<br>}<br><br> private void LoadXapFile( string xapPath)<br>{<br> DeploymentCatalog catalog = new DeploymentCatalog(xapPath);<br> catalog.DownloadCompleted += new EventHandler<AsyncCompletedEventArgs>(DownloadCompleted);<br> catalog.DownloadProgressChanged += new EventHandler<DownloadProgressChangedEventArgs>(ProgressChanged);<br> catalog.DownloadAsync();<br> _aggregateCatalog.Catalogs.Add(catalog);<br>}<br><br> void DownloadCompleted( object sender, System.ComponentModel.AsyncCompletedEventArgs e)<br>{<br> if (e.Error != null )<br> throw e.Error;<br>}<br><br> void catalog_DownloadProgressChanged( object sender, DownloadProgressChangedEventArgs e)<br>{<br> int progress = e.ProgressPercentage;<br> long received = e.BytesReceived;<br> long total = e.TotalBytesToReceive;<br>} <br><br> * This source code was highlighted with Source Code Highlighter .
Compositionhost
This class is the link between the directories that contain information on the export / import of parts, and the container that contains the composition of parts. This class allows you to override the initial configuration of the application (loading parts only from the current XAP file). It allows you to specify a set of directories, the contents of which will be tracked, and in case of a change (add / delete), the recomposition of existing parts will be called.

Example:
public partial class Shell : UserControl<br>{<br> public MainPage()<br> {<br> ComposeContainer()<br> }<br><br> private void ComposeContainer()<br> {<br> _aggregateCatalog = new AggregateCatalog( new DeploymentCatalog());<br> CompositionHost.Initialize(_aggregateCatalog);<br> CompositionInitializer.SatisfyImports( this );<br> }<br>} <br><br> * This source code was highlighted with Source Code Highlighter .
ExportFactory <T>
In some situations, you need to create multiple instances of the composition elements. For example, if the application allows the user to create several copies of documents (elements of the composition), then this cannot be achieved by conventional means. Take advantage of ExportFactory <T>.

Example:
[Export]<br> public class DocumentViewModel {<br><br> [Import] <br> public ExportFactory<IDocument> DocumentFactory<br> {<br> get ;<br> set ;<br> }<br><br> protected List <IDocument> Documents<br> {<br> get ;<br> set ;<br> }<br><br> public void CreateDocument() <br> {<br> Documents.Add(DocumentFactory.CreateExport().Value);<br> }<br>} <br><br> * This source code was highlighted with Source Code Highlighter .
Creating parts of the composition using ExportFactory <T>, the developer takes full responsibility for the lifetime of the created instance. If instances require freeing memory, then you can use the standard Dispose() pattern.

Example:
[Export]<br> public class DocumentViewModel : IDisposable <br>{<br> private bool isDisposed = false ;<br> [Import] <br> public ExportFactory<IDocument> DocumentFactory<br> {<br> get ;<br> set ;<br> }<br><br> private List <ExportLifetimeContext<IDocument>> ExportLifeTimeContextList<br> {<br> get ;<br> set ;<br> }<br><br> protected List <IDocument> Documents<br> {<br> get ;<br> set ;<br> }<br><br> public void CreateDocument() <br> {<br> ExportLifetimeContext<IDocument> LifeTimeContext = DocumentFactory.CreateExport();<br> ExportListLifeTimeContext.Add(LifeTimeContext);<br> Documents.Add(LifeTimeContext.Value);<br> }<br><br> public void Dispose()<br> {<br> Dispose( true );<br> GC.SuppressFinalize( this );<br> }<br><br> public void Dispose( bool disposing)<br> {<br> if (isDisposed)<br> return ;<br><br> if (disposing)<br> {<br> foreach (IDisposable d in ExportLifeTimeContextList)<br> d.Dispose();<br> }<br> <br> isDisposed = true ;<br> }<br>} <br><br> * This source code was highlighted with Source Code Highlighter .

An example of the implementation of the mechanism of recomposition

As an example, which reflects the simplicity and power of using MEF in Silverlight applications, I chose the implementation of support for visual themes (Themes) for application controls.

The application must meet the following requirements:
  1. Maintain threads
  2. Have the ability to download third-party XAP files with themes
  3. Downloading an XAP file should update the list of available topics (recomposition)
  4. The choice of topic should lead to a change in the appearance of controls.
Supporting those
To implement support for the themes, I turned to a published project that provides several ready-made sets of themes. From these projects I use xaml files with themes for basic controls.

Each topic is located in a separate assembly of a separate XAP file. Themes themselves are stored as resources and retrieved upon request.

As a mechanism for accessing topics, a class that implements the IThemeLoader interface is IThemeLoader . This class is able to extract the required resources and contains the name of the topic.

Example:
[InheritedExport]<br> public interface IThemeLoader<br>{<br> string Name<br> {<br> get ;<br> }<br><br> IEnumerable <ResourceDictionary> Resources<br> {<br> get ;<br> }<br><br>}<br><br><br> public class ThemeLoader : ThemeLoaderBase<br>{<br><br> #region IThemeLoader Members<br><br> public override string Name<br> {<br> get <br> {<br> return "Accent" ;<br> }<br> }<br><br> public override IEnumerable <ResourceDictionary> Resources<br> {<br> get <br> {<br> yield return LoadResourceDictionary( "/SLandMEFdevcamp.AccentTheme;component/Style.xaml" );<br> }<br> }<br><br> #endregion <br> <br> /* <br> protected virtual ResourceDictionary LoadResourceDictionary(string uri) <br> { <br> return new ResourceDictionary <br> { <br> Source = new Uri(uri, UriKind.Relative) <br> }; <br> } <br> */ <br>}<br> <br> * This source code was highlighted with Source Code Highlighter .
The attribute InheritedExport indicates that all implementations of the interface marked with this attribute should be exported.

Downloading third-party XAP files
To support third-party XAP files, I place the input field of the XAP file address and the start button on the main form of the application. Downloading is done using DeploymentCabinet , which initiates a recomposition.

As soon as the recomposition has occurred, the list of implementations of IThemeLoader will be IThemeLoader and a new list of available themes will be displayed on the UI.

Example:
private AggregateCatalog _aggregateCatalog = null ;<br> private IEnumerable <IThemeLoader> themesLoaders;<br><br> private void ComposeContainer()<br>{<br> _aggregateCatalog = new AggregateCatalog( new DeploymentCatalog());<br> CompositionHost.Initialize(_aggregateCatalog);<br> CompositionInitializer.SatisfyImports( this );<br>}<br><br>[ImportMany(AllowRecomposition = true )]<br> public IEnumerable <IThemeLoader> ThemesLoaders<br>{<br> get <br> {<br> return themesLoaders;<br> }<br> set <br> {<br> themesLoaders = value ;<br> RaisePropertyChanged( "ThemesLoaders" );<br> }<br>}<br><br> private IThemeLoader theme;<br> public IThemeLoader Theme<br>{<br> get <br> {<br> return theme;<br> }<br> set <br> {<br> theme = value ;<br> LoadTheme( value );<br> RaisePropertyChanged( "Theme" );<br> }<br>}<br><br> private void LoadTheme(IThemeLoader themeLoader)<br>{<br> if (themeLoader.Resources == null || !themeLoader.Resources.Any())<br> return ;<br><br> App.Current.Resources.MergedDictionaries.Clear();<br><br> foreach ( var resourceDict in themeLoader.Resources)<br> App.Current.Resources.MergedDictionaries.Add(resourceDict);<br>}<br><br> private void Button_Click( object sender, System.Windows.RoutedEventArgs e)<br>{<br> DeploymentCatalog catalog = new DeploymentCatalog(XapUrlTextBox.Text);<br> catalog.DownloadAsync();<br><br> _aggregateCatalog.Catalogs.Add(catalog);<br>} <br><br> * This source code was highlighted with Source Code Highlighter .
In this case, XAP files with themes are located in the same folder as the main XAP file, and therefore you can specify only the name of the XAP file without a full url. For example, SLandMEFdevcamp.AccentTheme.xap, SLandMEFdevcamp.Win7Theme.xap.

The logic of this code is as follows:The results of the program can be seen here.

Conclusion

MEF has a large number of advantages, among which are recompositions, the absence of a strictly defined place in which parts of the composition should be registered, etc.

MEF for Silverlight has additional features that are not available even in the desktop version, allowing you to build even more flexible programs.

Possibilities of recomposing allow you to change the set of components right during the program operation, which is very important for those cases when dynamic connection / disconnection of functionality, working modules, etc. is required.

Project source code

Source code can be downloaded here .

Upd: The ExportFactory<T> class has ExportFactory<T> added to the desktop version of the library in MEF 2 Preview 2 .

Source: https://habr.com/ru/post/106486/


All Articles