【问题标题】:Make .NET Core DI auto resolve class by generic interface / abstract class implementation通过泛型接口/抽象类实现使 .NET Core DI 自动解析类
【发布时间】:2018-07-28 20:25:31
【问题描述】:

.NET Core 中有没有办法注册一个泛型接口,并让它解析一个匹配某个实现的类。

比如我有如下界面:

public interface IMapper<TFrom, TTo>
{
}

我还有一个抽象类:

public abstract class Mapper<TFrom, TTo> : IMapper<TFrom, TTo>
{
    protected Mapper()
    {
        // some generic stuff
    }

    public abstract TTo Map(TFrom);
}

然后我可以像这样创建一个实现:

public class UserMapper : Mapper<Domain.User, Entity.User>
{
    public override Entity.User Map(Domain.User from)
    {
        // do mapping
    }
}

有没有办法,使用默认的.NET Core DI注册IMapper&lt;,&gt;,让它自动解析类?

因此,例如,如果我会在代码中的某处执行此操作:

class SomeClass
{
    public SomeClass(IMapper<Domain.User, Entity.User> mapper) {}
}

它以某种方式知道它应该解析类UserMapper&lt;Domain.User, Entity.User&gt;

原因是手动注册每个映射器有点冗长,具体到一个实现。所以我希望Microsoft.DependencyInjection 足够聪明,能够以某种方式自动解决其实现。

【问题讨论】:

    标签: c# dependency-injection .net-core inversion-of-control


    【解决方案1】:

    当前设计的唯一方法是使用反射:

    Assembly assembly = typeof(UserMapper).Assembly;
    
    foreach (var type in assembly.GetTypes()
        .Where(t => t.IsClass && !t.IsAbstract))
    {
        foreach (var i in type.GetInterfaces())
        {
            if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapper<,>))
            {
                // NOTE: Due to a limitation of Microsoft.DependencyInjection we cannot 
                // register an open generic interface type without also having an open generic 
                // implementation type. So, we convert to a closed generic interface 
                // type to register.
                var interfaceType = typeof(IMapper<,>).MakeGenericType(i.GetGenericArguments());
                services.AddTransient(interfaceType, type);
            }
        }
    }
    

    注意:您可以通过在 IServiceCollection 上创建一个扩展方法来简化它,并为 AddTransientAddSingleton 等提供各种重载。

    如果您更改设计,请使用非抽象泛型作为您的实现类型:

    public class Mapper<TFrom, TTo> : IMapper<TFrom, TTo>
    {
        //...
    }
    

    然后你可以像这样简单地注册:

    services.AddTransient(typeof(IMapper<,>), typeof(Mapper<,>));
    

    【讨论】:

      【解决方案2】:

      这是一个更通用的解决方案(默认作用域生命周期)

      先创建这个扩展方法

        public static void RegisterAllTypes(this IServiceCollection services, Assembly assembly, Type baseType,
                  ServiceLifetime lifetime = ServiceLifetime.Scoped)
              {
                  foreach (var type in assembly.GetTypes()
                      .Where(t => t.IsClass && !t.IsAbstract))
                  {
                      foreach (var i in type.GetInterfaces()
                          .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == baseType))
                      {
      
                          var interfaceType = baseType.MakeGenericType(i.GetGenericArguments());
                          services.Add(new ServiceDescriptor(interfaceType, type, lifetime));
                      }
                  }
              }
      

      然后将其添加到 startup.cs

       services.RegisterAllTypes(typeof(AClass).Assembly, typeof(IMapper<>));
      

      【讨论】:

        【解决方案3】:

        这里你已经准备好使用助手了:

        按通用接口

        services.AddAllGenericTypes(typeof(IGenericRepository<>), new[] {typeof(MyDbContext).GetTypeInfo().Assembly});
        

        按界面

        services.AddAllTypes<IGenerator>(new[] { typeof(FileHandler).GetTypeInfo().Assembly },
        

        扩展名来自: https://gist.github.com/GetoXs/5caf0d8cfe6faa8a855c3ccef7c5a541

        【讨论】:

          猜你喜欢
          • 2020-05-14
          • 1970-01-01
          • 2021-12-08
          • 1970-01-01
          • 1970-01-01
          • 2019-03-22
          • 2012-04-11
          • 2010-11-08
          • 2013-03-06
          相关资源
          最近更新 更多