【问题标题】:Autofac inject IEnumerable of generic interfacesAutofac 注入通用接口的 IEnumerable
【发布时间】:2019-12-19 15:06:16
【问题描述】:

所以,我承认 StackOverflow 中充斥着这个问题,但通常没有人解释他们为什么要这样做。我希望通过这样做,一个更好的答案会浮到顶部。

这家伙做了一些接近我想要的事情:Resolving IEnumerable of generic interfaces from Autofac container 但不完全是。

我知道IGenericInterface<ObjectA> 不等于IGenericInterface<ObjectB>

话虽如此,我很想将每个 IService<T> 注入到一个构造函数中,以便我可以构建查找。老实说,我想构建一个与DbContext.Set<T> 非常相似的实现

我的问题中的几个关键人物。

public interface IService<TMessage> where TMessage: IContractDTO
{
    IQueryable<TMessage> BuildProjection();
}

目前我一次注射这些

public class SomeController : BaseODataController<SomeEntityDTO>
{
    public SomeController(IControllerServiceContext context, IService<SomeEntityDTO> service)
      : base(context, service)
    {

    }

    //DO STUFF
}

IControllerServiceContext 是与DbContextAppSettings 以及我希望在每个控制器中使用的其他一些常用功能的复合接口。

在大多数情况下,这已经足够了。但是偶尔为了支持 EntityA 的逻辑,我可能需要对 B 进行快速查找。我宁愿使用 IService&lt;SomeEntityB&gt;BuildProjections() 实现而不是在控制器 A 中构建冗余。如果我注入每个我有一些这样可以成为 8 或 9 参数的构造函数,例如

所以我开始思考,如果我能够将 IServiceLookup 添加到 IControllerServiceContext,那么我将拥有我需要的一切。

我是从这条路开始的:

public class ServiceLookup<TContract>: IServiceLookup where TContract: BaseClass, IContractDTO
{

    public ServiceLookup(IEnumerable<IService<TContract>> services)
    {
        //Build _Services
    }

    private readonly IDictionary<Type, object> _services;

    public IService<TMessage> Service<TMessage>() where TMessage : class, IContractDTO
    {
        return (IService<TMessage>)(GetService(typeof(TMessage)));
    }

    private object GetService(Type type)
    {
        _services.TryGetValue(type, out var service);

        return service;
    }
}

由于明显的原因,当前的构造函数无法做到这一点。

但是有没有办法通过IIndexIEnumerable 获得我想要的字典,我可以构建&lt;type, object&gt; 的字典,其中对象是我的各种IService&lt;T&gt;

服务查找是基于读取DbContext 代码并简化DbContext.Set 的逻辑而构建的,这也是由IDictionary&lt;Type, object&gt; 驱动的。

如果通过某种解析器参数我可以获得所有IService&lt;T&gt;s,提取T 类型,并将它们添加到该列表中,我就可以参加比赛了。

编辑:我知道我可以注入构建每个服务所需的参数 进入ServiceLookup 并手动构建我的列表,这甚至可能是更好的答案......但如果我可以在没有所有这些的情况下做到这一点,它会更加强大,而且我基本上好奇 em>如果可能的话

Edit2:我希望在实现中能够做的事情如下所示:

public SomeController(IControllerServiceContext context, IServiceLookup lookup)
      : base(context, service)
{
    public SomeMethod() {
       var x = lookup.Service<EntityOneDTO>().BuildProjections().FirstOrDefault();
       var y = lookup.Service<EntityTwoDTO>().BuildProjections().FirstOrDefault();

    //Do Logic that requires both EntityOne and EntityTwo  
    }
}

【问题讨论】:

  • 我不明白你想要做什么。根据我的理解,我制作了这个样本:dotnetfiddle.net/Widget/nGlqm1,它可以工作。你能解释更多你想要做什么吗?
  • @CyrilDurand - 我添加了一些细节。您的示例与我要去的地方有点倒退。我有 Service1: IService 和 Service2: IService

标签: c# dependency-injection autofac


【解决方案1】:

假设您有以下类型:

public class MessageA { }
public class MessageB { }

public interface IService<TMessage> { }
public class ServiceA : IService<MessageA> { }
public class ServiceB : IService<MessageB> { }

你有一个控制器,你想根据你想要的任何东西得到一个IService&lt;MessageA&gt;

第一个解决方案是注入您可能需要的所有IService

public class Controller
{
    public Controller(IService<MessageA> serviceA, IService<MessageB> serviceB)
    {
        this._serviceA = serviceA;
        this._serviceB = serviceB; 
    }

    private readonly IService<MessageA> _serviceA;
    private readonly IService<MessageB> _serviceB;

    public void Do()
    {
        IService<MessageA> serviceA = this._serviceA;
    }
}

如果您的消息类型很少,但如果您的消息类型过多,则无效。

因为IService&lt;T&gt; 是通用的,而Do 没有简单的方法来混合这两个世界。第一个解决方案是引入一个非泛型接口

public interface IService { }
public interface IService<TMessage> : IService { }

并像这样注册这些类型:

builder.RegisterType<ServiceA>().As<IService<MessageA>>().As<IService>();
builder.RegisterType<ServiceB>().As<IService<MessageB>>().As<IService>();

然后你可以有一个IEnumerable&lt;IService&gt;。类似的东西:

public interface IServiceLookup
{
    IService<TMessage> Get<TMessage>();
}
public class ServiceLookup : IServiceLookup
{
    public ServiceLookup(IEnumerable<IService> services)
    {
        this._services = services
            .ToDictionary(s => s.GetType()
                .GetInterfaces()
                .First(i => i.IsGenericType 
                            && i.GetGenericTypeDefinition() == typeof(IService<>))
                .GetGenericArguments()[0], 
                          s => s);
    }
    private readonly Dictionary<Type, IService> _services;

    public IService<TMessage> Get<TMessage>()
    {
        // you should check for type missing, etc. 
        return (IService<TMessage>)this._services[typeof(TMessage)];
    }
}

然后在控制器中注入IServiceLookup

此解决方案的缺点是它会创建所有 IService 的实例,以避免您可以注入 IEnumerable&lt;Func&lt;IService&gt;&gt;

另一种解决方案是将IComponentContext 注入ServiceLookupComponentContext 是一种 Autofac 类型,您可以从中解析服务。

public class ServiceLookup : IServiceLookup
{
    public ServiceLookup(IComponentContext context)
    {
        this._context = context;
    }
    private readonly IComponentContext _context;

    public IService<TMessage> Get<TMessage>()
    {
        return this._context.Resolve<IService<TMessage>>();
    }
}

【讨论】:

  • 谢谢,我回办公室试试看!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-07
  • 1970-01-01
相关资源
最近更新 更多