Click here to Skip to main content
15,878,809 members
Articles / Programming Languages / C#

Extensible Application Design Using the Managed Extensionsibility Framework

Rate me:
Please Sign up or sign in to vote.
4.80/5 (5 votes)
4 Oct 2010CPOL14 min read 23.5K   464   23   5
In today's world, application requirements are changing and software is constantly evolving. The Managed Extensibility Framework is a one stop solution for most business problems we solve in our day to day life. Applications targeting MEF can easily adopt to changes in business elegantly.

Introduction

This article gives readers a fair idea of how an application can be designed using the Managed Extensions Framework and make the application design open to adopt frequent changes in business requirements.

Background

In one of our projects, the application was designed in such a way that the application contains sequences of steps, and the steps contains modules, and each page displays a step. Modules are reusable across the steps. We can do the design for this in many ways, one of which is data driven; using this approach, we can very much control the steps and modules order in the database, and use the data from the database to orderly render the steps and the modules in each step. That said, often we might have a requirement to have a module be reusable across applications. In that case, different applications might have different data models. If we tightly couple the module meta data to be data driven to one particular data model, then while reusing the modules across different applications, we need to take two things into consideration: one is to find a way to integrate our data model with the new application data model so that the module designed to be data driven would automatically start appearing in the new application, and second, for example, if we find a particular module is malfunctioning, then in order to remove the module from the page, the build manager needs to know the associated metadata of the module to be removed (database table details), otherwise it may give a module not found exception. Basically, this model expects a user to know the database tables that are responsible for rendering the steps and modules and carefully remove them in order to remove the module from the application.

Let us consider another way of doing it. Designing the application using the Managed Extensibility Framework. If an pplication is targeting the Managed Extensions Framework, the above said problems can be solved with low maintenance costs.

What is MEF all about? In simple terms, Exports/Imports. For more details about MEF, go to this link: http://mef.codeplex.com/wikipage?title=Guide&referringTitle=Overview.

Managed Extensibility Framework provides the flexibility of exporting functionality using Exports, and imports the exported functionality using Imports. We can use the Export/Import concept of MEF to design our steps and modules based application pattern. We can assume each module as an Export, and each step imports one or more modules. So how does a step knows what are its modules? We provide a contract and the associated metadata (module name, step name in which the module can go in, application name to which this module belongs to) to each module developer. Module developers just need to implement the contract and pass the necessary metadata to the module metadata, like which step/application it belongs to. Each module developer will produce a DLL as the output for each module. Application integrators just need to copy the module DLL to a given folder location where MEF is watching for the Exports. That's it, it automatically integrates the module with the right step and with the right application.

This solves the above two problems. For example, how to integrate the module with a different application. The module developer just needs to change the application name in the metadata to which the module belongs to (it can be configurable in App.config) and ship the DLL to the new application, which will be integrated into that application automatically (if that application design also targets MEF). I am really excited with this feature. If you feel a particular module is malfunctioning, then as a build engineer, you can just remove the module DLL from the MEF folder location. You don't need to know the associated metadata and the location where the metadata is stored.

Simple Introduction to MEF

The fundamental unit of MEF is a Composable part. Composable parts are of two types: Import and Export. In the Managed Extensibility Framework programming model, a type can be made as an Export service by attributing the type as [System.ComponentModel.Composition.Export], and a type can be made an import of a certain Export type by attributing it with [System.ComponentModel.Composition.Import].

Exports and imports don't have direct dependency on each other; they depend on a contract. Every Export has a contract, and every import declares the contract it needs. For example, if my Export has a contract IA (Interface) which is declared as follows:

C#
[Export(typeof(IA))]
Public class Export1 : IA
{
    // Implementation of the Export1 goes here. 
} 
[Export(typeof(IA))]
Public class Export2:IA
{
   //Implementation of the Export2 class goes here, 
}

Once the above parts are exported, the exports can be imported using the following code:

C#
public class Import1
{
  [ImportMany]
  Public IEnumerable<IA> _exports
}

The ImportMany attribute in the above code is used to import all the composable parts which are exported with the contract IA. There is also one attribute [Import] which does the import for a matching Export.

Catalogs

MEF discovers the parts with the help of Catalogs. Catalogs allow applications to easily consume all the parts that have been registered with the Export attribute. Below are the types of catalogs.

  • AssemblyCatalog: [System.ComponentModel.Composition. Hosting. AssemblyCatalog] to discover all the parts in a given assembly.
  • DirectoryCatalog: Which is used in our demo sample, [System.ComponentModel.Composition.Hosting.DirectoryCatalog]. It discovers all the Exports that are found in the assemblies under the given folder.
  • AggregateCatalog: Basically, this catalog is required to combine more than one catalog when a single catalog is not self sufficient.
  • TypeCatalog: To discover all the exports in a specific set of types, [System.ComponentModel.Composition.Hosting.TypeCatalog].
  • CompositionContainer: Once the catalog is prepared by choosing one from above, the catalog will be passed to the composition container, and uses the GetExports() etc. methods of the CompositionContainer to get the Exports as per the catalog settings.

For example, consider the code below:

C#
string relativePath = AppDomain.CurrentDomain.BaseDirectory + @"\Modules";
AggregateCatalog agrCatalog = new AggregateCatalog(new DirectoryCatalog(relativePath));
CompositionContainer objContainer = new CompositionContainer(agrCatalog);
TypeCatalog moduleCatalog = new TypeCatalog(typeof(IModule));
agrCatalog.Catalogs.Add(moduleCatalog);
_Modules = objContainer.GetExports<IModule, IModuleMetadata>();

Here in the above code, relativePath is set to the baseDirectory\Modules folder. A DirectoryCatalog is created to this relative path, and a TypeCatalog also created to create all the Exports under the IModule type, and added to the Aggregate Catalog. What this Aggregate Catalog means is in the basedirectory\Modules folder location, search in all the assemblies for the type IModule which are self-registered for Export attributes. Pass this AggregateCatalog to CompositionContainer and call the GetExports method to get the Exports of type IModule.

Using the Code

For simplicity, in this article, I have done a sample for a Company dash board using the Managed Extensibility Framework, which basically displays a page (a step), which contains company events, policies, and news in three separate grids (modules). So we have identified three different modules in the company dashboard step. Removing any module DLL from the Modules folder (MEF watch folder) will remove the module from the company dashboard step.

Below is the code structure of the sample MEF:

CodeStructure.jpg

Here, the project SampleMEF.Contract defines the contract for the modules we give to the module developers. This project contains Module contract IModule which basically defines onModuleLoad(); module implementers can override this method to implement the functionality on load of the module. MEF watches for all modules that implement the IModule interface type.

C#
//IModule.cs

public interface IModule : IComponent
{
    void OnModuleLoad();
}

IModule inherits from IComponent (implementation specific, normally it is not mandatory) because all the modules in this sample are UserControls.

IModuleMetadata provides the metadata information of the module (like to which step and application the module belongs to). Module metadata is very useful at the time of integrating the module with the application step to know where the module should be placed.

C#
//IModuleMetadata.cs

public interface IModuleMetadata {
    string StepName { get; }
    string ApplicationName { get; }
    string ModuleName{get;}
    
}

IModuleMetadata is the metadata about the module; for our demo, each module has an associated step and an application into which it needs to get displayed.

  • StepName: Step name of the module into which the module belongs to; e.g.: Company Dashboard.
  • ModuleName: Name of the module like Company News, Company Events etc.
  • ApplicationName: Name of the application to which the current module belongs to; e.g., Company Portal.

The BaseModule class implements the IModule interface. The purpose of having the BaseModule class is we can put common code for all the modules in the BaseModule class. We know that a module is a DLL for this demo; a DLL can have more than one ASCX control. The BaseModule class has a method LoadModules(..). Given a list of ASCX control names, it hands over each control loading to a custom virtual path provider and loads the control. This also implements the IModule interface and provides an empty implementation to the OnModuleLoad() method. Modules required to implement IModule can now just inherit from the BaseModule class. We can also think about the purpose of the BaseModule class as, we as application designers will ship the prepackaged software a module developer can use along with the contract DLL; in this case, it is the LoadModules() method.

C#
public class BaseModule:System.Web.UI.UserControl,IModule
{
    public void LoadModules(List<string> modules)
    {
        foreach (string module in modules)
        {
            string virtualPath = "~/Modules/" + 
              System.Reflection.Assembly.GetCallingAssembly().GetName().Name + 
              "," + System.Reflection.Assembly.GetCallingAssembly().GetName().Name + 
              "."+module;
            this.Controls.Add(this.LoadControl(virtualPath));
        }
    }

    #region IModule Members

    public virtual void OnModuleLoad()
    {
        
    }

    #endregion
}

ModuleMetadaAttribute extends from the MEF Export attribute, and implements IModuleMetadata. Module developers need to decorate their modules with this attribute in order to make the module an Export.

C#
//ModuleMetadataAttribute.cs 
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ModuleMetadataAttribute : ExportAttribute, IModuleMetadata {
    public ModuleMetadataAttribute() : base(typeof(IModule)) { }
    public string StepName { get; set; }
    public string ApplicationName { get; set; }
    public string ModuleName { get; set; }

}

In the above code, we have created a custom Export attribute called [ModuleMetadata] to apply on the modules that are going to be developed for the current step. It basically extends from the MEF class ExportAttribute and implements IModuleMetadata. The purpose of this attribute is when it is applied on a class by passing the StepName, ApplicationName, and ModuleName values, that class becomes MEF exportable and the metadata provides the details of the export.

The solution contains the SampleMEF.module1, SampleMEF.module2, and SampleMEF.module3 projects for Events, Policies, and News Modules. Each module is decorated with the ModuleMetadtaAttribute to tell the step that it is a possible import for your step. There is a control in each module called ParentModule (in each module project, you will find a control with the name ParentModule.ascx), which stands up primary among all the other controls in the module, and takes the responsibility of loading other controls using the loadcontrol() method.

Below is the code structure for SampleMEF.module1:

Module1CS.jpg

SampleMEF.module1.dll is basically for the Events module. SampleMEF.module1 contains the CompanyEventsModule.ascx control, which actually gets the Company Events data and displays it on the grid. Below is the code for CompanyEvents:

C#
CompanyEventsModule.ascx <script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
    this.OnModuleLoad();
}
#region IModule Members
public void OnModuleLoad()
{

    this.CompanyEventsDataGrid.DataSource = 
         CompanyDataClient.GetCompanyEventsData(1);
    this.CompanyEventsDataGrid.DataBind();

}

#endregion
</script>

The above ASCX content will be loaded in the below ParentModule code; the module developer needs to make a List<string> of control names (in this case, CompanyEventsModule.ascx etc.). BaseModule will take care of loading the controls.

C#
[ModuleMetadata(StepName = "CompanyPortalPage", 
  ApplicationName = "CompanyApp",ModuleName="Company News" )]
public partial class ParentModule : BaseModule 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        this.OnModuleLoad();
    }
    #region IModule Members
    public override void OnModuleLoad()
    {
        List<string> modules =new List<string>();
        modules.Add("CompanyEventsModule.ascx");
        base.LoadModules(modules);
    }
    #endregion
}

Why do we need to do this step at all? Why can not MEF create all the controls in the ASCX page when we keep all the subcontrols as part of the ParentModule.ascx control? Because MEF creates only the exported object, in this case, the ParentModule user control. MEF will not compile the ASCX content to create child objects, which is the expected behavior. Because of this reason, I have come up with a solution where we keep all the ASCX sub control content (CompanyEventsModule.ascx in SampleMEF.module1, CompanyPoliciesModule.ascx in SampleMEF.module2, and CompanyNewsModule.ascx in SampleMEF.module3) as embedded to the corresponding module DLLs, and at runtime, read the content of the subcontrols using the GetResourceManifestStream() method.

Similarly for the other two modules as well. Below is the code structure for the other two modules.

SampleMEF.module2 which is PoliciesModule

Module2CS.jpg

SampleMEF.module3 which is NewsModule

Module3CS.jpg

Most of the CMS frameworks will use the VirtualPath mechanism to get the ASCX, ASPX content from either a database or a Content Management System. In the same way, in this sample, we have used the VirtualPath provider concept to supply the content of ASCX, ASPX to the ASP.NET build system from the embedded resource (here, the module DLL). The providers project does the CMS framework job, which is self-explanatory.

The MEFSampleWeb project contains the company dashboard page. Default.aspx initializes MEF to the Modules folder in the website so that any module copied to the Modules folder will be automatically picked up by the step.

C#
public static IEnumerable<Lazy<IModule, IModuleMetadata>> _Modules;
public  _Default()
{
    InitializeComponentContainer();
}
protected void Page_Load(object sender, EventArgs e)
{
    foreach (var module in _Modules)
    {
        if (module.Metadata.ApplicationName.Equals("CompanyApp") && 
            module.Metadata.StepName.Equals("CompanyPortalPage"))
        {
            this.div1.Controls.Add((UserControl)module.Value);
        }
    }
}
private static void InitializeComponentContainer()
{
    try
    {
        string relativePath = AppDomain.CurrentDomain.BaseDirectory + @"\Modules";
        AggregateCatalog agrCatalog = 
          new AggregateCatalog(new DirectoryCatalog(relativePath));
        CompositionContainer objContainer = new CompositionContainer(agrCatalog);
        TypeCatalog moduleCatalog = new TypeCatalog(typeof(IModule));
        agrCatalog.Catalogs.Add(moduleCatalog);
        _Modules = objContainer.GetExports<IModule, IModuleMetadata>();
    }
    catch (Exception objException)
    {
    }
    
}

Here, we have used DirectoryCatalog to listen to the Modules folder of the website. We are also using a TypeCatalog of type IModule. Basically, among all Modules which are possible exports for the MEF, it will pick only Exports which are implementing the IModule interface (in our case, the BaseModule class, as we are already extending it from IModule). The _Modules property which is lazy loaded defers the loading of the modules until the property is accessed explicitly. A div tag is declared in the ASPX page to hold all the modules targeted in this step (company dash board). The above foreach basically collects all the modules (Exports) and adds them to the main div1 child controls.

SampleMEFProxies is the client wrapper for CompanyDataService. The Provider project basically implements a new virtual path provider to load the ASCX content from the DLL. Below is sample code for AssemblyVirtualPathProvider. Again, it is implementation specific to the module.

C#
public class AssemblyVirtualPathProvider :VirtualPathProvider
{
    public AssemblyVirtualPathProvider()
    {
    }
    private bool IsPathVirtual(string virtualPath)
    {
        String checkPath =
           VirtualPathUtility.ToAppRelative(virtualPath);
        return checkPath.StartsWith("~/Modules".ToLower().ToString(), 
               StringComparison.InvariantCultureIgnoreCase);
    }
    public override bool FileExists(string virtualPath)
    {
        if (IsPathVirtual(virtualPath))
        {
           
            return true;
        }
        else
            return Previous.FileExists(virtualPath);
    }

    //This method is used by the compilation
    //system to obtain a VirtualFile instance to 
    //work with a given virtual file path.
    public override VirtualFile GetFile(string virtualPath)
    {
        if (IsPathVirtual(virtualPath))
            return new AssemblyVirtualFile(virtualPath);
        else
            return Previous.GetFile(virtualPath);
    }
}

Registering the above virtual path provider with ASP.NET can be done in the global.asax file, and it is shown below.

C#
protected void Application_Start(object sender, EventArgs e)
{
    System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(
                       new AssemblyVirtualPathProvider()); 
}

Here is how the whole thing works:

  1. The requests for the company portal page comes to the Applicatoin_Start event and registers a new virtual path provider with ASP.NET, and it will call Page_Init() of the default.aspx page of the MEFSampleWeb project.
  2. Inside Page_Init(), MEF will be configured to watch the Modules folder for all Exports of type IModule.
  3. In the page_Load method, foreach iterates through the IEnumerable collection of _Modules.
  4. Consider that our MEFSampleWeb\Modules folder contains the above three modules: SampleMEF.module1.dll, SampleMEF.module2.dll, and SampleMEF.module3.dll.
  5. MEF finds three Export modules from each module DLL, an Export type ParentModule which implements the IModule interface.
  6. Each ParentModule is again a user control, and MEF initiates the ParentModule controls and calls the Page_Load() method, which in turn calls the OnModuleLoad() method.
  7. OnModuleLoad() prepares the list<string> of ASCX control names and calls the BaseModule.LoadModules() method.
  8. In the BaseModule.LoadModules() method, it prepares a VirtualPath and calls the LoadControl() method.
  9. If the virtual path starts with ~Modules\, then the AssemblyVirtualPathProvider of the Providers assembly will return the ASCX content from the current module DLL; else it will fall back to normal behavior. It loads all the content using the LoadControl() method and adds it to the Controls collection. That is how the exported type loading is finished.

The picture below depicts how MEF provides the extension points to the demo sample:

MEFDiagram.jpg

The picture below shows how we have used MEF:

MefNotes.jpg

The sample output when we run the application by keeping all the three module DLLs in the MEFSampleWeb\Modules folder is shown below (basically, grids will come up with data):

Output3Modules.jpg

If we remove a DLL from the Modules folder, e.g., the Policies module, then the output will be as shown below (basically, grids come up with data):

Output2Modules.jpg

If we design a fourth module and copy it to the Modules folder, then MEF will integrate the new module into the step.

Setup steps

This article uses the Entity Framework for company associated data. Download Mefscripts.txt from the given link and execute it on the database and update the connection string (MVCDBEntities, ApplicationServices) in the CompanyDataService web.config file.

After compiling the entire solution, copy the module1, module2, and module3 DLLs from the SampleMEF\Libraries folder to the MEFSampleWeb\Modules folder. We can now start playing around with the module DLLs.

Points of Interest

  • Module development is clearly separated. The module developer just needs to know the MEF contract that he/she needs to implement.
  • VirtualPathProvider is used to get the content of ASCX from the DLL. Register the new VirtualPathProvider with ASP.NET.
  • This mode of design/development is well suited for projects following agile methodology which can possibly have many sprints and changes to business requirements.
  • Suitable for projects where step/module based design is required and modules need to be reusable across steps and applications.

Please vote for this article if you feel it was useful.

Disclaimer

Any application design (for that matter, data driven or MEF) will have its own merits and demerits. We as designers need to take many parameters into consideration before proposing a technical solution to the business problem. This article is not in favor of any particular framework. It is entirely the designer's/developer's responsibility to choose/evaluate the technical architecture based on their business requirements.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
India India
I have around 9 years of experience in Microsoft technologies, .Net 2.0,3.5, Asp.net MVC developed small to medium scale products using Silverlight 2.0, Asp.net Ajax technologie and Javascript frameworks.

Comments and Discussions

 
QuestionIssue in calling user control code behind file Pin
Manish155-Aug-15 1:42
Manish155-Aug-15 1:42 
QuestionCan't download Source code Pin
znyls7-Jan-13 18:32
znyls7-Jan-13 18:32 
GeneralMy vote of 5 Pin
erikdraven18-Jan-12 6:33
erikdraven18-Jan-12 6:33 
GeneralGood Pin
Ibrahim Yusuf3-Oct-10 17:34
Ibrahim Yusuf3-Oct-10 17:34 
GeneralRe: Good Pin
Ramu Sangabathula Original4-Oct-10 7:37
Ramu Sangabathula Original4-Oct-10 7:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.