tags:

views:

205

answers:

4

I want to use MEF with asp.net mvc. I wrote following controller factory:

public class MefControllerFactory : DefaultControllerFactory
{
    private CompositionContainer _Container;

    public MefControllerFactory(Assembly assembly)
    {
        _Container = new CompositionContainer(new AssemblyCatalog(assembly));
    }



    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType != null)
        {
            var controllers = _Container.GetExports<IController>();

            var controllerExport = controllers.Where(x => x.Value.GetType() == controllerType).FirstOrDefault();

            if (controllerExport == null)
            {
                return base.GetControllerInstance(requestContext, controllerType);
            }

            return controllerExport.Value;
        }
        else
        {
            throw new HttpException((Int32)HttpStatusCode.NotFound,
                String.Format(
                    "The controller for path '{0}' could not be found or it does not implement IController.",
                    requestContext.HttpContext.Request.Path
                )
            );
        }
    }
}

In Global.asax.cs I'm setting my controller factory:

protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        RegisterRoutes(RouteTable.Routes);

        ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory.MefControllerFactory(Assembly.GetExecutingAssembly()));
    }

I have an area:

[Export(typeof(IController))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class HomeController : Controller
{
    private readonly IArticleService _articleService;

    [ImportingConstructor]
    public HomeController(IArticleService articleService)
    {
        _articleService = articleService;
    }

    //
    // GET: /Articles/Home/

    public ActionResult Index()
    {
        Article article = _articleService.GetById(55);

        return View(article);
    }

}

IArticleService is an interface.

There is a class which implements IArticleService and Exports it.

It works.

Is this everything what I need for working with MEF?

How can I skip setting PartCreationPolicy and ImportingConstructor for controller?

I want to set my dependencies using constructor.

When PartCreationPolicy is missing, I get following exception:

A single instance of controller 'MvcApplication4.Areas.Articles.Controllers.HomeController' cannot be used to handle multiple requests. If a custom controller factory is in use, make sure that it creates a new instance of the controller for each request.

A: 

I've been working with MEF/MVC a lot recently and have been blogging about my revised MEF+MVC design. I'm hoping to push it onto CodePlex soon, but for now, see if any of this helps you out:

  1. Modular ASP.NET MVC using the Managed Extensibility Framework (MEF), Part One
  2. Modular ASP.NET MVC using the Managed Extensibility Framework (MEF), Part Two
Matthew Abbott
Please keep in mind that MEF in asp.net MVC is NOT very friendly in partial trust environments. For example, the nerd dinner MEF sample will blow up spectacularly in any partial trust scenario. Basically, in partial trust you are very limited in how you use MEF as several of the internals of MEF are not accessible from partially trusted code.
Stephen M. Redd
I decided to use Unity instead of MEF.
denis_n
A: 

Your technique here is pretty solid, and will work even in partial trust. The nerd dinner MEF example has extensions that will allow you to deal with discovering controllers by convention and making them into MEF exports automatically, without flagging them with MEF attributes. But managing part catalogs directly doesn't work in partial trust, so the nerd dinner MEF techniques don't work in partial trust.

If you are working in full trust, and want convention based discovery with your controllers start with the Nerd Dinner MEF example, but you should probably also read about a couple major issues with the nerd dinner MEF example that will come up if your own application's model is in a separate class a library project. I blogged about those cases and suggested some fixes.

If you aren't all that interested in the convention based discovery stuff, then nerd dinner sample is a tad over-engineered. Your solution is probably fine as is... and works in partial trust too, which is always a bonus.

[update] I did spot one potential problem with your technique:

var controllerExport = controllers.Where(x => x.Value.GetType() == 
controllerType).FirstOrDefault();

In the where clause here, you are calling .Value on each export in the collection of parts... that is actually going to cause each of those exports to be composed and instantiated in order to be evaluated. That could be a nasty performance issue.

You might consider decorating your controllers with named export contracts like this:

[Export("Home", typeof(IController))]

Then using a controler factory like this instead:

public class MefControllerFactory: IControllerFactory
{
    private CompositionContainer _Container;

    public MefControllerFactory(Assembly assembly)
    {
        _Container = new CompositionContainer(new AssemblyCatalog(assembly));
    }

    #region IControllerFactory Members

    public IController CreateController(RequestContext requestContext, string controllerName)
    {

        var controller = _Container.GetExportedValue<IController>(controllerName);

        if (controller == null)
        {
            throw new HttpException(404, "Not found");
        }

        return controller;
    }

    public void ReleaseController(IController controller)
    {
       // nothing to do
    }

    #endregion
}
Stephen M. Redd
A: 

Thanks.

I did spot one potential problem with your technique:

var controllerExport = controllers.Where(x => x.Value.GetType() == controllerType).FirstOrDefault();

Yes, it is true.

After reading this (http://codepaste.net/yadusn) I understood how NerdDinner with MEF was done.

I used the conventional catalog for MEF and created my MEFed controller factory (without Export attribute on controllers).

 public static IController GetController(CompositionContainer container, Type controllerType)
    {
        var controllers = container.GetExports<IController, IDictionary<string, object>>();

        if (controllers == null) return null;

        var controllerExport = controllers
            .Where(exp => ExportMetadataContainsGuid(exp.Metadata, controllerType.GUID))
            .FirstOrDefault();

        return (controllerExport == null) ? null : controllerExport.Value;
    }

ExportMetadataContainsGuid method:

public static bool ExportMetadataContainsGuid(IDictionary<string, object> metaData, Guid guid)
    {
        return metaData.ContainsKey(MetadataGuidKey) && guid == (Guid)metaData[MetadataGuidKey];
    }

I use metadata to store type's GUID and use it to find right controller.

denis_n
A: 

I decided to return to Unity.

I've create a custom attribute instead of MEF's ExportAttribute

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class ImplementsAttribute : Attribute
{
    public ImplementsAttribute(Type contractType)
    {
        ContractType = contractType;
    }

    public Type ContractType
    {
        get; 
        private set;
    }
}

Example:

[Implements(typeof(ICustomerEmailService))]
public class CustomerEmailService : ICustomerEmailService
{...}

And a custom controller factory:

public class MyControllerFactory : DefaultControllerFactory
{
    private readonly IUnityContainer _container;

    public MyControllerFactory()
    {
        _container = new UnityContainer();

        Func<Type, bool> isController =
            (type) => typeof(IController).IsAssignableFrom(type)
                    && (type.IsAbstract || type.IsInterface 
                        || type.GetCustomAttributes(typeof(GeneratedCodeAttribute), true).Any()) != true;



        foreach (Assembly assembly in BuildManager.GetReferencedAssemblies())
        {
            try
            {
                var types = assembly.GetTypes();

                // Also register all controllers
                var controllerTypes = from t in types where isController(t) 
                                        select t;


                foreach (Type t in controllerTypes)
                {
                    _container.RegisterType(t);
                }


                // register all providers
                var providers =
                    from t in types
                    from export in t.GetCustomAttributes(typeof(ImplementsAttribute), true).OfType<ImplementsAttribute>()
                    select new { export.ContractType, Provider = t };

                foreach (var item in providers)
                {
                    if (item.ContractType != null)
                    {
                        _container.RegisterType(item.ContractType, item.Provider);
                    }
                    else
                    {
                        _container.RegisterType(item.Provider);
                    }
                }
            }
            catch 
            {
            }
        }
    }



    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
    {
        if (controllerType != null)
        {
            var controller = _container.Resolve(controllerType) as IController;

            if (controller == null)
            {
                return base.GetControllerInstance(requestContext, controllerType);
            }

            return controller;
        }


        throw new HttpException((Int32)HttpStatusCode.NotFound,
            String.Format(
                "The controller for path '{0}' could not be found or it does not implement IController.",
                requestContext.HttpContext.Request.Path)
        );
    }
}

It was too difficult for me to fix all problems for MEF's controller factory :(

denis_n