Ninject equivalent to MEF AssemblyCatalog

本文关键字:AssemblyCatalog MEF to equivalent Ninject | 更新日期: 2023-09-27 18:34:50

在 MEF 中,AssemblyCatalog用于扫描程序集以查找所有导出的类型并配置容器。有与Ninject等价物吗?

var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
var container = new CompositionContainer(catalog);

Ninject equivalent to MEF AssemblyCatalog

在 Ninject 中,使用类型和扫描程序集的属性来添加绑定的功能由名为 Ninject.Extensions.Convention 的扩展提供。

  1. 添加约定包,例如使用包管理器控制台:

    Install-Package Ninject.Extensions.Conventions
    
  2. 创建一些要在服务上使用的自定义属性。这将等效于 MEF 中的ExportAttribute

    public enum ExportScope
    {
        Singleton,
        Transient
    }
    [AttributeUsage(AttributeTargets.Class)]
    public class ExportAttribute : Attribute
    {
        public ExportAttribute(Type serviceInterface, ExportScope scope = ExportScope.Singleton)
        {
            this.ServiceInterface = serviceInterface;
            this.Scope = scope;
        }
        public Type ServiceInterface { get; set; }
        public ExportScope Scope { get; set; }
    }
    
  3. 创建一个自定义 BindingGenerator,它使用我们的ExportAttribute将类型绑定到给定接口:

    public class ExportAttributeBindingGenerator : IBindingGenerator
    {
        public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(Type type, IBindingRoot bindingRoot)
        {
            var attribute = type.GetCustomAttribute<ExportAttribute>();
            var serviceType = attribute.ServiceInterface;
            if (!serviceType.IsAssignableFrom(type))
            {
                throw new Exception(string.Format("Error in ExportAttribute: Cannot bind type '{0}' to type '{1}'.", 
                    serviceType, type));
            }
            var binding = bindingRoot.Bind(serviceType).To(type);
            switch (attribute.Scope)
            {
                case ExportScope.Singleton:
                    yield return (IBindingWhenInNamedWithOrOnSyntax<object>) binding.InSingletonScope();
                    break;
                case ExportScope.Transient:
                    yield return (IBindingWhenInNamedWithOrOnSyntax<object>) binding.InTransientScope();
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }
    
  4. 创建要在内核上使用的扩展方法并添加约定:

    public static class NinjectBindingExtensions
    {
        public static void BindExportsInAssembly(this IBindingRoot root, Assembly assembly)
        {
            root.Bind(c => c.From(assembly)
                              .IncludingNonePublicTypes()
                              .SelectAllClasses()
                              .WithAttribute<ExportAttribute>()
                              .BindWith<ExportAttributeBindingGenerator>());
        }
    }
    

现在,对于如此常见的事情来说,这似乎需要做很多工作,但它非常灵活和可扩展。它做任何你想让它做的事情。把它放在实用程序类库中,并在任何你想要的地方使用它,就像这样:

[Export(typeof(IFoo))]
public class Foo : IFoo
{
}
[Export(typeof(IBar), ExportScope.Transient)]
public class Bar : IBar
{
}
[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestNinjectConventions()
    {
        var kernel = new StandardKernel();
        kernel.BindExportsInAssembly(typeof(IFoo).Assembly);
        kernel.Get<IFoo>().Should().Be(kernel.Get<IFoo>());
        kernel.Get<IBar>().Should().NotBe(kernel.Get<IBar>());
    }
}