次の方法で共有


Using MEF 2 with ASP.NET MVC 3 [Nick]

This post discusses features in the preview version of MEF, and some details may change between now and the full release.

The latest MEF preview includes a new assembly, System.ComponentModel.Composition.Web.Mvc.CodePlex.dll, that simplifies the task of building flexible, testable and maitainable ASP.NET MVC web applications.

To achieve this it:

  • provides dependency injection for Controller classes,
  • defines a simple convention for identifying and configuring MEF parts,
  • maps the lifetime of part instances to the ASP.NET request processing lifecycle, and
  • simplifies the use of dependency injection in action filter attributes and model binders.

The .NET 4.0 version of the 'composition provider' assembly that can be downloaded from CodePlex only supports the preview version of MEF 2 with which it is bundled, and requires ASP.NET MVC 3. We expect to release the final version as a NuGet package targeting the MEF 2 RTM.

Update: the Composition Provider packages targeting the .NET Framework 4.5 Beta are now available on NuGet!. See this post for details including updated instructions.

In this article, we'll walk through the process of setting up basic composition for controllers and parts. We'll then examine some of the deeper support provided for other ASP.NET MVC features. You'll need some familiarity with both managed composition (MEF/IoC) and ASP.NET MVC.

This post is accompanied by a sample application called 'Fabrikam Books.' You may wish to download the sample solution now so that you can explore and experiment with runnable code.

Enabling the composition provider in an ASP.NET MVC application

Setting up the composition provider is super simple:

  1. If you haven't already, create your ASP.NET MVC 3 web application.
  2. Add assembly references to:
    • System.ComponentModel.Composition.CodePlex.dll
    • System.ComponentModel.Composition.Web.Mvc.CodePlex.dll
  3. Use Visual Studio to create a folder in the root of your web application project called Parts

The last step — creating a Parts folder — is a good way to get started. In the long run, you may reorganise and have several parts folders in your project, but the important thing is that parts for composition live in a namespace that includes a 'Parts' element.

The composition provider from this point on is configuration-free. You can press F5 and watch your site run — so far nothing obvious should have changed, but under the covers, ASP.NET will be using MEF to construct the controllers used to handle requests! Before you can take advantage of this, you'll need to create some parts.

Creating a part

ASP.NET MVC already defines some important roles for application classes – there are controllers, models, views, filters and so-on. There are many other roles that make up a complete application; classes for:

  • Accessing data
  • Performing calculations
  • Communicating with other sites and web services
  • The secret sauce that makes your application special!

When using the composition provider for ASP.NET MVC, these classes can be implemented as parts.

The Parts folder

By convention, classes created in the Parts namespace are assumed to be parts for managed composition. Create a regular class by right-clicking on the Parts folder and selecting “Add New” → “Class”. Call the class “TraceLogger”.

     public class TraceLogger
    {
        public void Write(string text)
        {
            System.Diagnostics.Trace.WriteLine(text);
        }
    }

Exporting contracts

The goal of managed composition is to decouple the parts in an application, usually through the use of interfaces as ‘contracts’ that a part provides to other parts.

In the Parts folder, create a new interface called “ILogger” and declare a “Write” method on it:

     public interface ILogger
    {
        void Write(string text);
    }

Now go back to the TraceLogger part and implement the ILogger interface:

     public class TraceLogger : ILogger { … }

By convention, the composition provider assumes that any interfaces implemented by a part are the exports that it provides to other parts in the system.

In the included sample application, you can find a number of parts under its Parts folder. The simplest example is TraceLogger, under Parts/Tracing. The FabrikamBooksDbContext part, under Parts/Data, exports the IDbContext contract. The constructor of this part imports an ILogger, showing how parts can consume the contracts provided by other parts.

Using a part from a controller

If your ASP.NET MVC application was created from the default template, you will have a controller class called “HomeController” in the application’s Controllers folder.

Controllers are the primary place to consume parts – by adding a constructor to HomeController that accepts an ILogger, the HomeController can import the ILogger contract supplied by the TraceLogger part:

     public class HomeController : Controller
    {
        ILogger _logger;

        public HomeController(ILogger logger)
        {
            _logger = logger;
        }
        
        public ActionResult Index()
        {
            _logger.Write("Executing the Index() action method.");
            return View();
        }
    }

Whenever the HomeController.Index() action is executed, the message from the _logger.Write() call will be written to the application’s debug output window (try it!).

In the same way that HomeController can import the ILogger contract by declaring a constructor parameter, other parts can import instances of each other. This is how, from small pieces, the managed composition system can build up complex applications to do interesting things.

In the sample application, HomeController can be found under the Controllers folder.

Property imports

Although the composition provider will automatically supply constructor parameters to controllers and other parts, it will not automatically set properties, even when those properties are of a type supplied by another part. To mark a property as being an import, apply the MEF Import attribute to the property declaration.

The composition scopes

Scope or 'lifetime' determines how long a part created by the composition provider will live, and how it will be shared between other parts.

Request scope

When instances of parts are created during the processing of a web request, those parts will be shared within the scope of that web request.

So, if CatalogController requires both a ProductRepository part and a CategoryRepository part, and each repository requires a DataContext, then the same DataContext part instance will be shared by both repositories within a single web request. Within different web requests, different part instances will be created and shared.

Once processing of a request completes, the parts that were created during the web request are no longer required. If the parts implement IDisposable, their Dispose() methods will be called automatically. This is a great way to make sure that resources like database connections, files and service proxies are always released correctly.

In the Fabrikam Books sample, FabrikamDbContext is an example of a request-scoped part. It can be found under Parts/Data.

The application scope

Occasionally, creating a part instance per-request is not the desired behavior. For example, a part implementing an in-memory cache of category names might be created once and then shared by all parts in all requests served by a web application.

To mark a part as being shared at the application level, apply the ApplicationShared attribute:

     [ApplicationShared]
    public class CategoryCache : ICache<Category> { … }

Application-shared parts can only depend on contracts supplied by other application-shared parts.

Be careful when creating such parts, they will need to be thread-safe.

In the sample, TraceLogger is implemented as an application-shared part. It can be found under Parts/Tracing.

Non-shared parts, contract names and wiring

MEF comes with many additional options for wiring parts together. By applying attributes to part types, the defaults for sharing and contract matching can be overridden.

Adding parts from additional assemblies

Sometimes, perhaps because code needs to be shared between multiple applications, a web application will need to be implemented with more than one assembly.

To find parts in assemblies other than the main web application, first, the parts must be in a namespace that includes a segment ‘Parts’ such as “MyApp.OtherAssembly.Parts” or even “MyApp.Parts.OtherAssembly”.

Then, to ensure it is found by the composition provider, the assembly must be explicitly added during application startup in Global.asax.

     protected void Application_Start()
    {
        Assembly otherAssembly = ...;
        CompositionProvider.AddPartsAssembly(otherAssembly);
    }

Having to use a Parts namespace for all parts may seem a little excessive, but it is a good reminder that not every class should be a part. Parts are the ‘coarse-grained’ chunks that make up an application. Assemblies that add non-trivial functionality will almost always have a large number of additional supporting classes that the parts in the assembly use, but that are not parts themselves.

Action filter attributes

Action filter attributes can be applied to ASP.NET MVC controllers or action methods in order to change the way the action is executed. Action filter attributes are not created by the composition provider, but can be provided with contracts via properties marked with the Import attribute.

     public class SaveChangesAttribute : ActionFilterAttribute
    {
        [Import]
        public IDbContext DbContext { get; set; }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            if (filterContext.Exception == null)
                DbContext.SaveChanges();
        }
    }

The sample includes Parts/Data/SaveChangesAttribute.cs. This is applied to methods on Controllers/HomeController.cs.

Model binders

The final place in ASP.NET MVC where the composition provider can be used is to create model binders. These parts transform HTTP request information into strongly-typed objects that can be used in action methods and elsewhere.

Model binders are regular parts and appear under the Parts folder, but must also be marked with the ModelBinderExport attribute so that the binder is associated with the type of model that it can bind.

     [ModelBinderExport(typeof(Book))]
    public class BookModelBinder : IModelBinder { … }

A full model binder example is given in the Fabrikam Books sample, under Parts/Binding/BookModelBinder.cs.

Summary

The composition provider for ASP.NET MVC is a great step towards making your ASP.NET MVC applications more flexible, testable and maintainable.

Using managed composition to construct parts at runtime is only a small part of the picture. The real value in this technique is that it promotes good design by separating the design of contracts from the parts that implement them.

Some good sources for discussion of these concepts are:

It is our hope that the combination of MEF and ASP.NET MVC 3 makes for an enjoyable and rewarding development experience. We'd love to hear your feedback and suggestions!

Download MEF 2 Preview 4 from the CodePlex site.

Comments

  • Anonymous
    November 11, 2011
    I remember being told not to think of MEF as an IoC container, but I guess that was v1 since this sure looks like IoC to me?

  • Anonymous
    November 11, 2011
    James, that was definitely the case with MEF1, which was 100% focused on third-party extensibility. We realised post v1 that in using MEF for extensibility most apps were using it for internal composition as well, so we hope to make that a much smoother experience. This increases our overlap with other general IoC containers, but there is a wide spectrum of functionality in that space so really MEF is MEF :) Whether you use MEF or another framework for composition depends on which particular aspects are important in your application. Thanks for checking out the new version! Nick

  • Anonymous
    November 27, 2011
    I like how it works without having to declare abstract interfaces for each part. Quite often I will have one and only one implementation, so maintaining a separate abstract interface is nothing but a waste of time.

  • Anonymous
    November 28, 2011
    Thanks for the feedback Jonathan.

  • Anonymous
    December 21, 2011
    Great job guys. This is awesome stuff. The convention based composition is great. Thanks for your hard work on this!