4

我们正在尝试使用 MEF 2 和 ASP.NET MVC 4 来支持可扩展的应用程序。这个问题实际上有两个部分(希望没关系,所以上帝):

  1. 我们如何使用 Microsoft.Composition 和 MVC 容器代码(MEF/MVC 演示源)来替换 Ninject 作为我们的 DI ICoreServiceICoreRepositoryIUnitOfWorkIDbContext
    看起来我们不能同时使用 Ninject 和 MVC 容器(我相信很多人都在说“duh”),所以如果可能的话,我们想使用 MEF。我尝试删除 Ninject 并[Export]在每个相关实现上设置属性,除了 Web 项目之外还跨越两个程序集,但Save()未能坚持没有错误。我将其解释为单例问题,但不知道如何解决(包括[Shared])。

  2. 我们如何在运行时动态加载多个程序集?
    我了解如何使用CompositionContainer.AddAssemblies()来加载特定的 DLL,但是为了使我们的应用程序能够正确扩展,我们需要更类似于我(模糊地)理解“完整”MEF 中的目录的方式,这些目录已从 Microsoft.Composition 包中删除(我认为?); 允许我们加载所有IPluggable(或其他)程序集,这些程序集将包括它们自己的 UI、服务和存储库层,并与核心服务/存储库相关联。

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

编辑 2
我使用迭代程序集从下面的 Nick Blumhardt 的答案中加载“肉”来更新我的 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;
    }

    ... 
}

用户控制器.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.
            }
        }
    }
}
4

3 回答 3

1

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

于 2012-08-06T20:36:41.207 回答
1

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

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

  1. 构建一个NinjectExportProvider您可以插入 MEF 的工具,因此当 MEF 搜索可用的导出时,它将能够询问您的 Ninject 容器。
  2. 使用 Common Services Locator 模式的实现在 MEF 和 Ninject 之间架起桥梁,反之亦然。

因为您使用 MEF 来实现可扩展性,所以您可能希望使用前者,因为这会将您的 Ninject 组件暴露给 MEF,而 MEF 又将它们暴露给您的插件。

要考虑的另一件事有点令人失望,实际上在 ASP.NET 上自动插入诸如 Wordpress的功能的空间并不大。ASP.NET 是一个编译和托管的环境,因此您要么通过在运行时手动加载程序集来求助于后期绑定,要么重新启动应用程序以获取新插件,这有点违背了能够通过应用程序插入新的扩展。

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

就直接提出的问题而言:

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

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

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)) };
    }      
  }
}

注意:我没有对此进行测试,这都是理论,并且基于以下示例AppSettingsExportDescriptorProviderhttp ://mef.codeplex.com/wikipage?title=ProgrammingModelExtensions

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

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

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

同样,我还没有测试所有这些,但我希望它至少为您指明了正确的方向。

于 2012-08-07T10:01:20.193 回答
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 不适合这种用途,因此如果插件集发生更改,最好的办法是使用目录观察程序检测它并提示用户重启应用。!

于 2012-08-14T04:00:36.953 回答