同じプロジェクトで Managed Extensibility Framework (MEF)をASP.NET MVC 4およびASP.NET Web APIとどのように統合しますか?
MVCコントローラーHomeController
とWeb APIコントローラーContactController
を使用したサンプルアプリケーションを考えてみましょう。どちらにもIContactRepository
タイプのプロパティがあり、MEFを使用して解決します。問題は、MEFをMVCおよびWeb APIにプラグインして、インスタンスが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および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"}
};
}
}
お問い合わせ先:
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
}
解決策は、 System.Web.Mvc.IDependencyResolver および System.Web.Http.Dependencies.IDependencyResolver を実装し、ASP.NET MVCおよびASP.NET Webに実装を登録することです。 Application_Start
メソッドでそれぞれAPI。
この例では、依存関係リゾルバをインストールするためにApplication_Start
から呼び出されるメソッドMefConfig
を実装するクラスRegisterMef
を作成します。クラスMefDependencyResolver
はSystem.Web.Mvc.IDependencyResolver
とSystem.Web.Http.Dependencies.IDependencyResolver
の両方を実装しているため、MVCとWeb APIの両方の依存関係解決義務を処理します。
Application_Start、これをGlobal.asax.csに入れます:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
[...]
MefConfig.RegisterMef();
}
}
MefDependencyResolverおよび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の回答は、MEFをDependencyResolverに統合するためにこれまで見てきた中で最高です。 MEF2のコンベンションベースのコンポジションを使用するようにかなり簡単に拡張できました。 MefConfigクラスは変更に必要なすべてのものであり、それほど多くはありません。
/// <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;
}
}
あなたはこれを見ることができます http://kennytordeur.blogspot.be/2012/08/mef-in-aspnet-mvc-4-and-webapi.html 。 Asp.net MVC 4/Web ApiプロジェクトでMEFを使用する方法について説明します。このコードに基づく Nugetパッケージ も存在します。そうすれば、非常に簡単かつ迅速にテストできます。
@ aknuds1のソリューションは機能しますが、すべてのAPI呼び出しでメモリリークが発生します。私は彼の解決策を修正してリークを修正しました。
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();
}
}
}
これは、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);
}
}
Application_StartでMefConfig.Initialise()を実行し、FilterConfig.RegisterGlobalFilters(GlobalFilterCollection filters)でfilters.Add(new Filters.MefFilterAttribute());を配置します。
私は@ akanuds1の答えに従いましたが、ControllerFactoryを次のように変更する必要もありました。
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));
}
Kenny Torduer氏の解決策は私にとってうまくいきましたが、正しい答えは想定されませんでした(すべての依存部品がカテゴリにあるにもかかわらず、コントローラーインスタンスを解決できませんでした。「型にデフォルトコンストラクターがない」エラーが発生しました)。
修正:どちらのアプローチも実際には機能しますが、コンベンションパーツレジストリの基本的な間違いにバカにされていました。正解の著者への心からの謝罪。