Autofac 注入泛型接口的 IEnumerable

3

所以,我知道StackOverflow上有很多这样的问题,但通常没有人解释他们为什么想要这样做。我希望通过这样做,更好的答案浮现出来。

这个家伙做了与我想要的接近:从Autofac容器解决通用接口的IEnumerable,但不完全一样。

我知道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<SomeEntityB>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;
    }
}

由于明显的原因,目前的构造函数无法实现这一点。 但是是否有一种方式可以获取我想要的字典,通过或者我可以构建<type, object>字典的,其中object是我的各种IService<T>? 服务查找是基于阅读DbContext代码并简化DbContext.Set逻辑构建的,它也受到驱动。 如果能够通过某种解析器参数获取所有IService<T>,提取类型,并将其添加到该列表中,那么这个功能就可以使用了。 编辑:我认识到我可以注入需要构建每个服务所需的参数到ServiceLookup中,并手动构建我的列表,甚至可能是更好的答案… 但是,如果我能做到没有所有这些,那么它会更加健壮,而且我对是否可能有根本上的好奇心。 编辑2:我希望在实现中能够做到这样:
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  
    }
}

我不明白你试图做什么。根据我的理解,我制作了这个示例:https://dotnetfiddle.net/Widget/nGlqm1,它可以正常工作。你能否更详细地解释一下你想要做什么? - Cyril Durand
@CyrilDurand - 我添加了一些细节。你的例子与我的方向有些相反。我有Service1:IService<Message1>和Service2:IService<Message2>。 - Seth
1个回答

1
假设您有以下类型:

public class MessageA { }
public class MessageB { }

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

您有一个控制器,想要根据需要获取一个IService<MessageA>

第一种解决方案是注入您可能需要的所有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<T>是通用的,而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<IService>。类似这样的内容:
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<Func<IService>>

另一种解决方案是将IComponentContext注入到ServiceLookup中。 ComponentContext 是一个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>>();
    }
}

谢谢,我回到办公室后会尝试一下! - Seth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接