thắc mắc [.NET] Đăng ký MediatR trong Autofac chỉ nhận được 1 trong 2 project

CzeK

Đã tốn tiền
Cấu trúc folder và sơ đồ architecture của dự án e đang làm (create from scratch) như sau

1645182436920.png

1645182446772.png


Nôm na là clean architecture + cqrs pattern
Trong dự án e sẽ không dùng built-in IoC container của .net mà e sẽ dùng Autofac vì nó có một số feature sau này e sẽ cần mà cái IoC của .NET không có và khách hàng cũng yêu cầu e dùng Autofac.
Trong này e có sử dụng MediatR để gửi các command/query request từ controller xuống và nhận kết quả trả về mà không cần quan tâm nhiều tới bên dưới nữa. E có config DI cho mọi thứ khác và đã chạy hoàn toàn ổn định tuy nhiên e có gặp phải 1 vấn đề với MediatR mà không biết sửa như thế nào.

Đây là MediatRModule.cs e dùng để define MediatR mà e chuẩn bị đăng ký vào Autofac container

C#:
using Autofac;
using Infrastructure.IoC.Outbox;
using MediatR;
using MediatR.Pipeline;
using System.Reflection;

namespace Infrastructure.IoC
{
    public class MediatRModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterAssemblyTypes(typeof(IMediator).GetTypeInfo().Assembly).AsImplementedInterfaces();
            var mediatrOpenTypes = new[]
            {
                typeof(IRequestHandler<,>),
                typeof(INotificationHandler<>),
            };

            foreach (var mediatrOpenType in mediatrOpenTypes)
            {
                builder
                    .RegisterAssemblyTypes(Assemblies.Application, ThisAssembly)
                    .AsClosedTypesOf(mediatrOpenType)
                    .AsImplementedInterfaces();
            }


            builder.RegisterGeneric(typeof(RequestPostProcessorBehavior<,>)).As(typeof(IPipelineBehavior<,>));
            builder.RegisterGeneric(typeof(RequestPreProcessorBehavior<,>)).As(typeof(IPipelineBehavior<,>));

            builder.Register<ServiceFactory>(ctx =>
            {
                var c = ctx.Resolve<IComponentContext>();
                return t => c.Resolve(t);
            });
        }
    }
}

Startup.cs để đăng ký MediatRModule vào Autofac container.
Lưu ý: e có comment 2 dòng services.AddMediatR vì cái này dùng container của .net chứ không phải Autofac. E sẽ nhắc lại cái này sau.

builder.RegisterModule(new MediatRModule());

C#:
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Infrastructure.IoC;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;

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

        public IConfiguration Configuration { get; }
        public ILifetimeScope AutofacContainer { get; private set; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" });
            });

            //services.AddMediatR(typeof(MediatRQueryEntryPoint).Assembly);
            //services.AddMediatR(typeof(MediatRCommandEntryPoint).Assembly);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1 v1"));
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();
        }

        public void ConfigureContainer(ContainerBuilder builder)
        {
            builder.RegisterModule(new MediatRModule());
            builder.RegisterModule(new EntityFrameworkCoreModule(Configuration));
            builder.RegisterModule(new ProcessingModule());
        }
    }
}

Program.cs (theo docs của Autofac)

C#:
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;

namespace Web
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseServiceProviderFactory(new AutofacServiceProviderFactory())
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

Mọi thứ chạy có vẻ ổn tuy nhiên:
  • Khi e dùng controller để gửi "Command" request bằng MediatR thì mọi thứ chạy ổn
  • Khi e dùng controller để gửi "Query" request bằng MediatR thì bùm, quăng lỗi tùm lum:

Lỗi của MediatR quăng:
- Error constructing handler for request of type: MediatR.IRequestHandler<GetContractorByIdQuery, GetContractorByIdResult>

Lỗi của Autofac quăng:
- MediatR.IRequestHandler<GetContractorByIdQuery, GetContractorByIdResult> has not been registered.

Cái error message cả 2 cùng quăng khi gửi "Query" request:
Bash:
System.InvalidOperationException: Error constructing handler for request of type MediatR.IRequestHandler`2[Query.Queries.ContractorQueries.GetContractorById.GetContractorByIdQuery,Query.Queries.ContractorQueries.GetContractorById.GetContractorByIdResult]. Register your handlers with the container. See the samples in GitHub for examples.
 ---> Autofac.Core.Registration.ComponentNotRegisteredException: The requested service 'MediatR.IRequestHandler`2[[Query.Queries.ContractorQueries.GetContractorById.GetContractorByIdQuery, Query, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[Query.Queries.ContractorQueries.GetContractorById.GetContractorByIdResult, Query, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
   at Autofac.ResolutionExtensions.ResolveService(IComponentContext context, Service service, IEnumerable`1 parameters)
   at Autofac.ResolutionExtensions.Resolve(IComponentContext context, Type serviceType, IEnumerable`1 parameters)
   at Autofac.ResolutionExtensions.Resolve(IComponentContext context, Type serviceType)
   at Infrastructure.IoC.MediatRModule.<>c__DisplayClass0_0.<Load>b__1(Type t) in C:\Users\phamd\Desktop\GitHub\Project\Infrastructure\IoC\MediatRModule.cs:line 35
   at MediatR.ServiceFactoryExtensions.GetInstance[T](ServiceFactory factory)
   at MediatR.Wrappers.HandlerBase.GetHandler[THandler](ServiceFactory factory)
   --- End of inner exception stack trace ---
   at MediatR.Wrappers.HandlerBase.GetHandler[THandler](ServiceFactory factory)
   at MediatR.Wrappers.RequestHandlerWrapperImpl`2.<>c__DisplayClass1_0.<Handle>g__Handler|0()
   at MediatR.Pipeline.RequestPreProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
   at MediatR.Pipeline.RequestPostProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
   at WebApplication1.Controllers.ContractorController.Get(String id) in C:\Users\phamd\Desktop\GitHub\Project\WebApplication1\Controllers\ContractorController.cs:line 32
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

HEADERS
=======
:authority: localhost:5001
:method: GET
:path: /api/Contractor/string
:scheme: https
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Host: localhost:5001
Referer: https://localhost:5001/swagger/index.html
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
sec-fetch-site: same-origin
sec-fetch-mode: cors
sec-fetch-dest: empty

Tuy nhiên, nếu e bỏ phần đăng ký của MediatR vào Autofac mà thay vào đó e sử dụng IoC built-in của .NET thì "Query" request chạy bình thường.

Startup.cs . E đã sử dụng 2 dòng services.AddMediatR và comment phần đăng ký MediatRModule vào Autofac.

services.AddMediatR(typeof(MediatRQueryEntryPoint).Assembly); services.AddMediatR(typeof(MediatRCommandEntryPoint).Assembly);

C#:
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Infrastructure.IoC;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;

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

        public IConfiguration Configuration { get; }
        public ILifetimeScope AutofacContainer { get; private set; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" });
            });

            services.AddMediatR(typeof(MediatRQueryEntryPoint).Assembly);
            services.AddMediatR(typeof(MediatRCommandEntryPoint).Assembly);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1 v1"));
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();
        }

        public void ConfigureContainer(ContainerBuilder builder)
        {
            //builder.RegisterModule(new MediatRModule());
            builder.RegisterModule(new EntityFrameworkCoreModule(Configuration));
            builder.RegisterModule(new ProcessingModule());
        }
    }
}

Lúc debug chạy chương trình thì nhận thấy là Autofac/MediatR nó có tìm thấy và đăng ký được cái CommandHandler như trong ảnh

1645183441956.png


Nhưng lại không tìm được các QueryHandler khác.

1645183463066.png


Theo e suy đoán thì do Autofac/MediatR sau khi tìm thấy được implementation của IRequestHandler nằm trong project Command thì nó đã ignore các project bên dưới Command và return về lập tức nên thành ra không đăng ký được các QueryHandler nằm trong project Query. Này chỉ là suy đoán của e , mấy thím nào có suggestion để cho Autofac/MediatR nhận cả 2 folder Command và Query thì thông não e với


---------------------------------------------------------------

Đã fix xong :)

Đoạn code này do e đăng ký assembly sai. Assemblies.Application + ThisAssembly đều chỉ trỏ về project Infrastructure nên nó chỉ scan đúng cái project Command xong trả về luôn.

C#:
foreach (var mediatrOpenType in mediatrOpenTypes)

            {

                builder

                    .RegisterAssemblyTypes(Assemblies.Application, ThisAssembly)

                    .AsClosedTypesOf(mediatrOpenType)

                    .AsImplementedInterfaces();

            }

Để nó nhận cả 2 project thì đổi thành

C#:
foreach (var mediatrOpenType in mediatrOpenTypes)

            {

                builder

                    .RegisterAssemblyTypes(
                        typeof(MediatRCommandEntryPoint).Assembly,
                        typeof(MediatRQueryEntryPoint).Assembly,
                        ThisAssembly
)

                    .AsClosedTypesOf(mediatrOpenType)

                    .AsImplementedInterfaces();

            }

MediatRCommandEntryPoint và MediatRQueryEntryPoint đều là các file trống nằm ở project Command/Query. Mục đích là để trỏ tới cái assembly implement IRequestHandler
 
Last edited:
Đặt gạch, lát tối đi chơi về vào chém gió. Ưng cho thím vì cái cách đặt câu hỏi và post bài có tâm như thế mới có hứng trả lời :sweet_kiss:

via theNEXTvoz for iPhone
 
Back
Top