基于类型

时间:2018-02-15 15:48:12

标签: c# dependency-injection ioc-container simple-injector

我正在使用MediatR来执行我的请求。在具体处理程序中,我的要求是根据具体情况在一个或多个对象实例上执行命令。

让我们举一个简单的例子。假设请求是为移动服务客户执行充值。要求规定,如果客户激活了多个服务,则必须在每个服务上完成充值。

当处理程序运行时,首先加载客户服务

var services = context.Services.Where(c => c.CustomerId == customerId);
foreach ( var service in services ) {
   //Do the topup
}

为了处理充值,我使用Command模式实现了它。所以我有以下模型

command pattern

其中Invoker正是MediatR处理程序实例。这是我的接收器界面

internal interface IReceiver<TRequest, TResponse>
    where TRequest : DefaultRequest
    where TResponse : DefaultResponse
{
        TResponse Apply( TRequest request );

        bool SupportsCommand( Type commandType );
}

并且有一些基于网络元素品牌(诺基亚,爱立信等)的不同实现

internal abstract class BaseNokiaReceiver<TRequest, TResponse> 
    : IReceiver<TRequest, TResponse>
    where TRequest : DefaultRequest
    where TResponse : DefaultResponse
{
    public BaseNokiaReceiver( ILogger logger, DataContext context, Service service )
        : base( logger, context, service ) {
    }

    public abstract TResponse Apply( TRequest request );
}

和具体的

internal class NokiaDataTrafficReceiver : BaseNokiaReceiver<TopupRequest, TopupResponse>
{
    public NokiaDataTrafficReceiver(ILogger logger, DataContext context, Service service) 
        : base(logger, context, service) {
    }

    public override TopupResponse Apply( TopupRequest request ) {
        //[...] Application code
    }
}

由于客户可以在其帐户上启用多个服务,因此我必须拥有多个具有单个命令类的接收器,例如

internal abstract class AbstractCommand<TRequest, TResponse> 
    : ICommand<TRequest, TResponse>
    where TRequest : DefaultRequest
    where TResponse : DefaultResponse
{
    protected IReceiver<TRequest, TResponse> _receiver;

    public AbstractCommand( IReceiver<TRequest, TResponse> receiver ) {
        _receiver = receiver;
    }

    public abstract Task<TResponse> Apply( TRequest request );
}

internal class Reload : AbstractCommand<TopupRequest, TopupResponse>
{
    public Reload( IReceiver<TopupRequest, TopupResponse> receiver ) : base( receiver ) {
    }

    public async override Task<TopupResponse> Apply( TopupRequest request ) {
        var response = _receiver.Apply( request );
        return await Task.FromResult<TopupResponse>( response );
    }
}

然后,处理程序实现变得类似于以下

var services = context.Services.Where( c => c.CustomerId = customerId );
foreach ( var service in services ) {
    IReceiver<TopupRequest, TopupResponse> receiver = null;
    if ( service is VoiceService ) {
        receiver = new VoiceAccountReceiver();
    }
    else if ( service is DataService ) {
        receiver = new DataTrafficReceiver();
    }
    Reload command = new Reload( receiver );
    var result = command.Apply( input );
}

每个接收器都有一个特定的充值。

实际上接收器的实例化发生在代码中,我想以我可以使用DI容器的方式进行更改。

通过伪代码交谈,我想注册一个严格依赖于类型的Receiver实例,例如

container.Register<IReceiver<TopupRequest, TopupResponse>, VoiceAccountReceiver>()
    .WhenParameterofType<VoiceService>();
container.Register<IReceiver<TopupRequest, TopupResponse>, DataTrafficReceiver>()
    .WhenParameterofType<DataService>();

然后使用

在运行时解析类型
container.GetInstance<IReceiver<TopupRequest, TopupResponse>>( typeof(service) );

1 个答案:

答案 0 :(得分:2)

正如我所看到的,您缺少一些类型信息,可以让您在编译时区分操作。这似乎很明显,因为您要在VoiceServiceDataService上添加类型检查。

所以您可以尝试将该类型信息添加到IReceiver<TRequest, TResponse>抽象中。例如:

interface IReceiver<TRequest, TResponse, TService> { }

这样,VoiceAccountReceiver可以按如下方式实现:

class VoiceAccountReceiver : IReceiver<TopupRequest, TopupResponse, VoiceService>

这允许根据其可用类型信息解析正确的接收器。优选地,您的处理程序应该依赖于介体抽象的类型。中介实现将负责回调容器以解析正确的类型。

例如:

var services = context.Services.Where(c => c.CustomerId == customerId);
foreach ( var service in services ) {
    var result = this.receiverMediator.Apply(input, service);
}

您的IReceiverMediator可以定义如下:

interface IReceiverMediator
{
    TResponse Apply<TRequest, TService>(TRequest input, object service)
        where TRequest : IRequest<TResponse>;
}

Simple Injector特定实现可以定义如下:

class SimpleInjectorReceiverMediator : IReceiverMediator
{
    private readonly Container container;

    public SimpleInjectorReceiverMediator(Container container) {
        this.container = container;
    }

    public TResponse Apply<TRequest, TService>(TRequest input, object service)
        where TRequest : IRequest<TResponse>
    {
        Type receiverType =
            typeof(IReceiver<,,>).MakeGenericType(typeof(TRequest), typeof(TResponse), service.GetType());

        dynamic receiver = container.GetInstance(receiverType);

        return (TResponse)receiver.Action();
    }
}

这样就没有条件注册。您可以按如下方式批量注册所有接收器:

container.Register(typeof(IReceiver<,,>), assemblies);
container.Register<IReceiverMediator>(new SimpleInjectorReceiverMediator(container));