You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于类型的Simple Injector条件注册及MediatR多服务充值实现问询

How to Combine Simple Injector Conditional Registration with MediatR for Multi-Service Recharge Scenarios

Great question! Let’s walk through how to set this up cleanly for your phone customer recharge use case—where you need to execute different recharge commands based on the services a customer has activated.

1. Define Core Interfaces for Recharge Commands

First, establish a base set of interfaces to standardize your recharge commands and their handlers. This keeps your Command pattern consistent and scalable:

// Base command interface for all recharge operations
public interface IRechargeCommand
{
    Guid CustomerId { get; }
    decimal Amount { get; }
}

// Generic handler interface tied to specific recharge commands
public interface IRechargeCommandHandler<in TCommand> where TCommand : IRechargeCommand
{
    Task HandleAsync(TCommand command, CancellationToken cancellationToken);
}

2. Implement Service-Specific Commands & Handlers

Create concrete implementations for each service type (e.g., voice, data, SMS). These encapsulate the unique logic for recharging each service:

// Voice service recharge command
public class VoiceRechargeCommand : IRechargeCommand
{
    public Guid CustomerId { get; init; }
    public decimal Amount { get; init; }
    // Add service-specific properties (like call minute allocation) if needed
}

// Voice service recharge handler
public class VoiceRechargeHandler : IRechargeCommandHandler<VoiceRechargeCommand>
{
    public async Task HandleAsync(VoiceRechargeCommand command, CancellationToken cancellationToken)
    {
        // Logic to process voice service recharge (e.g., update customer's call balance)
        await Console.Out.WriteLineAsync($"Recharging voice service for customer {command.CustomerId} with ${command.Amount}");
    }
}

// Repeat for other services
public class DataRechargeCommand : IRechargeCommand { /* ... */ }
public class DataRechargeHandler : IRechargeCommandHandler<DataRechargeCommand> { /* ... */ }

3. Configure Simple Injector Conditional Registration

Use Simple Injector’s RegisterConditional method to map each command type to its exact corresponding handler. This ensures the right handler is resolved when you request it for a specific command:

var container = new Container();

// Register all IRechargeCommandHandler implementations from your assembly
container.Register(typeof(IRechargeCommandHandler<>), typeof(VoiceRechargeHandler).Assembly);

// Add conditional registration to enforce exact command-handler matching
container.RegisterConditional(
    typeof(IRechargeCommandHandler<>),
    typeof(IRechargeCommandHandler<>),
    context => 
        context.Consumer.ImplementationType.GetGenericArguments()[0] == 
        context.ServiceType.GetGenericArguments()[0]
);

// Register MediatR with Simple Injector (standard setup)
container.RegisterMediatR(typeof(YourMediatRHandlerMarkerClass).Assembly);

// Validate container configuration to catch mismatches early
container.Verify();

This conditional registration guarantees that when you request IRechargeCommandHandler<VoiceRechargeCommand>, Simple Injector resolves only VoiceRechargeHandler—not any other handler that implements the generic interface.

4. Use Handlers in Your MediatR Request Processor

In your main MediatR handler (the one that loads the customer’s activated services), inject a factory or enumerable of handlers to resolve and execute the correct logic for each service:

Option 1: Use a Factory for Precise Resolution

public class ProcessCustomerRechargeHandler : IRequestHandler<ProcessCustomerRechargeRequest, Unit>
{
    private readonly ICustomerServiceRepository _customerServiceRepo;
    private readonly Func<Type, IRechargeCommandHandler<IRechargeCommand>> _handlerFactory;

    public ProcessCustomerRechargeHandler(
        ICustomerServiceRepository customerServiceRepo,
        Func<Type, IRechargeCommandHandler<IRechargeCommand>> handlerFactory)
    {
        _customerServiceRepo = customerServiceRepo;
        _handlerFactory = handlerFactory;
    }

    public async Task<Unit> Handle(ProcessCustomerRechargeRequest request, CancellationToken cancellationToken)
    {
        // Load the customer's activated services
        var activatedServices = await _customerServiceRepo.GetActivatedServicesAsync(request.CustomerId, cancellationToken);

        foreach (var service in activatedServices)
        {
            // Create the appropriate recharge command based on service type
            IRechargeCommand command = service.Type switch
            {
                ServiceType.Voice => new VoiceRechargeCommand { CustomerId = request.CustomerId, Amount = request.Amount },
                ServiceType.Data => new DataRechargeCommand { CustomerId = request.CustomerId, Amount = request.Amount },
                _ => throw new NotSupportedException($"Service type {service.Type} isn't supported for recharge")
            };

            // Resolve the matching handler
            var handlerType = typeof(IRechargeCommandHandler<>).MakeGenericType(command.GetType());
            dynamic handler = _handlerFactory(handlerType);

            // Execute the recharge logic
            await handler.HandleAsync((dynamic)command, cancellationToken);
        }

        return Unit.Value;
    }
}

Option 2: Use an Enumerable to Filter Handlers

If you prefer, inject all handlers and filter for the correct one instead of using a factory:

public class ProcessCustomerRechargeHandler : IRequestHandler<ProcessCustomerRechargeRequest, Unit>
{
    private readonly ICustomerServiceRepository _customerServiceRepo;
    private readonly IEnumerable<IRechargeCommandHandler<IRechargeCommand>> _allHandlers;

    public ProcessCustomerRechargeHandler(
        ICustomerServiceRepository customerServiceRepo,
        IEnumerable<IRechargeCommandHandler<IRechargeCommand>> allHandlers)
    {
        _customerServiceRepo = customerServiceRepo;
        _allHandlers = allHandlers;
    }

    public async Task<Unit> Handle(ProcessCustomerRechargeRequest request, CancellationToken cancellationToken)
    {
        var activatedServices = await _customerServiceRepo.GetActivatedServicesAsync(request.CustomerId, cancellationToken);

        foreach (var service in activatedServices)
        {
            IRechargeCommand command = /* ... create command as before ... */;
            var handlerType = typeof(IRechargeCommandHandler<>).MakeGenericType(command.GetType());
            
            // Find the handler that matches the command type
            var handler = _allHandlers.First(h => handlerType.IsAssignableFrom(h.GetType()));
            dynamic typedHandler = handler;

            await typedHandler.HandleAsync((dynamic)command, cancellationToken);
        }

        return Unit.Value;
    }
}

Key Tips

  • Type Safety: Using dynamic here is safe because you control the command-handler mapping via your switch statement and conditional registration. For stricter safety, you could add a method to handlers that returns their supported command type.
  • Lifetimes: If your handlers depend on scoped services (like database contexts), register them with Lifestyle.Scoped to match your MediatR setup.
  • Testing: You can easily mock the handler factory or enumerable in unit tests to isolate your main processor logic.

内容的提问来源于stack exchange,提问作者Lorenzo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 08:33:39