2012-11-26 13 views
27

Come si può integrare Managed Extensibility Framework (MEF) con ASP.NET MVC 4 e API Web ASP.NET nello stesso progetto?Come integrare MEF con ASP.NET MVC 4 e API Web ASP.NET

Considerare un'applicazione di esempio, con un controller MVC HomeController e un controller API Web ContactController. Entrambi hanno una proprietà di tipo IContactRepository, che si affidano a MEF per risolvere. Il problema è come collegare MEF a MVC e Web API, in modo che le istanze vengano create tramite MEF.

HomeController:

/// <summary> 
/// Home controller. Instruct MEF to create one instance of this class per importer, 
/// since this is what MVC expects. 
/// </summary> 
[Export] 
[PartCreationPolicy(CreationPolicy.NonShared)] 
public class HomeController : Controller 
{ 
    [Import] 
    private IContactRepository _contactRepository = null; 

    public ActionResult Index() 
    { 
     return View(_contactRepository.GetAllContacts()); 
    } 
} 

ContactController:

/// <summary> 
/// Contact API controller. Instruct MEF to create one instance of this class per importer, 
/// since this is what Web API expects. 
/// </summary> 
[Export] 
[PartCreationPolicy(CreationPolicy.NonShared)] 
public class ContactController : ApiController 
{ 
    [Import] 
    private IContactRepository _contactRepo = null; 

    public Contact[] Get() 
    { 
     return _contactRepo.GetAllContacts(); 
    } 
} 

IContactRepository e ContactRepository:

public interface IContactRepository 
{ 
    Contact[] GetAllContacts(); 
} 

[Export(typeof(IContactRepository))] 
public class ContactRepository : IContactRepository 
{ 
    public Contact[] GetAllContacts() 
    { 
     return new Contact[] { 
      new Contact { Id = 1, Name = "Glenn Beck"}, 
      new Contact { Id = 2, Name = "Bill O'Riley"} 
     }; 
    } 
} 

Contatto:

public class Contact 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
} 

risposta

28

La soluzione è quella di implementare System.Web.Mvc.IDependencyResolver e System.Web.Http.Dependencies.IDependencyResolver e registrare l'implementazione con ASP.NET MVC e ASP.NET Web API, rispettivamente, nel metodo Application_Start.

In questo esempio creeremo una classe MefConfig, che implementa un metodo RegisterMef che viene chiamato da Application_Start per installare il nostro risolutore di dipendenze. La classe MefDependencyResolver implementa sia System.Web.Mvc.IDependencyResolver e System.Web.Http.Dependencies.IDependencyResolver e, come tale, gestisce i compiti di risoluzione delle dipendenze sia per MVC che per le API Web.

Application_Start, Dieci in Global.asax.cs:

public class WebApiApplication : System.Web.HttpApplication 
{ 
    protected void Application_Start() 
    { 
     [...] 
     MefConfig.RegisterMef(); 
    } 
} 

MefDependencyResolver e MefConfig:

/// <summary> 
/// Resolve dependencies for MVC/Web API using MEF. 
/// </summary> 
public class MefDependencyResolver : System.Web.Http.Dependencies.IDependencyResolver, System.Web.Mvc.IDependencyResolver 
{ 
    private readonly CompositionContainer _container; 

    public MefDependencyResolver(CompositionContainer container) 
    { 
     _container = container; 
    } 

    public IDependencyScope BeginScope() 
    { 
     return this; 
    } 

    /// <summary> 
    /// Called to request a service implementation. 
    /// 
    /// Here we call upon MEF to instantiate implementations of dependencies. 
    /// </summary> 
    /// <param name="serviceType">Type of service requested.</param> 
    /// <returns>Service implementation or null.</returns> 
    public object GetService(Type serviceType) 
    { 
     if (serviceType == null) 
      throw new ArgumentNullException("serviceType"); 

     var name = AttributedModelServices.GetContractName(serviceType); 
     var export = _container.GetExportedValueOrDefault<object>(name); 
     return export; 
    } 

    /// <summary> 
    /// Called to request service implementations. 
    /// 
    /// Here we call upon MEF to instantiate implementations of dependencies. 
    /// </summary> 
    /// <param name="serviceType">Type of service requested.</param> 
    /// <returns>Service implementations.</returns> 
    public IEnumerable<object> GetServices(Type serviceType) 
    { 
     if (serviceType == null) 
      throw new ArgumentNullException("serviceType"); 

     var exports = _container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType)); 
     return exports; 
    } 

    public void Dispose() 
    { 
    } 
} 

public static class MefConfig 
{ 
    public static void RegisterMef() 
    { 
     var asmCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); 
     var container = new CompositionContainer(asmCatalog); 
     var resolver = new MefDependencyResolver(container); 
     // Install MEF dependency resolver for MVC 
     DependencyResolver.SetResolver(resolver); 
     // Install MEF dependency resolver for Web API 
     System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = resolver; 
    } 
} 
+1

funziona davvero, grazie. Per me ho appena cambiato un po 'il catalogo: var catalog = new AggregateCatalog(); catalog.Catalogs.Add (new DirectoryCatalog (@ ".. \ EducationBusinessLogic \ bin \ Debug", "*")); catalog.Catalogs.Add (nuovo AssemblyCatalog (Assembly.GetExecutingAssembly())); –

+1

Sfortunatamente, la soluzione suggerita porta a perdite di memoria quando viene utilizzata per tipi di servizi 'IDisposable' non condivisi come' ApiController's a causa di http://stackoverflow.com/questions/8787982/mef-keeps-reference-of-nonshared -idisposable-parti-non-permettere-li-to-be-coll. Mi chiedo come la gente aggiri questo. – Dejan

+0

FWIW, ho trovato che [Import] s non sono risolti nella classe ApiController a meno che la classe ApiController non venga esportata con un attributo [Export] sopra la dichiarazione della classe. – ThisIsTheDave

1

Si può dare un'occhiata a questo http://kennytordeur.blogspot.be/2012/08/mef-in-aspnet-mvc-4-and-webapi.html. Spiega come utilizzare MEF in un progetto ASP.net MVC 4/Web Api. Esiste anche un Nuget package basato su questo codice. In questo modo puoi testarlo molto facilmente e rapidamente.

+3

Ho guardato il tuo post sul blog prima di dare una risposta. Penso che l'uso di DefaultControllerFactory con MVC sia ormai una tecnica obsoleta, e uno [dovrebbe implementare IDependencyResolver invece] (http://weblogs.asp.net/shijuvarghese/archive/2011/01/21/dependency-injection-in- asp-net-mvc-3-using-dependencyresolver-e-controlleractivator.aspx). Inoltre, è meglio evitare solo il collegamento a una soluzione esterna in una risposta SO. – aknuds1

+1

Per una buona ragione, il blog è sparito. –

0

La soluzione di Mr Kenny Torduer ha funzionato per me mentre la presunta risposta corretta non ha (non è stato possibile risolvere l'istanza del controller sebbene tutte le parti dipendenti siano nel catelog, mi è stato restituito un errore "tipo non ha un costruttore predefinito") !

Correzione: entrambi gli approcci funzionano effettivamente, ero stupido da un errore elementare nel registro delle parti della convenzione. Le mie sincere scuse all'autore della risposta giusta.

+1

Nessun problema :) Entrambi gli approcci dovrebbero funzionare, anche se uno dovrebbe essere più aggiornato. – aknuds1

+0

@ aknuds1 Ricevo lo stesso problema "tipo non ha un costruttore predefinito" e per la vita di me non è possibile eseguirne il debug. Qual è stata la causa del problema? – Lloyd

+0

@Lloyd Non sono io che ho avuto questo problema, ma binjiezhao. Sospetto che la ragione per cui stai vedendo questo è che MEF non è configurato correttamente, in modo tale che un parametro costruttore non possa essere risolto tramite MEF (facendo in modo che MVC cerchi invece un costruttore predefinito). – aknuds1

0

Questo è un approccio più semplice che sto utilizzando nel mio progetto MVC4.

public static class MefConfig 
{ 
    public static CompositionContainer MefContainer = null; 

    public static void Initialise() 
    { 
      AggregateCatalog cat = new AggregateCatalog(); 
      cat.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly())); 
      MefContainer = new CompositionContainer(cat); 
     } 
} 

public class MefFilterAttribute : ActionFilterAttribute 
{ 
    public override void OnActionExecuting(ActionExecutingContext filterContext) 
    { 
     MefConfig.MefContainer.ComposeParts(filterContext.Controller); 
    }   
} 

In Application_Start eseguire MefConfig.Initialise() e in FilterConfig.RegisterGlobalFilters (filtri GlobalFilterCollection) inserisce i filtri.Add (new Filters.MefFilterAttribute());

+0

Funziona solo per la parte MVC, ma non per l'API Web. – Dejan

+0

no, non funziona affatto –

1

@ aknuds1 risposta è la migliore che ho visto finora per l'integrazione di MEF in DependencyResolver. Sono stato in grado di estenderlo abbastanza facilmente per usare la composizione basata sulla convenzione in MEF2. La classe MefConfig è tutto ciò che è necessario cambiare e quindi non di molto.

/// <summary> 
///  Responsible for configuring MEF for the application. 
/// </summary> 
public static class MefConfig 
{ 
    /// <summary> 
    ///  Registers MEF conventions and exports. 
    /// </summary> 
    public static void RegisterMef() 
    { 
     // Register MVC/API conventions 
     var registrationBuilder = new RegistrationBuilder(); 
     registrationBuilder.ForTypesDerivedFrom<Controller>().SetCreationPolicy(CreationPolicy.NonShared).Export(); 
     registrationBuilder.ForTypesDerivedFrom<ApiController>().SetCreationPolicy(CreationPolicy.NonShared).Export(); 
     var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly(), registrationBuilder); 
     var aggregateCatalog = new AggregateCatalog(assemblyCatalog); 
     var container = new CompositionContainer(aggregateCatalog); 
     var resolver = new MefDependencyResolver(container); 
     // Install MEF dependency resolver for MVC 
     DependencyResolver.SetResolver(resolver); 
     // Install MEF dependency resolver for Web API 
     GlobalConfiguration.Configuration.DependencyResolver = resolver; 
    } 
} 
+0

Mi sento in dovere di commentare, dal momento che qualcuno ha votato la mia risposta qui che c'è un difetto ed è che otterrete perdite di memoria e di connessione usando la risposta accettata. Ho pubblicato un'altra domanda relativa a questo, che ancora non ci sono risposte: http://stackoverflow.com/questions/20806239/how-to-properly-scope-composition-per-request-using-asp-net- MVC-WebAPI-e-MEF. Ho scritto la mia soluzione che presto aggiungerò alla mia domanda, specialmente se nessuno offre un'altra soluzione. –

0

ho seguito la risposta di @ akanuds1 ma ho anche dovuto cambiare il ControllerFactory a questo:

public class MefControllerFactory : DefaultControllerFactory 
{ 
    private readonly CompositionContainer compositionContainer; 

    public MefControllerFactory(CompositionContainer compositionContainer) 
    { 
     this.compositionContainer = compositionContainer; 
    } 

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

     IController result; 

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

     return result; 
    } 
} 

Glogal.asax.cs

protected void Application_Start() 
{ 
    ... 
    var container = MefConfig.Register(); 
    ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(container)); 
}