📜 ⬆️ ⬇️

Using MEF (Managed Extensibility Framework) for developing 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 implementations while the application is running (recomposition), work with multiple implementations of abstractions, split monolithic applications into independent parts, etc.

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

The situation in web applications is completely different. The developers are not responsible for the creation of pages, controls, etc., because Asp.net Runtime takes care of everything.
')
Thus, to implement MEF support for web applications, you should combine the algorithms of both tools.

Known solutions


There is a solution for how MEF can be used for WebForms applications. But this example has several important limitations.

High level architecture


The goal of the solution is to implement support for the MEF container without the need to use the re-inheritance mechanism and to provide support for the HttpModule and HttpHandler .

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

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

On the other hand, HttpModule are created once immediately after the start of the entire application. Thus, the import for them should be done as early as possible and all HttpModule should be stored in the 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 HttpModule . In this module, add the Pre_Init and Init handlers for the current requested page. In the first handler, it is possible to perform composition for the page, the master page, and user controls. The Init event will allow performing a composition for server controls, since on Pre_Init they do not exist yet.

Example:
public class ComposeContainerHttpModule : IHttpModule<br>{<br> public void Init(HttpApplication context)<br> {<br> context.PreRequestHandlerExecute += ContextPreRequestHandlerExecute;<br> }<br><br> private void ContextPreRequestHandlerExecute( object sender, EventArgs e)<br> {<br> Page page = HttpContext .Current.CurrentHandler as Page ;<br> if (page != null )<br> {<br> page.PreInit += Page_PreInit;<br> page.Init += Page_Init;<br> }<br> }<br><br> private void Page_Init( object sender, EventArgs e)<br> {<br> Page handler = sender as Page ;<br><br> if (handler != null )<br> {<br> CompositionBatch batch = new CompositionBatch();<br> batch = ComposeWebPartsUtils.BuildUpControls(batch, handler.Controls);<br> ContextualCompositionHost.Container.Compose(batch);<br> }<br> }<br><br> private void Page_PreInit( object sender, EventArgs e)<br> {<br> Page handler = sender as Page ;<br><br> if (handler != null )<br> {<br> CompositionBatch batch = new CompositionBatch();<br> batch = ComposeWebPartsUtils.BuildUp(batch, handler);<br> batch = ComposeWebPartsUtils.BuildUpUserControls(batch, handler.Controls);<br> batch = ComposeWebPartsUtils.BuildUpMaster(batch, handler.Master);<br> ContextualCompositionHost.Container.Compose(batch);<br> }<br> }<br><br> public void Dispose()<br> {<br> }<br>}<br> <br> * 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, the recursive function implements import for server controls and their controls.

The BuildUp(CompositionBatch batch, Object o) method BuildUp(CompositionBatch batch, Object o) checks if the Object o any imported items and adds it to the list of objects to implement the composition. Once all controls have been processed, the CompositionBatch object can be used to initialize the container. After that, all imports will be initialized and available during the life of the query.

Example:
public static class ComposeWebPartsUtils<br>{<br> public static CompositionBatch BuildUp(CompositionBatch batch, Object o)<br> {<br> ComposablePart part = AttributedModelServices.CreatePart(o);<br><br> if (part.ImportDefinitions.Any())<br> {<br> if (part.ExportDefinitions.Any())<br> throw new Exception( string .Format( "'{0}': Handlers cannot be exportable" , o.GetType().FullName));<br><br> batch.AddPart(part);<br> }<br><br> return batch;<br> }<br><br> public static CompositionBatch BuildUpUserControls(CompositionBatch batch, ControlCollection controls)<br> {<br> foreach (Control c in controls)<br> {<br> if (c is UserControl)<br> batch = ComposeWebPartsUtils.BuildUp(batch, c);<br> batch = BuildUpUserControls(batch, c.Controls);<br> }<br><br> return batch;<br> }<br><br> public static CompositionBatch BuildUpControls(CompositionBatch batch, ControlCollection controls)<br> {<br> foreach (Control c in controls)<br> {<br> batch = ComposeWebPartsUtils.BuildUp(batch, c);<br> batch = BuildUpControls(batch, c.Controls);<br> }<br><br> return batch;<br> }<br><br> public static CompositionBatch BuildUpMaster(CompositionBatch batch, MasterPage master)<br> {<br> if (master != null )<br> batch = BuildUpMaster(ComposeWebPartsUtils.BuildUp(batch, master), master.Master);<br><br> return batch;<br> }<br>}<br> <br> * This source code was highlighted with Source Code Highlighter .

Note:
I cannot use the same technique, which is used in the example from the codeplex site (inheritance of PageHandlerFactory and redefinition of the GetHandler() method), since By this time, no controls for the page have yet been created.

HttpHandler

Handlers do not have events where all imports could be satisfied. It would be ideal to use the corresponding HandlerFactory and override the GetHandler() method as was done 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 see no other option than implementing my own SimpleWebHandlerFactory factory instead of the one present in the .net framework. The main goal of any HandlerFactory is to determine the type that should be instantiated for the current request. HandlerFactory can only get type by parsing the resource that was requested. Thus, I need a parser that could parse the HttpHandler code. Fortunately, such a parser exists ( SimpleWebHandlerParser ), is public and I just need to make a wrapper for it ( WebHandlerParser ).

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

Below is the code of classes that implement the above described functionality.
public class SimpleWebHandlerFactory : IHttpHandlerFactory<br>{<br> public virtual IHttpHandler GetHandler( HttpContext context, string requestType, string virtualPath, string path)<br> {<br> Type type = WebHandlerParser.GetCompiledType(context, virtualPath, path);<br> if (!( typeof (IHttpHandler).IsAssignableFrom(type)))<br> throw new HttpException( "Type does not implement IHttpHandler: " + type.FullName);<br><br> return Activator.CreateInstance(type) as IHttpHandler;<br> }<br><br> public virtual void ReleaseHandler(IHttpHandler handler)<br> {<br> }<br>}<br><br> internal class WebHandlerParser : SimpleWebHandlerParser<br>{<br> internal WebHandlerParser( HttpContext context, string virtualPath, string physicalPath)<br> : base (context, virtualPath, physicalPath)<br> {<br> }<br><br> public static Type GetCompiledType( HttpContext context, string virtualPath, string physicalPath)<br> {<br> WebHandlerParser parser = new WebHandlerParser(context, virtualPath, physicalPath);<br> Type type = parser.GetCompiledTypeFromCache();<br> if (type != null )<br> return type;<br> else <br> throw new HttpException( string .Format( "File '{0}' is not a web handler." , virtualPath));<br> }<br> <br> protected override string DefaultDirectiveName<br> {<br> get <br> {<br> return "webhandler" ;<br> }<br> }<br>}<br><br> public class ComposableWebHandlerFactory : SimpleWebHandlerFactory<br>{<br> public override IHttpHandler GetHandler( HttpContext context, string requestType, string virtualPath, string path)<br> {<br> IHttpHandler handler = base .GetHandler(context, requestType, virtualPath, path);<br><br> if (handler != null )<br> {<br> CompositionBatch batch = new CompositionBatch();<br> batch = ComposeWebPartsUtils.BuildUp(batch, handler);<br> ContextualCompositionHost.Container.Compose(batch);<br> }<br><br> return handler;<br> }<br>}<br> <br> * This source code was highlighted with Source Code Highlighter .

HttpModule

As I mentioned earlier, all HttpModules are created at the start of the application. So, I have to make a composition right after the application starts.
Example:
public class ScopedContainerHttpModule : IHttpModule<br>{<br> private CompositionContainer _container;<br><br> public void Init(HttpApplication app)<br> {<br> ComposeModules(app);<br> }<br><br> private void ComposeModules(HttpApplication app)<br> {<br> CompositionBatch batch = ComposeWebPartsUtils.BuildUpModules(app);<br> _container.Compose(batch);<br> }<br>}<br><br> public static class ComposeWebPartsUtils<br>{<br> public static CompositionBatch BuildUpModules(HttpApplication app)<br> {<br> CompositionBatch batch = new CompositionBatch();<br><br> for ( int i = 0; i < app.Modules.Count - 1; i++)<br> batch = BuildUp(batch, app.Modules.Get(i));<br><br> return batch;<br> }<br>} <br><br> * This source code was highlighted with Source Code Highlighter .

I get an HttpApplication object, retrieve all 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 in order for my solution to work.
< httpHandlers > <br> ......<br> < remove path ="*.ashx" verb ="*" /> <br> < add path ="*.ashx" verb ="*" type ="common.composition.WebExtensions.ComposableWebHandlerFactory, common.composition" /> <br> ......<br> </ httpHandlers > <br> < httpModules > <br> < add name ="ContainerCreator" type ="common.composition.WebExtensions.ScopedContainerHttpModule, common.composition" /> <br> ......<br> < add name ="ComposeContainerModule" type ="common.composition.WebExtensions.ComposeContainerHttpModule, common.composition" /> <br> </ httpModules > <br><br> * This source code was highlighted with Source Code Highlighter .

I remove the default * .ashx file handler and add mine ( ComposableWebHandlerFactory ).

I add the ContainerCreator module to create and initialize the infrastructure for the local and global containers.

ComposeContainerModule will be used to initialize the local container.

That's all!

I did not need to use inheritance 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 features presented become available.

Example


I use the demo that I took from the WebFomsAndMef example with a few modifications.

Each element of the application imports the SampleCompositionPart class with the [PartCreationPolicy(CreationPolicy.NonShared)] attribute, which ensures that a new instance of SampleCompositionPart during the act of import. This class contains the only Id field that is returned by Guid.

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

Example:
///PageSample.aspx <br><asp:Content ID= "BodyContent" runat= "server" ContentPlaceHolderID= "MainContent" ><br> <% =SamplePart.Id %><br></asp:Content><br> <br> ///PageSample.cs <br> public partial class PageSample : System.Web.UI. Page <br>{<br> [Import]<br> public SampleCompositionPart SamplePart<br> {<br> get ;<br> set ;<br> }<br>} <br><br> * This source code was highlighted with Source Code Highlighter .


Source code can be downloaded here .

I hope this article will help you start using MEF in asp.net WebForms applications.

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


All Articles