【问题标题】:Mediatr: reducing number of DI'ed objectsMediatr:减少 DI 对象的数量
【发布时间】:2018-12-29 03:05:54
【问题描述】:

我有很多命令和查询,其中大多数需要相同的接口 DI 来做不同的事情。是否有可能减少我的每个处理程序都需要并且一遍又一遍地重复的这种混乱?

public class GetCoinByIdQueryHandler : IRequestHandler<GetCoinByIdQuery, CoinModel>
{
    private readonly EventsContext context;
    private readonly ICacheClient cache;
    private readonly ILogger logger;
    private readonly IMapper mapper;
    private readonly Settings settings;

    public GetCoinByIdQueryHandler(
        EventsContext context, ICacheClient cache, ILogger logger,
        IMapper mapper, IOptions<Settings> settings)
    {
        this.context = context;
        this.cache = cache;
        this.logger = logger;
        this.mapper = mapper;
        this.settings = settings.Value;
    }
 }

这可能与 Mediatr 没有直接关系,但我正在寻找一种更优雅的方法,将所有常见的参数减少到可能是一个 DI 的参数。

如果有任何不同,我将使用 Autofac 作为我的 DI 容器。

编辑:可能有所有处理程序从基类继承的基类,并且在基类中可以访问所有接口并将它们设置为基类的属性,但我不知道如何实现这一点。

编辑 2:Autofac 有属性注入,但这似乎不是正确的方法,所以使用 Mediatr 的人,你如何处理一遍又一遍地重复自己。我见过的每一个使用 Mediatr 的开源项目,似乎都没有解决重复自己的问题。

【问题讨论】:

标签: c# dependency-injection .net-core autofac mediatr


【解决方案1】:

当我发现自己处于多个处理程序具有许多共同依赖项的情况时,我会看两件事:

  1. 我的处理程序是否做得太多;和
  2. 如果是这样,我是否可以在单独的类中重构一些行为

例如,在您发布的处理程序代码中,有一个缓存客户端,这可能意味着您的处理程序做了两件事:

  1. 执行业务逻辑取回硬币;和
  2. 执行一些逻辑确实会返回一个已缓存的硬币,或缓存您刚刚检索到的硬币

MediatR 具有behaviors 的概念,它允许您在一个地方处理横切关注点;这可能适用于缓​​存、日志记录和异常处理。如果您熟悉 ASP.NET Core 中间件,它们遵循相同的概念,因为给出了每个行为:

  1. 当前请求(或 MediatR 术语中的查询);和
  2. 管道中的下一项,可以是其他行为或查询处理程序

让我们看看如何在行为中提取缓存逻辑。现在,你不需要按照这个例子来做一个 T,它实际上只是一种可能的实现。

首先,我们将定义一个应用于需要缓存的查询的接口:

public interface IProvideCacheKey
{
    string CacheKey { get; }
}

然后我们可以更改GetCoinByIdQuery 来实现这个新接口:

public class GetCoinByIdQuery : IRequest<CoinModel>, IProvideCacheKey
{
    public int Id { get; set; }

    public string CacheKey => $"{GetType().Name}:{Id}";
}

接下来,我们需要创建负责缓存的 MediatR 行为。这使用了 ASP.NET Core 中提供的IMemoryCache,只是因为我不知道你的ICacheClient 接口的定义:

public class CacheBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IProvideCacheKey, IRequest<TResponse>
{
    private readonly IMemoryCache _cache;

    public CacheBehavior(IMemoryCache cache)
    {
        _cache = cache;
    }

    public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    {
        // Check in cache if we already have what we're looking for
        var cacheKey = request.CacheKey;
        if (_cache.TryGetValue<TResponse>(cacheKey, out var cachedResponse))
        {
            return cachedResponse;
        }

        // If we don't, execute the rest of the pipeline, and add the result to the cache
        var response = await next();
        _cache.Set(cacheKey, response);

        return response;
    }
}

最后,我们需要使用 Autofac 注册行为:

builder
    .RegisterGeneric(typeof(CacheBehavior<,>))
    .As(typeof(IPipelineBehavior<,>))
    .InstancePerDependency();

我们有了它,缓存现在是一个横切关注点,它的实现存在于单个类中,使其易于更改和测试。

我们可以对不同的事物应用相同的模式,让处理程序只负责业务逻辑。

【讨论】:

  • 我完全同意,缓存和日志记录之类的事情应该使用行为来完成。
  • 我已经在使用一般日志记录的行为,但是我要在命令中执行的日志记录是从缓存或数据库中检索对象的天气。您的答案在各个方面都很完美,因为我现在可以将行为实际应用于缓存(甚至没有考虑过)。在给出的例子中。鉴于您给我的示例,我如何在日志记录行为和缓存之间创建通信,以便如果缓存没有找到它,我会告诉记录器制作日志。
  • 第二个问题,我将我的定义为builder.RegisterGeneric(typeof(LoggingBehavior&lt;,&gt;)).As(typeof(IPipelineBehavior&lt;,&gt;)); 与您注册行为的方式有何不同? builder .RegisterGeneric(typeof(CacheBehavior&lt;,&gt;)) .As(typeof(IPipelineBehavior&lt;,&gt;)) .InstancePerDependency();
  • 它们等价于 InstancePerDependency 是 Autofac 应用的默认生命周期范围。这意味着每次从容器中解析时都会创建一个新实例。
  • 回答你问题的第二部分,如果缓存行为需要做日志记录,那么你的缓存接口需要被注入到行为中。
猜你喜欢
  • 1970-01-01
  • 2018-02-12
  • 2016-05-26
  • 2019-03-26
  • 2014-04-25
  • 1970-01-01
  • 2020-12-30
  • 2018-09-03
  • 2017-05-09
相关资源
最近更新 更多