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

MEF - CompositeWPF under one roof

MEF and the CompositeWPF both offer distinct and very valuable services.  MEF allows us to easily create Add-in components and the CompositeWPF makes it easy to decouple our services and views.   Today I started integrating the two (reference MEF forum message).

Note: I am currently working on the Proof-Of-Concept (POC), when I have it completed it I will start TDD and refactor it as applicable.  See FLASH DEMO HERE   FULL SOURCE HERE (7meg) see notes under image.

2010.02.06 - Updated blog entry - was notified by reader that the "Full Source Here" link was invalid.  Updated to point to the last update I made while I was running the MEFContrib project (had to turn it over since contract requirements was no longer permitting me to maintain it). 

--------------------------------------------------------------------------------------------------

My objective will be to make it as simple as possible to implement the integration without making any changes to the CompositeWPF or MEF libraries - all of the wiring up will be done in the MEFContrib.Library project.

Below you'll see the only code required in the App.xaml.cs code behind file:

Notice that I have the MEF, CompositeWPF and Unity source code under SRC - these are the complete source code packages.  When the smoke settles I'll only upload DLLs, for now it will be helpful to step into code when it comes time to refactor.

Above the Parts folder contains MEF parts, they are compiled to the bin\Extensions folder.  The bootstrapper references our MEFContrib.Library.

The bootstrapper code that pertains to the CompositeWPF follows below.  The LoadReferencedModules  class below tells the CompositeWPF to load all modules referenced by the BasicWPFApp - in our case it will be the RSSFeed module that I copied/pasted from a CompositeWPF demo I wrote earlier.

namespace MEFContrib.Library.Base
{
    public class MEFBootStrapper : UnityBootstrapper
    {
        private Assembly _assembly;

        private Application _application;
        private MEFCompositionContainer _container;
 
/// <summary>
/// CompositeWPF
/// </summary>
/// <returns></returns>
protected override DependencyObject CreateShell()
{
    _application.MainWindow.Show();
    return _application.MainWindow;
}
 
/// <summary>
/// CompositeWPF
/// </summary>
/// <returns></returns>
protected override IModuleEnumerator GetModuleEnumerator()
{
    Container.RegisterType<ILogger, TraceToolLogger>();
 
    return LoadReferencedModules.GetModules(
        _application.MainWindow.GetType());
}
 
 
/// <summary>
/// MEF
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public MEFBootStrapper(object sender, BootStrapperEventArgs e)
{
    _application = sender as Application;
    _assembly = e.Assembly;
 
    _application.Exit += new ExitEventHandler(_application_Exit);
 
    if (Compose())
        Run();
    else
        _application.Shutdown();
}

The Window1.xaml.cs code-behind file contains an earlier POC so I left it as is.  Note there are no references to the MessageBoxLogger nor DebugLogger in our BasicWPFApp.  MEF will load whatever it finds in the bin\extensions folder which happens to contain these two logging features.

The Windows XAML below has two region names "MainRegion" and "MainView". 

<Window x:Class="BasicWPFApp.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:cal="http://www.codeplex.com/CompositeWPF"
    Title="Window1" Height="400" Width="600">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="25"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
 
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="2*" />
        </Grid.ColumnDefinitions>
 
        <StackPanel Orientation="Horizontal" Grid.ColumnSpan="2">
            <TextBox Name="textBox1" Width="120">Hello World</TextBox>
            <Button Name="button1"  Width="57" Click="button1_Click">Debug</Button>
            <Button Name="button2" Width="55" Click="button2_Click">MB</Button>
        </StackPanel>
        <GridSplitter Grid.Row="1" Width="10"/>
        <ItemsControl Grid.Row="1" Name="MainRegion"  
                      cal:RegionManager.RegionName="MainRegion" Margin="0,0,6,0">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <DockPanel/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
 
        <ItemsControl Grid.Row="1" Grid.Column="1" Name="MainView"  
                      cal:RegionManager.RegionName="MainView">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <DockPanel/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </Grid>
</Window>

When the RSSFeed module loads it will populate the MainRegion with the RSSFeedView, which in turn will populate the MainView RSSViewer.

namespace RSSFeed
{
    [Module(ModuleName = "RSSFeedModule")]
    public class RSSFeedModule : ModuleBase
    {
        /// <summary>
        /// Injection Constructor
        /// </summary>
        /// <param name="regionManager">Registered IRegionManager</param>
        /// <param name="container">Registered IUnityContainer</param>
        /// <param name="presenter">Registered IMainPresenter</param>
        public RSSFeedModule(IRegionManager regionManager,
            IUnityContainer container)
        {
            RegionManager = regionManager;
            Container = container;
        }
 
        protected override void RegisterViewsAndServices()
        {
            base.RegisterViewsAndServices();
 
            // Add view to region
            IRegion mainRegion = RegionManager.Regions["MainRegion"];
 
            // Name it so we can move it later in MainController.cs
            mainRegion.Add(Container.Resolve<RSSFeedView>(), "RssFeed");
        }
 
 
        public override void Initialize()
        {
            base.Initialize();
        }
    }
}

Tags: ,
Categories: CompositeWPF | MEF


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

MEF - WPF QuickStart

Currently the Managed Extensibility Framework (MEF) does not have documentation/steps for creating a new WPF Application (there is documentation for a console application) so I figured I'd document the steps I take here; there are plenty of samples to get me going.

  1. Create New WPF Application
  2. Remove the StartupUri="Window1.xaml" from the App.xaml file
  3. Add a reference to the Projects | ComponentModel project
  4. Add the following code to the App.xaml.cs code-behind file:

        /// <summary>
        /// Interaction logic for App.xaml
        /// </summary>
        public partial class App : Application
        {
            private CompositionContainer _container;

            [Import("MainWindow")]
            public new Window MainWindow
            {
                get { return base.MainWindow; }
                set { base.MainWindow = value; }
            }

            protected override void OnStartup(StartupEventArgs e)
            {
                base.OnStartup(e);

                if (Compose())
                {
                    MainWindow.Show();
                }
                else
                {
                    Shutdown();
                }
            }
            protected override void OnExit(ExitEventArgs e)
            {
                base.OnExit(e);

                if (_container != null)
                {
                    _container.Dispose();
                }
            }
            private bool Compose()
            {
                var catalog = new AggregatingComposablePartCatalog();
                catalog.Catalogs.Add(new AttributedAssemblyPartCatalog(Assembly.GetExecutingAssembly()));

                _container = new CompositionContainer(catalog.CreateResolver());
                _container.AddPart(this);

                try
                {
                    _container.Compose();
                }
                catch (CompositionException compositionException)
                {
                    MessageBox.Show(compositionException.ToString());
                    return false;
                }
                return true;
            }

        }
     

  5. Add an [Export("MainWindow")] attribute to the Window1.xaml.cs file's Window1 class.



  6. Compile and run the application - your Window1.xaml file should load.  If you get two windows then you failed to comply with step #2 above

  
A less conventional route is to use a "BootStrapper" class

The BootStrapper code follows:

namespace MEFContrib.Library.Base
{
    public class BootStrapper
    {
        private Assembly _assembly;
        private Application _application;
        private CompositionContainer _container;

        public BootStrapper(object sender, BootStrapperEventArgs e)
        {
            _application = sender as Application;
            _assembly = e.Assembly;

            _application.Exit += new ExitEventHandler(_application_Exit);

            if (Compose())
                _application.MainWindow.Show();
            else
                _application.Shutdown();
        }

        void _application_Exit(object sender, ExitEventArgs e)
        {
            if (_container != null)
                _container.Dispose();
        }

        private bool Compose()
        {
            var catalog = new AggregatingComposablePartCatalog();
            catalog.Catalogs.Add(new AttributedAssemblyPartCatalog(_assembly));
            ((IMefApplication)_application).OnAddCatalog(catalog.Catalogs);

            _container = new CompositionContainer(catalog.CreateResolver());
            _container.AddPart(_application);

            try
            {
                _container.Compose();
            }
            catch (CompositionException compositionException)
            {
                MessageBox.Show(compositionException.ToString());
                return false;
            }
            return true;
        }
    }
}

MEF_QuickStart.zip (2.10 mb)


Tags:
Categories: MEF


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

Hello MEF (Managed Extensibility Framework)

It was a tough decision to put another new framework on the burner for the Solution Development Management System (http://www.CodePlex.com/SDMS)....  It seems just as I get a framework under my belt, i.e., Composite Application Block (CAB), Smart Client Software Factory (SCSF), Web Client Software Factory (WCSF) and CompositeWPF (aka Prism) a new, and better one comes out to replace them before they have had a chance to mature - they are labeled "alternate tools" but the reality is they are tools that have the potential to become obsolete in the wake of the new framework. 

To be the best I can be (for those that rely on me) I have to stay on the bleeding edge because these will be tomorrows tools; perhaps literally.  The day after tomorrow they could be replaced.   This reality use to stress me out; I always felt like I was behind, at least until I read Rocky Lhotka's article "Keeping sane in the wake of technologies pace"; I can't articulate why "Misery loves company" but this article changed my way of thinking (forever) - as Rocky noted I now have "chosen to embrace and revel in the change".

An excerpt from Rocky's article follows: 

In 2007 I found myself becoming increasingly unhappy, feeling more and more overwhelmed by the rate of change. I felt like I was slipping farther and farther behind. However, the rate of change is outside the control of any of us individually. So this year, rather than fight or despair about it, I’ve chosen to embrace and revel in the change.

The problem with "revel in change" is that you can have no loyalties, particularly to communities or to things that you wanted to do, e.g., I had Contrib projects planned for both the SCSF and CompositeWPF that had to be abandoned.   This part leaves me with a deep sense of regret; feeling like I abandoned the "community team".  I rationalize the feeling by remembering that I have a higher priority to my current and future contracts (it doesn't always help)....

So tomorrow's tool is the Managed Extensibility Framework (MEF).  Since it is Glenn Block's baby I am not concerned that it doesn't even offer a CTP or Beta.   I'm sure I'll take a hit for this  - I'll have to constantly refactor (which is fine for my Open Source project).   For clients it can be more risky and has to be weighed heavily... I remember convincing my clients, when .NET 2.0 was in beta, that I should write their applications using it.  This ended up being a smart decision but I took some serious black-eyes when .NET 2.0 was released; security was increased from beta to release and the sites went down and I was facing problems that there was no googling for - it brought new meaning to the term "Bleeding Edge" because I bled profusely (I lived).

My current client does have a golden rule that I can bring anything in-house, and train the team on it, as long as it is released.  Should my next contract have a similiar rule (which I can respect) then I hope to have an infrastructure in place, and understanding of the framework, so that no time is spent on infrastructure - only Use Case.

It's time to bleed....

MEF and CompositeWPF will co-exist Cool


Tags: ,
Categories: CompositeWPF | MEF


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