我们如何使用ASP.NET MVC 4和MEF 2支持模块化和可测试模式?

时间:2012-08-06 20:29:28

标签: asp.net-mvc asp.net-mvc-4 mef

我们正在尝试将MEF 2与ASP.NET MVC 4一起使用来支持可扩展的应用程序。这个问题确实有两个部分(希望没关系,所以这就是神):

  1. 我们如何使用Microsoft.Composition和MVC容器代码(MEF/MVC demo source)将Ninject替换为ICoreServiceICoreRepository的DI, IUnitOfWorkIDbContext
    看起来我们不能同时使用Ninject和MVC容器(我敢肯定很多人会说“duh”),所以如果可能的话,我们想和MEF一起使用。我尝试删除Ninject并在每个相关实现上设置[Export]属性,除了Web项目之外还包含两个程序集,但Save()未能保持没有错误。我将其解释为单身问题,但无法弄清楚如何对其进行排序(包括[Shared])。

  2. 我们如何在运行时动态加载多个装配?
    我理解如何使用CompositionContainer.AddAssemblies()来加载特定的DLL,但是为了使我们的应用程序能够正确扩展,我们需要更类似于我(模糊地)理解“完整”MEF中的目录,这些目录已被剥离从Microsoft.Composition包中出来(我想?);允许我们加载所有IPluggable(或其他)程序集,这些程序集将包括它们自己的UI,服务和存储库层,并且也会绑定到Core服务/存储库。

  3. 编辑1
    一点more reading解决了第一个问题,实际上是一个单身人士问题。将[Shared(Boundaries.HttpRequest)]附加到CoreDbContext解决了持久性问题。当我简单地尝试[Shared]时,它将“单例化”扩展到应用程序级别(交叉请求)并抛出一个异常,说明编辑的对象已经在EF缓存中。

    编辑2
    我使用了来自Nick Blumhardt的答案的迭代程序集“meat”来更新我的Global.asax.cs代码。他的代码中的标准MEF 2容器在我的工作中不起作用,可能是因为我使用的是MEF 2(?)MVC容器。摘要:下面列出的代码现在可以正常工作。


    CoreDbContext.cs(Data.csproj)

    [Export(typeof(IDbContext))]
    [Shared(Boundaries.HttpRequest)]
    public class CoreDbContext : IDbContext { ... }
    

    CoreRepository.cs(Data.csproj)

    [Export(typeof(IUnitOfWork))]
    [Export(typeof(ICoreRepository))]
    public class CoreRepository : ICoreRepository, IUnitOfWork
    {
        [ImportingConstructor]
        public CoreRepository(IInsightDbContext context)
        {
            _context = context;
        }
    
        ... 
    }
    

    CoreService.cs(Services.csproj)

    [Export(typeof(ICoreService))]
    public class CoreService : ICoreService
    {
        [ImportingConstructor]
        public CoreService(ICoreRepository repository, IUnitOfWork unitOfWork)
        {
            _repository = repository;
            _unitOfWork = unitOfWork;
        }
    
        ... 
    }
    

    UserController.cs(Web.csproj)

    public class UsersController : Controller
    {
        [ImportingConstructor]
        public UsersController(ICoreService service)
        {
            _service = service;
        }
    
        ... 
    }
    

    Global.asax.cs(Web.csproj)

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            CompositionProvider.AddAssemblies(
                typeof(ICoreRepository).Assembly,
                typeof(ICoreService).Assembly,
            );
    
            // EDIT 2 -- 
            // updated code to answer my 2nd question based on Nick Blumhardt's answer
            foreach (var file in System.IO.Directory.GetFiles(Server.MapPath("Plugins"), "*.dll"))
            {
                try
                {
                    var name = System.Reflection.AssemblyName.GetAssemblyName(file);
                    var assembly = System.Reflection.Assembly.Load(name);
                    CompositionProvider.AddAssembly(assembly);
                }
                catch
                {
                    // You'll need to craft exception handling to
                    // your specific scenario.
                }
            }
        }
    }
    

3 个答案:

答案 0 :(得分:1)

MEF不打算用作DI框架。这意味着您应该将您的“插件”(无论它们是什么)组合与您的基础架构依赖关系分开,并通过MEF实现前者,后者通过您喜欢的任何DI框架实现。

答案 1 :(得分:1)

我认为对于MEF能做什么和不能做什么有一些误解。

最初MEF被认为是纯粹的可扩展性架构,但随着框架发展到第一个版本,它也可以完全支持作为DI容器。 MEF将为您处理依赖注入,并通过它的ExportProvider架构来实现。完全可以使用other DI frameworks with MEF。所以实际上有很多方法可以实现:

  1. 构建一个可插入MEF的NinjectExportProvider,因此当MEF搜索可用的导出时,它将能够查询您的Ninject容器。
  2. 使用公共服务定位器模式的实现在MEF和Ninject之间建立桥接,反之亦然。
  3. 因为您使用MEF来实现可扩展性,所以您可能希望使用前者,因为这会将您的Ninject组件暴露给MEF,MEF会将它们暴露给您的插件。

    要考虑的另一件事,有点令人失望,实际上在ASP.NET上自动插入功能 ala Wordpress的空间不大。 ASP.NET是一个编译和管理的环境,因此您可以通过在运行时手动加载程序集来进行后期绑定,或者重新启动应用程序以获取新的插件,这样就有可能破坏对象的能力。通过应用程序插入新扩展。

    我的建议是,计划您的架构选择任何可扩展性点作为启动,并假设任何核心更改都需要重新启动部署和应用程序。

    就所提出的直接问题而言:

    1. CompositionProvider接受ContainerConfiguration实例,该内容在内部用于创建提供商使用的CompositionContainer。因此,您可以使用此作为自定义容器实例化方式的点。 ContainerConfiguration支持WithProvider方法:

      var configuration = new ContainerConfiguration().WithProvider(new NinjectExportDescriptorProvider(kernel)); CompositionProvider.SetConfiguration(configuration);

    2. NinjectExportDescriptorProvider可能在哪里:

      public class NinjectExportDescriptorProvider: ExportDescriptorProvider
      {
        private readonly IKernel _kernel;
      
        public NinjectExportDescriptorProvider(IKernel kernel)
        {
          if (kernel == null) throw new ArgumentNullException("kernel");
      
          _kernel = kernel;
        }
      
        public override IEnumerable<ExportDescriptorPromise> GetExportDescriptors(
          CompositionContract contract, DependencyAccessor dependencyAccessor)
        {
          var type = contract.ContractType;
      
          if (!_kernel.GetBindings(type).Any())
            return NoExportDescriptors;
      
          return new[] {
            new ExportDescriptorPromise(
              contract,
              "Ninject Kernel",
              true, // Hmmm... need to consider this, setting it to true will create it as a shared part, false as new instance each time,
              NoDependencies,
              _ => ExportDescriptor.Create((c, o) => _kernel.Get(type), NoMetadata)) };
          }      
        }
      }
      

      注意:我没有对此进行测试,这是完全理论,并且基于http://mef.codeplex.com/wikipage?title=ProgrammingModelExtensions

      上的示例AppSettingsExportDescriptorProvider

      它与使用标准ExportProvider不同,因为使用CompostionProvider是围绕轻量级构图构建的。但实质上,您正在完成对Ninject内核的访问,并将其提供给CompositionContainer

      1. 与添加特定的新提供程序(参见上文)一样,您可以使用ContainerConfiguration来读取可用的程序集,可能类似于:

        var configuration = new ContainerConfiguration().WithAssemblies(AppDomain.GetAssemblies())

      2. 同样,我还没有测试过所有这些,但我希望它至少能指出你正确的方向。

答案 2 :(得分:1)

如果我理解正确,那么您正在寻找将从目录加载所有程序集并将其加载到容器中的代码;这是执行此操作的框架:

var config = new ContainerConfiguration();
foreach (var file in Directory.GetFiles(@".\Plugins", "*.dll"))
{
   try
   {
       var name = AssemblyName.GetAssemblyName(file);
       var assembly = Assembly.Load(name);
       config.WithAssembly(assembly);
   }
   catch
   {
       // You'll need to craft exception handling to
       // your specific scenario.
   }
}
var container = config.CreateContainer();
// ...

Hammett讨论了这个场景,并在F#中显示了一个更完整的版本:http://hammett.castleproject.org/index.php/2011/12/a-decent-directorycatalog-implementation/

注意,在应用程序启动后,这不会检测添加到目录的程序集 - Microsoft.Composition不适合这种用途,所以如果插件集更改,最好的办法就是检测到目录观察程序并提示用户重新启动应用程序。 HTH!