Still looking for a sponsor Max Paulousky is looking for a Silverlight/.Net job in the Commonwealth

Share to Facebook Tweet this! Share to MySpace Share to Google Share to Live   Share via AddThis

Using MEF (Managed Extensibility Framework) in Asp.Net WebForms applications

Introduction

Managed Extensibility Framework is a great framework to write extensible/pluggable applications. It allows easily separating implementation from abstraction, adding/changing/deleting implementation at runtime (recomposition), working with multiple implementations of an abstraction, breaking monolithic applications into independent parts. It is an integral part of .Net Framework 4 (full and Silverlight versions).

Abstracts

Almost all MEF samples are console applications. Why? Because it’s easy to manage lifetime of composable parts in console applications (MEF takes care of it) and a developer concentrates on the goal of an example.

The situation in web applications is totally different. Developers are not responsible for creating Pages, Controls, Master pages, handlers. Asp.net runtime does it for them.

So, the main problem is how we could combine workflows of asp.net runtime and MEF.

Known Solutions

There is a sample on the codeplex web site how MEF in WebForms applications could be used. But that sample has a few restrictions.

  1. Developer should re-inherit all controls, pages and user controls from provided classes;
  2. The solution does not provide any implementation for supporting HttpModules and HttpHandlers.

High-level architecture

The goal is to avoid inheritance mechanism and implement supporting of IHttpHandler and IHttpModule interfaces.

I am going to use provided sample of WebForms with MEF as a basis. That means, I am going to use two containers – global one and a container per request (scoped container).

Every time a request comes, Asp.Net runtime creates a requested page/handler and creates all dependent controls. I propose to compose imports right after a control and all embedded controls are created (see my schema below) and store it in the scoped container:

MEF workflow in WebForms applications

Otherwise, HttpModules are created once right after an application is started. So, import for them should be satisfied as soon as possible and all HttpModules should be stored in the global container.

Implementation

Pages/Controls

To be able to satisfy imports for a page and its dependants I am going to use an HttpModule. I am going to add  Pre_Init and Init handlers for the current requested page. In that handlers I will be able to compose imports for the page, master pages, user controls because they will be created by Pre_Init. Imports for server controls will be satisfied on the Init event because they are not created by the Pre_Init event.

Initially, I satisfy imports for the page, then for user controls and Master pages, and finally I do the same for controls and controls of the controls.

private void Page_PreInit(object sender, EventArgs e)
{
    Page handler = sender as Page;
 
    if (handler != null)
    {
        CompositionBatch batch = new CompositionBatch();
        batch = BuildUp(batch, handler);
        batch = BuildUpUserControls(batch, handler.Controls);
        batch = BuildUpMaster(batch, handler.Master);
        ContextualCompositionHost.Container.Compose(batch);
    }
}
 
private void Page_Init(object sender, EventArgs e)
{
  Page handler = sender as Page;
 
  if (handler != null)
  {
    CompositionBatch batch = new CompositionBatch();
    batch = BuildUpControls(batch, handler.Controls);
    ContextualCompositionHost.Container.Compose(batch);
  }
}
 
private CompositionBatch BuildUpUserControls(CompositionBatch batch, ControlCollection controls)
{
  foreach (Control c in controls)
  {
    if (c is UserControl)
      batch = BuildUp(batch, c);
    batch = BuildUpUserControls(batch, c.Controls);
  }
 
  return batch;
}
 
private CompositionBatch BuildUpControls(CompositionBatch batch, ControlCollection controls)
{
    foreach (Control c in controls)
    {
        batch = BuildUp(batch, c);
        batch = BuildUpControls(batch, c.Controls);
    }
 
    return batch;
}
 
private CompositionBatch BuildUpMaster(CompositionBatch batch, MasterPage master)
{
    if (master != null)
        batch = BuildUpMaster(BuildUp(batch, master), master.Master);
 
    return batch;
}
 
private CompositionBatch BuildUp(CompositionBatch batch, Object o)
{
    ComposablePart part = AttributedModelServices.CreatePart(o);
 
    // any imports?
    if (part.ImportDefinitions.Any())
    {
        if (part.ExportDefinitions.Any())
            // exports are not allowed
            throw new Exception(string.Format("'{0}': Handlers, MasterPages and Controls cannot be exportable", o.GetType().FullName));
 
        // then compose handler
        // Todo: should happen in a per-request container
        batch.AddPart(part);
    }
    return batch;
}

Method BuildUp(CompositionBatch batch, Object o) checks whether Object o has any ImportAttribute definition and adds appropriate Composable part to the composition batch. After all controls are processed, CompositionBatch object can be used to compose container. After that, all imports will be resolved and will be available during the request.

Note. I can't use the same technique as is used in the codeplex sample (inheritance from PageHandlerFactory and overriding GetHandler method) because no controls within the page are created yet.

Handlers

Handlers do not have such events where all imports can be satisfied. It would be the ideal variant to use some HandlerFactory and override GetHandler method (as it was done for Pages in the codeplex sample). There is such a class in .net framework (SimpleWebHandlerFactory) but it is internal. I don't know why MSFTs did it but it looked weird because the same factory for web pages was not internal.

I do not see any other way out except implementing my own SimpleWebHandlerFactory instead of internal one. The main goal of any HandlerFactory is to get type that should be exposed for the current requested path. The handler factory can get the type via parsing the requested resource. So, I have to implement own WebHandlerParser. Thanks god, there is such a HandlerParser (SimpleWebHandlerParser), it is public  and I just create wrapper for it.

As a result, I can implement a SimpleWebHandlerFactory's descendant (ComposableWebHandlerFactory) to be able to satisfy import within an HttpHandler.

Here is a sequence diagram that describes the workflow:

The sequence diagram to create HttpHandler and satisfy its imports

Here is source code that allows creating composable http handlers:

public class SimpleWebHandlerFactory : IHttpHandlerFactory                                                           
{                                                                                                                    
    public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string virtualPath, string path) 
    {                                                                                                                
        Type type = WebHandlerParser.GetCompiledType(context, virtualPath, path);                                    
        if (!(typeof(IHttpHandler).IsAssignableFrom(type)))                                                          
            throw new HttpException("Type does not implement IHttpHandler: " + type.FullName);                       
                                                                                                                     
        return Activator.CreateInstance(type) as IHttpHandler;                                                       
    }                                                                                                                
                                                                                                                     
    public virtual void ReleaseHandler(IHttpHandler handler)                                                         
    {                                                                                                                
    }                                                                                                                
}      
 
 
internal class WebHandlerParser : SimpleWebHandlerParser                                            
{                                                                                                   
    internal WebHandlerParser(HttpContext context, string virtualPath, string physicalPath)         
        : base(context, virtualPath, physicalPath)                                                  
    {                                                                                               
    }                                                                                               
                                                                                                    
    public static Type GetCompiledType(HttpContext context, string virtualPath, string physicalPath)
    {                                                                                               
        WebHandlerParser parser = new WebHandlerParser(context, virtualPath, physicalPath);         
        Type type = parser.GetCompiledTypeFromCache();                                              
        if (type != null)                                                                           
            return type;                                                                            
        else                                                                                        
            throw new HttpException(string.Format("File '{0}' is not a web handler.", virtualPath));
    }                                                                                               
                                                                                                    
    protected override string DefaultDirectiveName                                                  
    {                                                                                               
        get                                                                                         
        {                                                                                           
            return "webhandler";                                                                    
        }                                                                                           
    }                                                                                               
}           
 
public class ComposableWebHandlerFactory : SimpleWebHandlerFactory
{
    public override IHttpHandler GetHandler(HttpContext context, string requestType, string virtualPath, string path)
    {
        IHttpHandler handler = base.GetHandler(context, requestType, virtualPath, path);
 
        if (handler != null)
        {
            CompositionBatch batch = new CompositionBatch();
            batch = BuildUp(batch, handler);
            ContextualCompositionHost.Container.Compose(batch);
        }
 
        return handler;
    }
 
    private CompositionBatch BuildUp(CompositionBatch batch, IHttpHandler handler)
    {
        ComposablePart part = AttributedModelServices.CreatePart(handler);
 
        // any imports?
        if (part.ImportDefinitions.Any())
        {
            if (part.ExportDefinitions.Any())
                // exports are not allowed
                throw new Exception(string.Format("'{0}': Handlers cannot be exportable", handler.GetType().FullName));
 
            batch.AddPart(part);
        }
 
        return batch;
    }
}                                                                                                                                                                                                      

 

Modules

As I mentioned above, all HttpModules are created on the application’s start. I satisfy imports of all modules in the application right the main composition module is created.

public class ScopedContainerHttpModule : IHttpModule
{
  public void Init(HttpApplication app)
  {
    ...
    ComposeModules(app);
    ...
  }
 
  private void ComposeModules(HttpApplication app)
  {
    CompositionBatch batch = new CompositionBatch();
 
    for (int i = 0; i < app.Modules.Count - 1; i++)
      batch = BuildUp(batch, app.Modules.Get(i));
 
    _container.Compose(batch);
  }
 
  private CompositionBatch BuildUp(CompositionBatch batch, Object o)
  {
    ComposablePart part = AttributedModelServices.CreatePart(o);
 
    if (part.ImportDefinitions.Any())
    {
      if (part.ExportDefinitions.Any())
        throw new Exception(string.Format("'{0}': Handlers, MasterPages and Controls cannot be exportable", o.GetType().FullName));
 
      batch.AddPart(part);
    }
 
    return batch;
  }
  
  ...
}

I get HttpApplication object, extract all modules and compose a global container based on import definitions of modules.

I have to add a few lines of code into web.config of any WebForms project to make my solution work.

<httpHandlers>
  ......
  <remove path="*.ashx" verb="*" />
  <add path="*.ashx" verb="*" type="common.composition.WebExtensions.ComposableWebHandlerFactory, common.composition" />
  ......
</httpHandlers>
<httpModules>
  <add name="ContainerCreator" type="common.composition.WebExtensions.ScopedContainerHttpModule, common.composition"/>
  ......
  <add name="ComposeContainerModule" type="common.composition.WebExtensions.ComposeContainerHttpModule, common.composition" />
</httpModules>

I delete default handler for *.ashx files and add a new one (ComposableWebHandlerFactory).

I add ContainerCreator module to create and initialize environment for the scoped and global containers.

ComposeContainerModule will be used to build up scoped container (container per request).

That is all!

I do not need to re-inherit controls, do not need to write any additional code. The solution could be added to any web project and just web.config should be updated.

Sample

I use demo from the codeplex WebFomsAndMef example.

Each part of the application imports CreationPolicy.NonShared SampleCompositionPart class. This class contains one property that returns guid.

The application displays guid value for the currently displaying element – page, control, usercontrol, masterpage, HttpHandler, HttpModule

Here is the code of the Page sample:

///PageSample.aspx
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
  <% =SamplePart.Id %>
</asp:Content>
 
///PageSample.cs
public partial class PageSample : System.Web.UI.Page
{
  [Import]
  public SampleCompositionPart SamplePart
  {
    get;
    set;
  }
}

Web Forms and Mef sample

The source code can be downloaded here.

Hope, it helps!

This work is licensed under a Creative Commons Attribution By license.

Leave a Comment [ RSS ]

  • re: Using MEF (Managed Extensibility Framework) in Asp.Net WebForms applications

    Very helpful. Thanks!

  • re: Using MEF (Managed Extensibility Framework) in Asp.Net WebForms applications

    Nice work this is helpful for people want to learn asp.

  • re: Using MEF (Managed Extensibility Framework) in Asp.Net WebForms applications

    Is it possible to to get great level of isolation like in MAF (System.Addin)? Can I load controls/user controls and unload them (unload assemblies from memory)?

  • What about httpcontext

    Hi!

    I wrote a simple part which makes a call to System.Web.HttpContext.Current and redirects the request to a new URL.

    How do I prevent parts from accessing for example HttpContext?What a headache if parts can't be sandboxed to some degree.

    Awesome article :-) Thank you very much!


  • re: Using MEF (Managed Extensibility Framework) in Asp.Net WebForms applications

    Hans, well, can't provide a quick solution

  • re: Using MEF (Managed Extensibility Framework) in Asp.Net WebForms applications

    Requesting Gravatar... abedon says:

    Hi Max,

    Is it possible to apply MEF to a Web Service (.asmx)?

    I added a custom HttpHandlerFactory for .asmx to your example as follow:
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////
    public class MefWebServiceHandlerFactory : IHttpHandlerFactory
    {
    private static WebServiceHandlerFactory wshf = new WebServiceHandlerFactory();
    public IHttpHandler GetHandler(HttpContext context, string requestType, string virtualPath, string path)
    {
    IHttpHandler handler = wshf.GetHandler(context, requestType, virtualPath, path);

    if (handler != null)
    {
    ComposablePart part = AttributedModelServices.CreatePart(handler);

    // any imports?
    if (part.ImportDefinitions.Any())
    {
    if (part.ExportDefinitions.Any())
    {
    // exports are not allowed
    throw new Exception("Handlers cannot be exportable");
    }

    // then compose handler
    // Todo: should happen in a per-request container
    var batch = new CompositionBatch();
    batch.AddPart(part);

    ContextualCompositionHost.Container.Compose(batch);
    }
    }

    return handler;
    }

    public virtual void ReleaseHandler(IHttpHandler handler)
    {
    }
    }
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////

    Then added the handler to web.config:
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////
    <remove path="*.asmx" verb="*" />
    <add path="*.asmx" verb="*" type="Microsoft.ComponentModel.Composition.WebExtensions.MefWebServiceHandlerFactory" />
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////

    Finally, I added a new asmx.
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////
    [WebService]
    public class WebService1 : WebService
    {
    [Import]
    public ICompositionPart SamplePart
    {
    get;
    set;
    }

    [WebMethod]
    public string HelloWorld()
    {
    return "Hello World";
    }
    }
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////

    When I run the application, it goes through the MefWebServiceHandlerFactory as expected. But unfortunately, WebService1.SamplePart is always null and it seems the composition part can't be discovered. Anything wrong?

  • re: Using MEF (Managed Extensibility Framework) in Asp.Net WebForms applications

    abedon, I saw an example of working MEF with web services. I will try to find it

  • re: Using MEF (Managed Extensibility Framework) in Asp.Net WebForms applications

    Requesting Gravatar... abedon says:

    Hi Max, any luck?

  • re: Using MEF (Managed Extensibility Framework) in Asp.Net WebForms applications

    Requesting Gravatar... johnM says:

    Any link to "an example of working MEF with web services" ?

    Thanks

  • re: Using MEF (Managed Extensibility Framework) in Asp.Net WebForms applications

    Requesting Gravatar... Richard says:

    Is there a reason you're using a custom WebHandlerFactory, instead of composing the handler in the PreRequestHandlerExecute event?

  • re: Using MEF (Managed Extensibility Framework) in Asp.Net WebForms applications

    Requesting Gravatar... Richard says:

    The problem with web services is that the handler is not the service; it will be one of the four internal types which inherit from System.Web.Services.Protocols.WebServiceHandler, depending on whether it supports async operations, and whether it requires session state. The handler calls the internal CreateServerInstance method on the ServerProtocol class to create an instance of the WebService class, which means you don't get a chance to intercept the call.

    A MEF-enabled base class seems to be the only sensible option.

Comments have been closed on this topic.