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.
- Developer should re-inherit all controls, pages and user controls from provided classes;
- 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:
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:

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;
}
}
The source code can be downloaded here.
Hope, it helps!