Search code examples
c#asp.net-mvc-4mef

CompositionContractMismatchException when trying to use MEF with MVC controller


I'm working on a bigger C# MVC 4 project which is divided in several assemblies (Core, Domain, Backend MVC, Frontend MVC etc.). I use the plugin architecture provided by MEF to load and resolve most dependencies. Now I also wanted it to use to load MVC controller. Typical scenario found in dozens of samples.

But I keep getting this YSOD:

The exception says:

[CompositionContractMismatchException: Cannot cast the underlying exported value of type "XY.HomeController (ContractName="XY.HomeController")" to type "XY.HomeController".]
System.ComponentModel.Composition.ExportServices.CastExportedValue(ICompositionElement element, Object exportedValue) +505573
System.ComponentModel.Composition.<>c__DisplayClass10`2.<CreateSemiStronglyTypedLazy>b__c() +62
System.Lazy`1.CreateValue() +14439352
System.Lazy`1.LazyInitValue() +91
  XY.DependencyManagement.SomeCustomControllerFactory.GetControllerInstance(RequestContext requestContext, Type controllerType) in (Path)\Core\DependencyManagement\SomeCustomControllerFactory.cs:32
System.Web.Mvc.DefaultControllerFactory.CreateController(RequestContext requestContext, String controllerName) +89
System.Web.Mvc.MvcHandler.ProcessRequestInit(HttpContextBase httpContext, IController& controller, IControllerFactory& factory) +305
System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +87
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +12550291
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +288

The custom ControllerFactory:

 public class SomeCustomControllerFactory : DefaultControllerFactory {

    private readonly CompositionContainer _compositionContainer;

    public SomeCustomControllerFactory (CompositionContainer compositionContainer) {
        _compositionContainer = compositionContainer;
    }

    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) {
        var export = _compositionContainer.GetExports(controllerType, null, null).SingleOrDefault();

        IController result;

        if (export != null) {
            result = export.Value as IController;
        } else {
            result = base.GetControllerInstance(requestContext, controllerType);
            _compositionContainer.ComposeParts(result);
        }

        return result;
    }

 protected override Type GetControllerType(RequestContext requestContext, string controllerName) {

        Type controllerType = base.GetControllerType(requestContext, controllerName);

 // used to find objects in the container which assemblies are in a sub directory and not discovered by MVC
 // TODO: only create parts that are used
        if (controllerType == null && this._compositionContainer != null &&
            this._compositionContainer != null) {

            var controllerTypes =
                this._compositionContainer.GetExports<Controller, IDictionary<string, object>>()
                    .Where(
                        e =>
                        e.Value.GetType().Name.ToLowerInvariant() ==
                        controllerName.ToLowerInvariant() + ControllerNameByConvention)
                    .Select(e => e.Value.GetType()).ToList();

            switch (controllerTypes.Count) {
                case 0:
                    controllerType = null;
                    break;
                case 1:
                    controllerType = controllerTypes.First();
                    break;
                case 2:
                    throw CreateAmbiguousControllerException(requestContext.RouteData.Route, controllerName,
                                                             controllerTypes);
            }
        }

        return controllerType;
    }

And a CustomDependencyResolver:

 public class CustomDependencyResolver : IDependencyResolver {

    private readonly CompositionContainer _container;
    public CustomDependencyResolver(CompositionContainer container) {
        _container = container;
 }
    public IDependencyScope BeginScope() {
        return (IDependencyScope)this;
    }

    public object GetService(Type serviceType) {
        var export = _container.GetExports(serviceType, null, null).SingleOrDefault();

        return null != export ? export.Value : null;
    }

    public IEnumerable<object> GetServices(Type serviceType) {
        var exports = _container.GetExports(serviceType, null, null);
        var createdObjects = new List<object>();

        if (exports.Any()) {
            foreach (var export in exports) {
                createdObjects.Add(export.Value);
            }
        }

        return createdObjects;
    }

Everything is configured this way DependencyResolver.SetResolver(new CustomDependencyResolver(container)); ControllerBuilder.Current.SetControllerFactory(new SomeCustomControllerFactory(container));

Side note: MEF2 RegistrationBuilder and an AggregateCatalog with three AssemblyCatalogs and one DirectoryCatalog are used.

The whole thing works perfectly if I extract it from the main project solution and create a new mvc 4 internet project solution and integrate it there. (Tested it with one assembly, with a second simple core lib.)

I already switched on CompositionOptions.DisableSilentRejection. And found this resource to debug MEF related errors https://blogs.msdn.com/b/dsplaisted/archive/2010/07/13/how-to-debug-and-diagnose-mef-failures.aspx?Redirected=true I removed everything in the HomeController (Empty constructor, no imports etc.). The MEF container is filled with suitable exports. Everything fine.

After a whole day of debugging and research I learned a lot about MEF but still having the same problem. Hopefully somebody can give me a hint wants wrong here. Cause moving everything to a new MVC project would be very very time-consuming :-(

Thanks!


Solution

  • This looks similar to the System.InvalidCastException that is sometimes thrown when the same assembly is loaded twice in different contexes or from different locations. All assembly loading in MEF is being handled by the AssemblyCatalog class using the Assembly.Load(AssemblyName) method which will load the assembly with the given name in the Load context. However, under certain conditions, it will load it in the LoadFrom context and this can sometimes cause casting exceptions such as the one you mention:

    Cannot cast the underlying exported value of type "XY.HomeController(ContractName="XY.HomeController")" to type "XY.HomeController".]

    What I would do is look if the assembly that contains XY.HomeController is deployed in more than one locations. Don't forget to look in the GAC if it is a strong named assembly.

    A similar problem is mentioned in Telerik's forum.

    If you want more details on this subject have a look at "How the Runtime Locates Assemblies" "Best Practices for Assembly Loading" as well as the Load-related entries in Suzanne Cooks MSDN blog.