Using MEF (Managed Extensibility Framework) to Develop Asp.Net WebForms Applications

    MEF is a good framework for writing extensible applications. It allows you to easily separate the implementation from the abstraction, add / modify / delete the implementation while the application is running (recomposition), work with multiple implementations of abstractions, split monolithic applications into independent parts, etc.

    Most of the examples of MEF are console or WPF applications. Why? Because in this case it is easiest to control the lifetime of composable parts. MEF itself takes care of this, and the developer concentrates on the tasks of the example.

    The situation in web applications is fundamentally different. Developers are not responsible for creating pages, controls, etc., because Asp.net Runtime takes care of everything.

    Thus, in order to implement MEF support for web applications, you should combine the algorithms of both tools.

    Known Solutions


    There is a solution how MEF can be used for WebForms applications. But this example has several important limitations.
    • Developers should re-inherit all controls, pages and user controls, which is not always possible
    • This example does not offer a solution to support HttpModuleandHttpHandler

    High level architecture


    The purpose of the solution is to provide support for the MEF container without the need to use a re-inheritance mechanism and provide support HttpModuleand HttpHandler.

    Despite the fact that I rejected the solution presented above, I am going to use it as a basis. This means that I will use two containers - local (per request) and global.

    Each time a request arrives, asp.net runtime creates the requested page or handler and creates all the dependent controls. I propose to initialize the imported elements immediately after all parts of the page are created, and save all initialized imports to a local container.

    On the other handHttpModuleare created once immediately after the start of the entire application. Thus, imports for them should be carried out as early as possible and all HttpModuleshould be stored in a global container.

    Implementation


    Pages and controls

    In order to perform the import operation for the page and all its dependencies, you should use the additional one HttpModule. This module should be added to the handlers Pre_Initand Initfor the current page you request. In the first handler, it becomes possible to perform composition for the page, master page and user controls. The event Initwill allow you to execute the composition for server controls, as on Pre_Initthey do not yet exist.

    Example:
    public class ComposeContainerHttpModule : IHttpModule
    {
      public void Init(HttpApplication context)
      {
        context.PreRequestHandlerExecute += ContextPreRequestHandlerExecute;
      }

      private void ContextPreRequestHandlerExecute(object sender, EventArgs e)
      {
        Page page = HttpContext.Current.CurrentHandler as Page;
        if (page != null)
        {
          page.PreInit += Page_PreInit;
          page.Init += Page_Init;
        }
      }

      private void Page_Init(object sender, EventArgs e)
      {
        Page handler = sender as Page;

        if (handler != null)
        {
          CompositionBatch batch = new CompositionBatch();
          batch = ComposeWebPartsUtils.BuildUpControls(batch, handler.Controls);
          ContextualCompositionHost.Container.Compose(batch);
        }
      }

      private void Page_PreInit(object sender, EventArgs e)
      {
        Page handler = sender as Page;

        if (handler != null)
        {
          CompositionBatch batch = new CompositionBatch();
          batch = ComposeWebPartsUtils.BuildUp(batch, handler);
          batch = ComposeWebPartsUtils.BuildUpUserControls(batch, handler.Controls);
          batch = ComposeWebPartsUtils.BuildUpMaster(batch, handler.Master);
          ContextualCompositionHost.Container.Compose(batch);
        }
      }

      public void Dispose()
      {
      }
    }

    * This source code was highlighted with Source Code Highlighter.

    Initially, import is performed for the page, then for user controls and master pages, and at the very end, a recursive function imports for server controls and their controls.

    The method BuildUp(CompositionBatch batch, Object o)checks if the object has Object oany imported elements and adds it to the list of objects for composition. Once all controls have been processed, the object CompositionBatchcan be used to initialize the container. After that, all imports will be initialized and available throughout the life of the request.

    Example:
    public static class ComposeWebPartsUtils
    {
      public static 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 cannot be exportable", o.GetType().FullName));

          batch.AddPart(part);
        }

        return batch;
      }

      public static CompositionBatch BuildUpUserControls(CompositionBatch batch, ControlCollection controls)
      {
        foreach (Control c in controls)
        {
          if (c is UserControl)
            batch = ComposeWebPartsUtils.BuildUp(batch, c);
          batch = BuildUpUserControls(batch, c.Controls);
        }

        return batch;
      }

      public static CompositionBatch BuildUpControls(CompositionBatch batch, ControlCollection controls)
      {
        foreach (Control c in controls)
        {
          batch = ComposeWebPartsUtils.BuildUp(batch, c);
          batch = BuildUpControls(batch, c.Controls);
        }

        return batch;
      }

      public static CompositionBatch BuildUpMaster(CompositionBatch batch, MasterPage master)
      {
        if (master != null)
          batch = BuildUpMaster(ComposeWebPartsUtils.BuildUp(batch, master), master.Master);

        return batch;
      }
    }

    * This source code was highlighted with Source Code Highlighter.

    Note:
    I can’t use the exact same technique that is used in the example from the codeplex website ( PageHandlerFactorymethod inheritance and override GetHandler()), because by this time, no controls have yet been created for the page.

    Httphandler

    Handlers do not have events where all imports could be satisfied. It would be ideal to use the appropriate HandlerFactoryand override the method GetHandler()as it was for the pages in the codeplex example. And such a class exists ( SimpleWebHandlerFactory), but it is internal. I don’t know why Microsoft programmers did this, but it looks weird because The factory for the pages is public.

    I do not see any other option than implementing my own factory SimpleWebHandlerFactoryinstead of being present in the .net framework. The main goal of anyone HandlerFactoryis to determine the type to be instantiated for the current request.HandlerFactorycan get the type only through the parsing of the resource that was requested. So I need a parser that can parse the code HttpHandler. Fortunately, such a parser exists ( SimpleWebHandlerParser), is public, and I only need to make a wrapper for it ( WebHandlerParser).

    Below is a sequence diagram that describes the operation of the algorithm to create a composition for the HttpHandler

    below code of classes that implement the functionality described above.
    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 = ComposeWebPartsUtils.BuildUp(batch, handler);
          ContextualCompositionHost.Container.Compose(batch);
        }

        return handler;
      }
    }

    * This source code was highlighted with Source Code Highlighter.

    HttpModule

    As I mentioned earlier, everyone is HttpModulescreated at the start of the application. Thus, I have to make the composition right after the application starts.
    Example:
    public class ScopedContainerHttpModule : IHttpModule
    {
      private CompositionContainer _container;

      public void Init(HttpApplication app)
      {
        ComposeModules(app);
      }

      private void ComposeModules(HttpApplication app)
      {
        CompositionBatch batch = ComposeWebPartsUtils.BuildUpModules(app);
        _container.Compose(batch);
      }
    }

    public static class ComposeWebPartsUtils
    {
      public static CompositionBatch BuildUpModules(HttpApplication app)
      {
        CompositionBatch batch = new CompositionBatch();

        for (int i = 0; i < app.Modules.Count - 1; i++)
          batch = BuildUp(batch, app.Modules.Get(i));

        return batch;
      }
    }

    * This source code was highlighted with Source Code Highlighter.

    I get the HttpApplicationobject, extract all the modules from it and based on this information I fill the global container.

    As a result, I should add a few lines to the web.config file of any WebForms project so that my solution works.

      ......
      
      
      ......


      
      ......
      


    * This source code was highlighted with Source Code Highlighter.

    I delete the * .ashx file handler, which is present by default, and add my ( ComposableWebHandlerFactory).

    I am adding a module ContainerCreatorto create and initialize the infrastructure for local and global containers.

    ComposeContainerModulewill be used to initialize the local container.

    That's all!

    I did not need to use the inheritance of controls, write additional code in the main program. This solution can be added to any web project based on web forms, and only web.config needs to be updated so that all the features presented become available.

    Example


    I am using a demo, which I took from the example WebFomsAndMef with minor modifications.

    Each application element imports a class SampleCompositionPartwith an attribute [PartCreationPolicy(CreationPolicy.NonShared)], which ensures that a new instance will be created SampleCompositionPartduring the import act. This class contains a single field Idthat Guid returns.

    The application displays the Guid value for the currently displayed item - page, control, user control, HttpHandlerand page master HttpModule.

    Example:
    ///PageSample.aspx

      <% =SamplePart.Id %>


    ///PageSample.cs
    public partial class PageSample : System.Web.UI.Page
    {
      [Import]
      public SampleCompositionPart SamplePart
      {
        get;
        set;
      }
    }

    * This source code was highlighted with Source Code Highlighter.


    Source code can be downloaded here .

    I hope this article helps get started using MEF in asp.net WebForms applications.

    Also popular now: