CASK - MEF / Unity - Chicken before the egg....

Problem Statement:  The Community Starter Kit, while very powerful and dynamic, is not extensible due to it's complexity; although CASK "is" extensible in the technical sense the CSK has not taken off with the C# Community because the learning curve is very steep.    

Vision Statement: Use MEF to permit programmers to easily create modules that users can upload, configure and use (isolating them from the complexity of the system).   Currently I have successfully plugged in the MEFContrib's MEFContrib.Library.Web project.  


Code review request - email comments to Bill@global-webnet.com or post them HERE in the MEF forum.   The current design will permit MEF to be used to load MEFModules so that they are available when the page attempts to dynamically instantiate them.   Unity will be used for dependency injection, event aggregation, etc.   In summary: MEF usage will have to be limited to loading external modules (uploaded modules) and Unity will be used for internal use. 
 

CASK HIGH-LEVEL OVERVIEW (within the context of this story)

The Community (Advanced) Starter Kit dynamically instantiates classes (see first arrow in figure B) and is a data driven application; there is only one page (Community_Default.aspx) that serves up all pages for the entire site.  

Currently there is an IModule interface that allows modules to be programmatically added via the Admin | Modules section (the Calendar and Update module are currently functional).  The problem is these modules have to be compiled into the application otherwise there are issues that MEF is here to eliminate.  

The "dynamic instantiation" of the applicable class requires that the module/assembly be loaded in memory prior to the CreateInstance statement - MEF modules must be loaded prior to the ASP.NET Page_Init().    Currently this is accomplished in the IHttpModule.Page_Init() method.

This forced me to modify the MEFContrib project so that MEF objects were composed prior to the pages Page_Init().   Since all controls are not added until after Page_Init(), reference second arrow in Figure B, this means those dynamically modules cannot have MEF Imports/Exports. 

Modified UnityHttpModule.cs from MEFContrib project: 

using System;

using System.Web;

using System.Web.UI;

using System.Collections.Generic;

using System.ComponentModel.Composition;

using Microsoft.Practices.Unity;

 

namespace MEFContrib.Library.Web.Unity {

public class UnityHttpModule : IHttpModule {

 

private HttpApplication context;

private IUnityContainer Container {

    get { return HttpContext.Current

        .Application.GetContainer(); }

}

 

/// <summary>

/// Ensure any MEF Imports/Exports are processed/available

/// for Page_Init

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void page_Init(object sender, EventArgs e)

{

    CompositionContainer compositionContainer =

        ((CompositionContainer)Container

            .Resolve<ICompositionService>());

 

    compositionContainer.Compose();

}

 

/// <summary>

/// Build up each control in the page's control tree - done during

/// post init to ensure dynamically loaded controls get processed

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void OnPageInitComplete(object sender, EventArgs e)

{

    Page page = (Page)sender;

    foreach (Control c in GetControlTree(page))

        Container.BuildUp(c.GetType(), c);

}

 

/// <summary>

/// Get the controls in the page's control tree excluding the page

/// itself

/// </summary>

/// <param name="root"></param>

/// <returns></returns>

private IEnumerable<Control> GetControlTree(Control root)

{

    foreach (Control child in root.Controls)

    {

        yield return child;

        foreach (Control c in GetControlTree(child))

            yield return c;

    }

}

 

public void Init(HttpApplication context)

{

    this.context = context;

    context.PreRequestHandlerExecute +=

        OnPreRequestHandlerExecute;

}

 

public void Dispose()

{

    context.PreRequestHandlerExecute -=

        OnPreRequestHandlerExecute;

}

 

private void OnPreRequestHandlerExecute(

    object sender, EventArgs e)

{

 

    IHttpHandler handler = HttpContext.Current.Handler;

    HttpContext.Current.Application.GetContainer()

        .BuildUp(handler.GetType(), handler);

 

    Page page = HttpContext.Current.Handler as Page;

    if (page != null)

    {

        page.InitComplete += OnPageInitComplete;

        page.Init += new EventHandler(page_Init);

    }

}

 

 

}

}

 

 

Below we see that when we hit the breakpoint in Page_Init (figure b) that we have both a reference to the IUnityContainer and MEFModule - breakpoint watch window follows: 


Figure A. 


Figure B.

CASK full source available HERE Changeset 45207

Modified MEFHttpApplication.cs from MEFContrib.Library.Web 

using System;

using System.ComponentModel.Composition;

using System.Web;

using MEFContrib.Library.Web.Unity;

using Microsoft.Practices.Unity;

 

namespace MEFContrib.Library.Web.UI {

public class MEFHttpApplication : HttpApplication {

 

protected IUnityContainer Container

{

    get { return HttpContext.Current

            .Application.GetContainer(); }

}

 

protected void Application_Start(object sender, EventArgs e)

{

    Container

        .AddNewExtension<MEFContainerExtension>();

 

    var catalog =

        new DirectoryPartCatalog("bin", "*.dll", false);

 

    Container

        .RegisterInstance<ComposablePartCatalog>(catalog);

 

    ApplicationStart(sender, e);

}

 

protected void Application_End(object sender, EventArgs e)

{

    ApplicationEnd(sender, e);

    Container.Dispose();

}

 

protected void Application_BeginRequest(

    object sender, EventArgs e)

{

    var catalog = Container.Resolve<ComposablePartCatalog>();

    CompositionContainer compositionContainer =

        new CompositionContainer(catalog);

    Container.RegisterInstance<ICompositionService>(

        compositionContainer);

 

    ApplicationBeginRequest(sender, e);

}

 

protected void Application_EndRequest(

    object sender, EventArgs e)

{

    ApplicationEndRequest(sender, e);

}

 

protected void Application_Error(

    object sender, EventArgs e)

{

    ApplicationError(sender, e);

}

 

protected virtual void ApplicationStart(

    object sender, EventArgs e) { }

protected virtual void ApplicationEnd(

    object sender, EventArgs e) { }

protected virtual void ApplicationBeginRequest(

    object sender, EventArgs e) { }

protected virtual void ApplicationEndRequest(

    object sender, EventArgs e) { }

protected virtual void ApplicationError(

    object sender, EventArgs e) { }

}

 

}

 


Tags: ,
Categories: MEF


Actions: E-mail | Permalink |  Grammar/Typo/Better way? Please let me know