当实体在不同的项目中时,为什么 Mediator 不解析方法?

Why doesn't Mediatr resolve method when entites are in different projects?

我有一个简单的项目来尝试 Mediatr 问题。当我的处理程序的具体 class 在我的 API 的同一个项目中时,它起作用了。但是,当我将该处理程序 class 带入另一个项目(并且 API 引用该项目 ofc)时,它不会解析注册表。

我收到这个错误:

Handler was not found for request of type MediatR.IRequestHandler`2[MyBiz.GetTokenModelRequest,MyBiz.TokenModel]. Register your handlers with the container. See the samples in GitHub for examples.

我在我的项目中有这个结构,还显示了它在哪里工作,哪里不工作:

有关更多说明,请参阅代码:

MyApi2 -> Startup.cs:

namespace MyApi2
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddMediatR();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc();
        }
    }
}

MyApi2 -> ValuesController:

namespace MyApi2.Controllers
{
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        private readonly IMediator _mediator;

        public ValuesController(IMediator mediator)
        {
            _mediator = mediator;
        }

        [HttpGet]
        public async Task<IEnumerable<string>> Get()
        {
            try
            {
                var rr = await _mediator.Send(new GetTokenModelRequest());
            }
            catch (Exception ex)
            {
                throw;
            }
            return new string[] { "value1", "value2" };
        }
    }
}

MyBiz -> GetTokenModelRequest

namespace MyBiz
{
    public class GetTokenModelRequest : LoginModel, IRequest<TokenModel>
    {
    }
    public class LoginModel
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }
    public class TokenModel
    {
        #region Properties

        public Guid Id { get; set; }
        public string Username { get; set; }
        public string Token { get; set; }
        public DateTime Expiration { get; set; }

        #endregion
    }
}

MyInftra -> TokenQueryHandler

namespace MyInfra
{
    public class TokenQueryHandler : ITokenQueryHandler
    {
        public Task<TokenModel> Handle(GetTokenModelRequest request, CancellationToken cancellationToken)
        {
            return Task.FromResult(new TokenModel());
        }
    }
}

所以,如果我 MOVE TokenQueryHandlerMyInfraMyApi 它有效,但我应该可以把它作为一个参考项目,对吧?

更新

从版本 7.0.0 of the MediatR.Extensions.Microsoft.DependencyInjection 包开始,当调用 AddMediatR() 扩展方法时,AppDomain 不再自动扫描包含要注册的 MediatR 基类型的已加载程序集。

事实上,该函数的无参数重载已完全从程序包中删除,需要用户传入程序集(或类型)以进行扫描。

这使得 MediatR 基类型(IRequestHandlerINotificationHandlerIRequestPreProcessorIRequestPostProcessor)在每个引用的程序集中的注册,明确地在用户的控制下,并且自由裁量权。

因此,如果我们想在 MediatR 容器中注册的假想程序集 Assembly1Assembly2 中有一些 MediatR 基类型:

而不是做:services.AddMediatR();

您需要做:services.AddMediatR(typeof(Assembly1), typeof(Assembly2));

这使得我的原始答案(如下)对于使用此软件包的第 7 版(可能更高版本)的任何人来说都是多余的,但我会为那些使用旧版本的人保留它。


原答案

注意:以下答案仅与 MediatR.Extensions.Microsoft.DependencyInjection 包的 7.0.0 以下版本相关。

在您的 startup.cs 文件中调用 AddMediatR() 扩展方法会做很多事情来初始化 MediatR:

  • 它扫描应用程序域以查找当前加载的程序集
  • 它扫描每个当前加载的程序集以查找每个 class 继承自 MediatR 基本类型(IRequestHandlerINotificationHandlerIRequestPreProcessorIRequestPostProcessor)
  • 它将每个 MediatR 基类型注册到容器中供以后使用

考虑到以上几点,了解 .NET CLR 如何加载引用的程序集很重要。 Rick Strahl 的一篇非常有趣的文章 blog post 详细介绍了这一点,但我将在这里用一句话总结它:

In a nutshell, referenced assemblies are not immediately loaded - they are loaded on the fly as needed. So regardless of whether you have an assembly reference in a top level project, or a dependent assembly assemblies typically load on an as needed basis, unless explicitly loaded by user code. The same is true of dependent assemblies.

为什么知道这一点很重要?

好吧,在您的 MyApi2 项目中,您引用了 MyInfra 项目,但 您实际上并没有以任何方式使用它 。这意味着程序集不会被 CLR 加载,因此 MediatR 将无法在 App Domain 当前加载的程序集中找到它。因此,您的 IRequestHandler 将不会被注册(也不会在该项目中注册任何其他 MediatR 基类型)。

此问题的解决方案是确保在调用 AddMediatR() 之前 加载包含您希望在 MediatR 容器中注册的类型的程序集。

您可以执行以下任一操作:

  • Manually load 你引用的程序集
  • 从您的 MyApi2 项目
  • 中引用位于您的 MyInfra 项目中的 type/function

后一种选择是最典型的,因为您 通常 有一些功能位于您要调用的引用程序集中(而不是仅仅有一个程序集包含类型)。

无论选择哪个选项,请确保在添加 MediatR 之前执行此操作。否则你将 运行 陷入同样的​​问题。