Comment intégrer Managed Extensibility Framework (MEF) à ASP.NET MVC 4 et à l’API Web ASP.NET dans le même projet?
Prenons un exemple d'application, avec un contrôleur MVC HomeController
et un contrôleur API Web ContactController
. Les deux ont une propriété de type IContactRepository
, qu’ils s’appuient sur MEF pour résoudre. Le problème est de savoir comment connecter MEF aux API Web et MVC afin que les instances soient créées via 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 et 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"}
};
}
}
Contact:
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
}
La solution consiste à implémenter System.Web.Mvc.IDependencyResolver et System.Web.Http.Dependencies.IDependencyResolver et à enregistrer votre implémentation avec ASP.NET MVC et ASP.NET Web API respectivement dans votre Application_Start
méthode.
Dans cet exemple, nous allons créer une classe MefConfig
, qui implémente une méthode RegisterMef
qui est appelée à partir de Application_Start
afin d’installer notre résolveur de dépendances. La classe MefDependencyResolver
implémente à la fois System.Web.Mvc.IDependencyResolver
et System.Web.Http.Dependencies.IDependencyResolver
et, en tant que telle, gère les tâches de résolution des dépendances pour MVC et l'API Web.
Application_Start, mettez ceci dans votre Global.asax.cs:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
[...]
MefConfig.RegisterMef();
}
}
MefDependencyResolver and 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;
}
}
@ aknuds1 est le meilleur que j'ai vu jusqu'à présent pour intégrer MEF dans DependencyResolver. J'ai été en mesure d'étendre assez facilement l'utilisation de la composition basée sur les conventions dans MEF2. La classe MefConfig est tout ce qui était nécessaire pour changer et pas beaucoup.
/// <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;
}
}
Vous pouvez consulter cette page http://kennytordeur.blogspot.be/2012/08/mef-in-aspnet-mvc-4-and-webapi.html Il explique comment utiliser MEF dans un projet Asp.net MVC 4/Web Api. Il existe également un package Nuget basé sur ce code. De cette façon, vous pouvez le tester très facilement et rapidement.
La solution de @ aknuds1 fonctionne, mais laisse fuir de la mémoire sur chaque appel d'API. J'ai modifié sa solution pour réparer la fuite.
public class MefDependencyResolver : System.Web.Http.Dependencies.IDependencyResolver, System.Web.Mvc.IDependencyResolver
{
private readonly CompositionContainer container;
private readonly List<Lazy<object, object>> exports = new List<Lazy<object, object>>();
private readonly object syncRoot = new object();
public MefDependencyResolver(CompositionContainer container)
{
this.container = container;
}
public IDependencyScope BeginScope()
{
return new MefDependencyResolver(container);
}
/// <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(nameof(serviceType));
var serviceExport = container.GetExports(serviceType, null, null).FirstOrDefault();
if (serviceExport == null) return null;
lock (this.syncRoot)
{
exports.Add(serviceExport);
}
return serviceExport.Value;
}
/// <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(nameof(serviceType));
var serviceExports = container.GetExports(serviceType, null, null);
if (!serviceExports.Any()) return Enumerable.Empty<object>();
lock (this.syncRoot)
{
exports.AddRange(serviceExports);
}
return serviceExports.Select(x => x.Value);
}
public void Dispose()
{
lock (this.syncRoot)
{
foreach (var e in exports)
{
this.container.ReleaseExport(e);
}
exports.Clear();
}
}
}
C'est une approche plus simple que j'utilise dans mon projet 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);
}
}
Dans Application_Start, exécutez MefConfig.Initialise () et dans FilterConfig.RegisterGlobalFilters (filtres GlobalFilterCollection), mettez filters.Add (new Filters.MefFilterAttribute ());
J'ai suivi la réponse de @ akanuds1 mais j'ai également dû changer le ControllerFactory en ceci:
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));
}
La solution de M. Kenny Torduer a fonctionné pour moi alors que la bonne réponse supposée ne le permettait pas (impossible de résoudre l'instance de contrôleur alors que toutes les parties dépendantes sont dans le catalogue de thèmes, une erreur m'a été donnée: "le type n'a pas de constructeur par défaut")!
Correction : les deux approches fonctionnent réellement, j'étais stupide par une erreur élémentaire dans le registre des pièces de la convention. Mes sincères excuses à l'auteur de la bonne réponse.