Click here to Skip to main content
15,868,164 members
Articles / Web Development / ASP.NET

MEF with ASP.NET - "Hello World!"

Rate me:
Please Sign up or sign in to vote.
4.84/5 (18 votes)
12 Mar 2011CPOL12 min read 65.7K   1.2K   41   7
Basic MEF (Managed Extensibility Framework) with ASP.NET. Extreme barebones "Hello World!" example.

Introduction

This is a super simple and barebones "Hello World!" MEF example in ASP.NET.

The examples demonstrates the basic setup steps needed to get started with MEF in ASP.NET.

I hope they will give the necessary "bootstrap" for people to get started with MEF in ASP.NET, and help them to get the basic concept of the framework down, and thus be better armed to venture deeper into the more intricate aspects themselves.

It gives a fundamental setup for people to toy with and experiment with and expand on.

I sure wish I'd come upon some very simple examples like these a couple of days ago myself.
It would have saved Google hundreds of searches, and myself many many hours of fruitless reading.
Or maybe my Google-fu just isn't strong enough?
Please drop useful (beginner) links in the comments!

Background

MEF (Managed Extensibility Framework) is a framework developed by Microsoft.

The MEF community site on codeplex has more information and examples - But sadly nothing simple for getting your feet wet - At least for ASP.NET (non MVC).

To put it really short: MEF is a framework for wiring up plugins to your application (so that you can access whatever services they provide).

MEF makes this very easy, compared to MAF (Managed Addin Framework) that is also made by Microsoft. The general consensus seems to be to keep well away from MAF unless you have a special need. The main reason for this is that MAF code wise is rather convoluted to get up and running.

The difference between the two: "MEF is about "extensibility", and MAF is about "Isolation" (either conquering or enabling)".

The majority of MEF examples out there are for console applications, Silverlight/WPF. There are a few pure web application examples, but they all seem to be for ASP.NET MVC.

I've virtually scoured the net for days without finding a simple and to the point no frills ASP.NET example, the frustration of which I hope can spare my fellow wannabe MEF ASP.NET coders with this little article.

The *very* few pure ASP.NET examples I've been able to find all go way overboard, from the point of view of someone just wanting to try it out and see something work without understanding realms of code.

There's a reason those examples "go overboard" though, as eventually if you want to get serious with MEF in ASP.NET, you'll need to "go overboard".

But one needs to get on the ship first - Hence, here we are.

How MEF Works

The general idea is:

  • Decorate (wire) your code with [Import] [Export] attributes (for MEF to match up)
  • Load your plugins into a catalog (I'm using DirectoryCatalog, but there are a few more types of catalog)
  • Put your catalog into a container, i.e., CompositionContainer (There are a couple of variations on doing this)
  • Have the container method ComposeParts do the wiring up (also has a couple of variations)

And you are done.

Once you got your head wrapped around that (and know about the wiring bit - see below), MEF itself becomes fairly easy.

I have to admit it took me more time than I care to admit to extract the above very simple steps from all the MEF stuff out there, as it's never really spelled out, and kind of disappears in the demonstrations of all the fancy stuff you can do with MEF.

Time for some code...

Using the Code

To create your own barebones example from scratch, you need to do the following:

  • Setup an empty web application project in Visual Studio 2008/2010. You need .NET 4 (MEF is included).
  • "Add a reference" to System.ComponentModel.Composition to the project.
  • Add a webform page (aspnetMEFBasic.aspx) to the project.
  • Add a div tag to the page (for output purposes):
    HTML
    <div id="div1" runat="server"></div>
  • In the code behind (aspnetMEFBasic.aspx.cs), add:
    C#
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
  • Replace the code with:
    C#
    namespace aspnetMEFBasic {
      public partial class aspnetMEFBasic : System.Web.UI.Page {
        //Import our "plugin"
        [Import]
        Class1 c1;
    
        protected void Page_Load(object sender, EventArgs e) {
          //Step 1:
          //Find the assembly (.dll) that has the stuff we need 
          //(i.e. [Export]ed stuff) and put it in our catalog
          DirectoryCatalog catalog = new DirectoryCatalog
    	(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"));
    
          //Step 2:
          //To do anything with the stuff in the catalog, 
          //we need to put into a container (Which has methods to do the magic stuff)
          CompositionContainer container = new CompositionContainer(catalog);
    
          //Step 3:
          //Now lets do the magic bit - Wiring everything up
          container.ComposeParts(this);
    
          //Step4:
          //Lets see if it works
          div1.InnerText = c1.s1;
        }
      }
    }

    Note the [Import] attribute (and read the comments).

  • Add a class file (Code.cs) to the project (just plop it in the root for now). Add the following to the file:
    C#
    using System.ComponentModel.Composition;
  • Replace the code with:
    C#
    namespace aspnetMEFBasic {
        [Export]
        public class Class1 {
          [Import]
          public string s1; 	//Gets it's value ("Hello World!") from "somewhere" - 
    			//MEF takes care of this.
        }
    
        public class Class2 {
          private class Class3 {
            [Export]
            public string s3 = "Hello World!"; 	//Not as "private" as one would think- 
    					//Not to MEF at least
          }
        }
    }
  • Note the two [Export] and the one [Import] attribute. Also note that Class3 is private - and that we normally wouldn't be able to get at s3 from the outside.
  • Compile and run the code - and the aspnetMEFBasic.aspx page should display "Hello World!"

Points of Interest

First off - This is of course not truly a "disconnected" plugin that we are using for our page, as Class1 is declared directly in our application project - Thus, we could declare a variable s1 of Class1 type in the first place (we do however manage to grab the private s3 variable)

I've chosen to do it like this simply for demonstration purposes - and to keep it very very simple.

It could have been made even simpler in fact. Class2 (and thus Class3) are not really needed for a barebones example. You can remove those and then in Class1 do public string s1 = "Hello World!; instead (so that s1 has a value) and it will work the same.

But I've done it like this to demonstrate just how "dirty" MEF can get (reaching inside a private class - Class3) - and thus making you aware that you need to watch out!

Today's Host is

Notice the "container.ComposeParts(this);" bit in step 3 (in the codebehind).

In the example, "this" refers to the current webpage we are on - You could have used "sender" as well (one of the arguments to Page_Load). "sender" will often be the one you use, once you start hooking into the events of the ASP.NET Page Lifecycle.

In MEF Parlour, "this"/"sender" is the "host" process, i.e., the process that needs to be hooked up to whatever services are provided in our catalog to take advantage of these.

You can in fact pass in any class, having some [Import] attributes set that needs to be hooked up with their corresponding [Export]s from the catalog.

The really important take away here is:
The catalog (and thus the container it's added to) itself has absolutely no clue about who the host is (will be) - Hence, you need to pass the host in as a parameter to let MEF know who it is that needs to be hooked up.

And this is where it can get a bit tricky in a web application (as opposed to a console application) once you start coding outside the immediate codebehind (where the host is obvious) as it's then not always straightforward to figure out how to get hold of the correct host to pass as parameter to compose.

Wiring up - [Import]/[Export]

The wiring up is controlled with the [Import] and [Export] attributes. MEF makes sure the right [Import] gets the corresponding [Export]. MEF does this by inferring the types of the things that are imported/exported - If the type of an [Import] matches the type of an [Export], they are hooked up.

I could also have supplied the type myself (and in general, you will): [Import(typeof(string))]/[Export(typeof(string))] for the simple string and [Import(typeof(Class1))] for the [Import] at the top of the aspnetMEFBasic.aspx.cs page (with a corresponding [Export(typeof(Class1))] at the top of Class1).

This will become useful later!

What happens if there are two [Export(typeof(string))] to one [Import(typeof(string))]?

It is in fact possible for an [Import] to take a list of [Export]s. I will however not cover here how that works (this is of course not possible for the simple [Import] types - int, string, etc.)

Most console examples out there demonstrate this quite well - like the Calculator example.
That MSDN page is a very good place to start looking for more information about MEF.

What if you have more than one [Export(typeof(string))], that each needs to go to different [Import(typeof(string))]s?

Then we need to help MEF a bit to know which [Export(typeof(string))] goes to which [Import(typeof(string))]:
You can do so by adding a name value to the attribute like:
[Import("TheString1")] and likewise [Export("TheString1")] and
[Import("TheString2")] and likewise [Export("TheString2")].
Note that these ("TheString1" and "TheString2") are simply strings - They are not types!
This way MEF knows which string [Import]/[Export] goes where even though each [Export] could in fact fit with each of the [Import]s (type wise).

And if we were really nice, we would still add the type as well like this:
[Import("TheString1", typeof(string))] and likewise [Export("TheString1", typeof(string))] and
[Import("TheString2", typeof(string))] and likewise [Export("TheString2", typeof(string))].
(as said, this becomes useful later - So it's a good habit to always add the type too).

Expanded Wiring (With Which True Plugins will be Possible)

In general, you should *avoid* types of concrete classes (like Class1) in [Import]/[Export] attributes, and the same goes for simple types like string and int etc. (in general mind you).

Instead, you should use types of Interface - because this is where the real magic starts to happen!

(small aside)
In the above code example, the only real magic that happened was the bit where we somehow managed to get hold of the private s3 variable.
This is more the magic of reflection (that MEF uses) than that of MEF itself.
With reflection you can do "dirty tricks" like that.
The danger being that you inadvertently get the wrong bits hooked up if you are not careful with your naming in the [Import]/[Export] attributes.
Naming something [Import("Sendmail")] is probably not a good idea, as everyone does that... Hence you might get hooked up to something you didn't expect (once you start importing 3rd party plugins)
(end aside)
Back to the real magic - Let's make an actual true plugin.
That is, a piece of code (plugin) our web app has no prior knowledge about how it works - Except, you guessed it, the Interface, to that plugin.

Interfaces are often referred to as "Contracts" both in this context and others. It's the contract that the plugin signs (by implementing the Interface), thus agreeing to supply what's defined in the contract, and conversely, it tells the application which "services" it can expect from a given plugin that agrees to that contract.

Both the application and the plugin need to be aware of the contract (Interface) of course - This is the glue that will bind them together. The contract has zero functionality in and of itself though (which will become rather obvious in a bit).

To setup the true application/plugin structure, we will need three different projects (as opposed to just the one we had earlier):

  • Web application project
  • Interface project
  • Plugin project

You can make a Visual Studio solution for each if you like - to truly demonstrate how a "Real world" example would work with different parties making the main app and others the plugins (and either or the Interfaces/contracts).

Or - simply have 3 distinct projects in one solution as that makes it a bit easier to hook things together when you make changes in the code - as it can be automated (and you'll be making both the main app and the plugin anyhow).

The first thing we need to make is the Interface project. And it's as simple as can be - as said, it has zero functionality by itself.

The Interface project consists of one class file (aspnetMEFInterface.cs) only that looks like this:

C#
namespace aspnetMEF {
  public interface IHelloWorld {
    string s { get; }
  }
}

That's all - Really. No tricks. This is simply a contract about delivering (or receiving) a string. That's all we need.

The plugin (aspnetMEFPlugin.cs) is as simple as can be as well.

C#
using System.ComponentModel.Composition;

namespace aspnetMEF {
  public class aspnetMEFPlugin : IHelloWorld  {
    private string _s = "HelloWorld!";
    #region IHelloWorld Members

    [Export("aspnetMEFHelloWorld", typeof(string))]
    public string s {
      get { return _s; }
    }

    #endregion
  }
}

Remember to "Add a reference" to System.ComponentModel.Composition to the project.

And likewise the aspnetMEFwebapp.aspx.cs:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace aspnetMEF {
  public partial class aspnetMEF : System.Web.UI.Page {
    [Import("aspnetMEFHelloWorld", typeof(string))]
    string s;

    protected void Page_Load(object sender, EventArgs e) {
      var catalog = new DirectoryCatalog(System.IO.Path.Combine
		(AppDomain.CurrentDomain.BaseDirectory, "bin"));
      var container = new CompositionContainer(catalog);
      container.ComposeParts(this);

      div1.InnerText = s;
    }
  }
}

Remember to "Add a reference" to System.ComponentModel.Composition to the project.

And the aspnetMEFwebapp.aspx simply has the div1 added:

HTML
<div id="div1"  runat="server">
</div>

Now how to make this work - Assuming each is an individual solution.
Take the aspnetMEFInterface.dll and dump it into the bin folder of both aspnetMEFPlugin and aspnetMEFwebapp. "Add a reference" to it in both projects (Note that in practise, you need to do this before writing code referencing the interface of course).

And that's that.

Start up aspnetMEFwebapp and greet the world :-)



Only importing/exporting a simple string is a bit boring (and limited) ofc.

To expand on this example is simple though (exercise for the reader).

In aspnetMEFPlugin.cs, do the following:

C#
using System.ComponentModel.Composition;

namespace aspnetMEF {
  [Export("aspnetMEFHelloWorld", typeof(IHelloWorld))]
  public class aspnetMEFPlugin : IHelloWorld  {
    private string _s = "HelloWorld!";
    #region IHelloWorld Members

		//[Export("aspnetMEFHelloWorld", typeof(string))]
    public string s {
      get { return _s; }
    }

    #endregion
  }
}

(Showing the file in full to make it absolutely clear as to what has changed.)
Notice that the [Export] attribute has been moved to the class declaration.
Note that the type of the [Export] has been changed to IHelloWorld (this is the true power). Everything else is the same.

In aspnetMEFwebapp.aspx.cs, change the [Import] to match, and naturally change the type of the s variable to match (I've changed the name as well - to hw).

C#
[Import("aspnetMEFHelloWorld", typeof(IHelloWorld))]
IHelloWorld hw;
...
div1.InnerText = hw.s;

Everything else is the same.

No changes in the Interface itself.

Notice how we are now importing an entire instance of a class. This is the final piece of magic.

Now we can access the methods of our plugins and not just a simple string getter.

More exercises for the reader:

  • Add some methods to the plugin
  • Add those methods to the interface as well (or the webapp won't know they exist)

You now have a fully functioning plugin ecosystem!!!

Enjoy :-)

Some Notes Regarding Webapps and MEF

What we've done so far is the simplest of simple. What makes webapp and MEF a bit tricky as compared to a console app is that of state. Each time a webpage is served, we lose all the work we just did (as opposed to a console app that keeps everything in memory all the time).
We need to do the whole catalog/container/compose thing each and every time we serve a page. And it's expensive... As mentioned, MEF relies on reflection to do its magic, and reflection is well... expensive.

Application to the rescue - right? Yes and no... As that raises the spectre of concurrency (or Parts Lifetime in MEF parlour) - Have a look here.

Not to mention, we might want access to (dynamic) controls (user and server) and master pages and and and....
As such, one needs to hook the catalog/container/compose parts into the aspnet page lifecycle. And combining all this is where it starts getting tricky : Have a look here :

It's doable, but it starts getting a bit scary :-)

History

  • 2011.03.11: "Todays host is" section added

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Denmark Denmark
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionPlug-in capabilities Pin
David Radcliffe2-Apr-13 5:03
David Radcliffe2-Apr-13 5:03 
AnswerRe: Plug-in capabilities Pin
mgkr2-Apr-13 5:50
mgkr2-Apr-13 5:50 
QuestionMore detail informationa about MEF Pin
Gunjal Amol21-Aug-12 22:09
Gunjal Amol21-Aug-12 22:09 
SuggestionMy vote of 5 Pin
Ra-one29-May-12 19:04
Ra-one29-May-12 19:04 
GeneralMy vote of 5 Pin
Vinod Satapara7-Feb-12 1:12
Vinod Satapara7-Feb-12 1:12 
GeneralNice Job Pin
Mike Hankey11-Mar-11 17:54
mveMike Hankey11-Mar-11 17:54 
Just starting to get into MEF and this helps get me started.
If you are cross-eyed and have dyslexia, can you read all right?
http://www.hq4thmarinescomm.com[^]
JaxCoder.com[^]WinHeist - Windows Electronic Inventory SysTem

GeneralRe: Nice Job Pin
mgkr12-Mar-11 1:55
mgkr12-Mar-11 1:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.