1

I'm having a difficult time wrapping my head around Mef and how imports and exports work. My project structure is as follows.

Projects:
MefMVPApp (Main MVC 4 app)
MefMVCFramework.Common(Interfaces shared between the projects)
MefMVCDemo.Plugins.OrderStatus (pluggable area.)
MefMVCDemo.Plugins.Data (Repository for OrderStatus)
OrderStatus.Models(domain models shared between the projects)

The goal of the main Mvc App will be to host plug-able areas via mef.

The OrderStatus Area has a controller called OrderStatusController and is decorated with the Export Attribute and a ImportingConstructor.

[Export(typeof(IController))]
        [ExportMetadata("controllerName", "OrderStatus")]
        [PartCreationPolicy(CreationPolicy.NonShared)] 
        public class OrderStatusController : Controller
        {
            private readonly IRepository<OrderStatusApp.OrderStatusResponse>_repository ;
            [ImportingConstructor]
            public OrderStatusController(IRepository<OrderStatusApp.OrderStatusResponse> oRepository)
            {
                _repository = oRepository;
            }
            public ActionResult Index()
            {
                var model = _repository.GetAll();
                return View();
            } 
    }

IRepository is a class in the MefMVCFramework.Common assembly and will be used for generic CRUD operations.

public interface IRepository<T> where T : class
            {
               IEnumerable<T> GetAll();
               T GetById(int id);
               void Add(T entity);
               int SaveOrUpdate(T entity);
                bool Delete(T entity);
                bool Delete(int id); 
            }

The MefMVCDemo.Plugins.Data assembly contains a Class called OrderManagementRepository that inherents for the generic repository and is marked with an Export Attribute.

[Export(typeof(IRepository<OrderStatusApp.OrderStatusResponse>))]
            [PartCreationPolicy(CreationPolicy.NonShared)] 
                public class OrderManagementRepository  : IRepository<OrderStatusApp.OrderStatusResponse>
                {
                    private readonly JsonServiceClient _client;

                    public OrderManagementRepository()
                    {
                        _client = new JsonServiceClient("http://localhost:52266");
                    }
                    public IEnumerable<OrderStatusApp.OrderStatusResponse> GetAll()
                    {

                        throw new NotImplementedException("Can not get all");
                    }
                    public OrderStatusApp.OrderStatusResponse GetById(int id)
                    {
                        throw new NotImplementedException();
                    }
                    public void Add(OrderStatusApp.OrderStatusResponse entity)
                    {
                        throw new NotImplementedException();
                    }
                    public int SaveOrUpdate(OrderStatusApp.OrderStatusResponse entity)
                    {
                        throw new NotImplementedException();
                    }
                    public bool Delete(OrderStatusApp.OrderStatusResponse entity)
                    {
                        throw new NotImplementedException();
                    }
                    public bool Delete(int id)
                    {
                        throw new NotImplementedException();
                    }
                } 

Using Mefx tool I am able to see my parts and there are no rejection.

mefx /dir:C:\
Source.PreBranch.Keep\Prototypes\Projects\MefDemoApp\mefMVC4App\bin /parts
MefMVCDemo.Plugins.Data.OrderManagementRepository
mefMVCDemo.Plugins.OrderStatus.Controllers.OrderStatusController
MefMVCDemo.Plugins.OrderStatus.Verbs.OrderStatusVerb

I can see my import.

mefx /dir:C:\
Source.PreBranch.Keep\Prototypes\Projects\MefDemoApp\mefMVC4App\bin /imports
MefMVCFramework.Common.IRepository(OrderStatus.Models.OrderStatusApp+OrderStatus
Response)
MefMVCFramework.Common.IRepository(OrderStatus.Models.OrderStatusApp+OrderStatus
Response)

Now when browse my main mvc site with the /orderstatus uri I get the following error: No parameterless constructor defined for this object.

Adding a default constructor to the OrderStatusController that takes no overloads doesn't seem to work.

I guess the question is what am I doing wrong? Why does my interface in the constructor all way end up being null and why is there an mvc error about the "No parameterless constructor defined for this object".

Kiquenet
  • 14,494
  • 35
  • 148
  • 243
KC.
  • 87
  • 12

2 Answers2

1

The default controller factory in MVC tries to create the controller using a parameterless constructor. If you want to change this behavior, then you need to create your own custom controller factory.

Here is an example of a ControllerFactory using imports/exports on controllers

I'm using MEF for importing some parts to my app but my controllers are not imported/exported, so I created the following controller factory

public class ControllerFactory : IControllerFactory
{
    private readonly CompositionContainer _container;
    private IControllerFactory _innerFactory;

/// <summary>
/// Constructor used to create the factory
/// </summary>
/// <param name="container">MEF Container that will be used for importing</param>
public ControllerFactory(CompositionContainer container)
{
    _container = container;
    _innerFactory = new DefaultControllerFactory();
}

/// <summary>
/// Method used for create the controller based on the provided name. It calls the
/// constructor of the controller passing the MEF container
/// </summary>
/// <param name="requestContext">Context of the request</param>
/// <param name="controllerName">Name of the controller provided in the route</param>
/// <returns>The controller instance</returns>
public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
    Type controllerType = FindControllerByName(controllerName);

    var args = new object[] { this._container };
    var controller = (IController)Activator.CreateInstance(controllerType, args);

    return controller;
}

/// <summary>
/// This methods looks into the current Assembly for the Controller type
/// </summary>
/// <param name="name">The controller name provided in the route</param>
/// <returns>The controller type</returns>
private static Type FindControllerByName(string name){
    var a = Assembly.GetAssembly(typeof(ControllerFactory));
    var types = a.GetTypes();
    Type type = types.Where(t => t.Name == String.Format("{0}Controller", name)).FirstOrDefault();              

    return type;
}


public System.Web.SessionState.SessionStateBehavior GetControllerSessionBehavior(System.Web.Routing.RequestContext requestContext, string controllerName)
{
    return System.Web.SessionState.SessionStateBehavior.Default;
}

public void ReleaseController(IController controller)
{
    var disposableController = controller as IDisposable;
    if (disposableController != null)
    {
        disposableController.Dispose();
    }
}

}

pollirrata
  • 5,188
  • 2
  • 32
  • 50
  • Thanks for the reply. I created the controller factory using the code above(I called it MefControllerFactory), added the following line to my global.asax file. ControllerBuilder.Current.SetControllerFactory(typeof(MefControllerFactory)); It throws this error when I load the site: An error occurred when trying to create the IControllerFactory 'MefMVCApp.MefControllerFactory'. Make sure that the controller factory has a public parameterless constructor.] – KC. Apr 10 '13 at 00:04
  • Does it have a public parameterless constructor? – pollirrata Apr 10 '13 at 03:52
  • Thanks for the help. the controller factory was the piece I was missing. I modified the code you gave me just a little and now have it working. – KC. Apr 10 '13 at 18:23
1

Thank you pollirrata for pointing me in the right direction.

I had to change a few things to get this to work.

1.) I added an interface called INameMetadata to my MefMVCFramework.Common project.

public interface INameMetadata
{
    string Name { get; }
}

2.) Modified My ExportMetadata Tag on my controller export to be Name, OrderStatus.

 [Export(typeof(IController))]
[ExportMetadata("Name", "OrderStatus")]
[PartCreationPolicy(CreationPolicy.NonShared)] 
public class OrderStatusController : Controller
{
    private IRepository<OrderStatusApp.OrderStatusResponse> _repository;

    [ImportingConstructor]
    public OrderStatusController(IRepository<OrderStatusApp.OrderStatusResponse> oRepository)
    {
        _repository = oRepository;

    }

    public ActionResult Index()
    {
        var model = _repository.GetById(47985);
        return View(model);
    } 
}

3.) Created the MefControllerFactory (based off what pollirrata posted but modified to look for the Metadata)

 public class MefControllerFactory : IControllerFactory
{
    private string _pluginPath;
    private readonly DirectoryCatalog _catalog;
    private readonly CompositionContainer _container;
    private DefaultControllerFactory _defaultControllerFactory;

    public MefControllerFactory(string pluginPath)
    {
        _pluginPath = pluginPath;
        _catalog = new DirectoryCatalog(pluginPath);
        _container = new CompositionContainer(_catalog);
        _defaultControllerFactory = new DefaultControllerFactory();
    }

    public MefControllerFactory(CompositionContainer compositionContainer)
    {

        _container = compositionContainer;
        _defaultControllerFactory = new DefaultControllerFactory();
    }
    #region IControllerFactory Members
    public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
    {
        //IController controller = null;
        var controller = _container.GetExports<IController,INameMetadata>()
            .Where(e=>e.Metadata.Name.Equals(controllerName))
            .Select(e=>e.Value).FirstOrDefault();

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

        return controller;

    }
    public void ReleaseController(IController controller)
    {
       var disposable = controller as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }
    #endregion


    public SessionStateBehavior GetControllerSessionBehavior(System.Web.Routing.RequestContext requestContext, string controllerName)
    {
        return SessionStateBehavior.Default;
    }
}

4.) I created a Class called MefConfig in Main MVC app and moved it to the App_Start Dir.

public static class MefConfig
{
    public static void RegisterMef()
    {
        //var builder = new RegistrationBuilder();
        //builder.ForTypesDerivedFrom<IRepository<OrderStatusApp.OrderStatusResponse>>().Export<IRepository<IRepository<OrderStatusApp.OrderStatusResponse>>>();

        var directoryCatalog = new DirectoryCatalog(HostingEnvironment.MapPath("~/bin"), "*.dll");

        var container = new CompositionContainer(directoryCatalog, true);
        ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(container));

        //Working
        //ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(HostingEnvironment.MapPath("~/bin")));

        // Install MEF dependency resolver for MVC
        var resolver = new MefDependencyResolver(container);
        DependencyResolver.SetResolver(resolver);
        // Install MEF dependency resolver for Web API
        GlobalConfiguration.Configuration.DependencyResolver = resolver;
        var d = container.GetExportedValues<IRepository<OrderStatusApp.OrderStatusResponse>>();
        //Mefx.
        try
        {
            //var ci = new CompositionInfo(aggregateCatalog, container);
            var ci = new CompositionInfo(directoryCatalog, container);
            var partDef = ci.GetPartDefinitionInfo(typeof(IRepository<OrderStatusApp.OrderStatusResponse>));

            //var possibleCauses = partDef.FindPossibleRootCauses();
            var stringWriter = new StringWriter();
            CompositionInfoTextFormatter.Write(ci, stringWriter);
            var compStatString = stringWriter.ToString();
        }
        catch
        {

        }
        MvcApplication.ActionVerbs = container.GetExports<IActionVerb, IActionVerbMetadata>();
    }
}

5.) Load the Mefconfig from the global.asax.

 protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        //Register Mef 
        MefConfig.RegisterMef();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

    }
Kiquenet
  • 14,494
  • 35
  • 148
  • 243
KC.
  • 87
  • 12