如何在 ASP MVC 5 中将 MediatR 与 Autofac 一起使用?

Posted

技术标签:

【中文标题】如何在 ASP MVC 5 中将 MediatR 与 Autofac 一起使用?【英文标题】:How do I use MediatR with Autofac in ASP MVC 5? 【发布时间】:2015-02-10 03:58:00 【问题描述】:

作者提供了一个exampleMediatR如何在使用Autofac的控制台应用程序中使用:

var builder = new ContainerBuilder();
builder.RegisterSource(new ContravariantRegistrationSource());
builder.RegisterAssemblyTypes(typeof (IMediator).Assembly).AsImplementedInterfaces();
builder.RegisterAssemblyTypes(typeof (Ping).Assembly).AsImplementedInterfaces();
builder.RegisterInstance(Console.Out).As<TextWriter>();

var lazy = new Lazy<IServiceLocator>(() => new AutofacServiceLocator(builder.Build()));
var serviceLocatorProvider = new ServiceLocatorProvider(() => lazy.Value);
builder.RegisterInstance(serviceLocatorProvider);

我举了这个例子并尝试让它与 ASP MVC 5 和 Autofac.Mvc5 包一起工作:

var builder = new ContainerBuilder();
builder.RegisterSource(new ContravariantRegistrationSource());
builder.RegisterAssemblyTypes(typeof(IMediator).Assembly).AsImplementedInterfaces();
builder.RegisterAssemblyTypes(typeof(AddPostCommand).Assembly).AsImplementedInterfaces();
builder.RegisterControllers(typeof(HomeController).Assembly);
var container = builder.Build();
var lazy = new Lazy<IServiceLocator>(() => new AutofacServiceLocator(container));
var serviceLocatorProvider = new ServiceLocatorProvider(() => lazy.Value);
builder.RegisterInstance(serviceLocatorProvider);
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

当我运行 Web 应用程序时,我收到一个错误页面,告诉我 ServiceLocationProvider 依赖项尚未注册。我究竟做错了什么?

怀疑问题是由于我正在注册ServiceLocatorProvider 实例之后调用Build - 在作者的示例中,@987654329由于Lazy&lt;&gt;,@ 方法随后被调用。不过,我不知道如何解决这个问题。

【问题讨论】:

【参考方案1】:

我在正确注册 MediatorServiceLocatorProvider 类时遇到问题。 我拉了一会儿头发,但最终设法绕过它。

我的错误是在根 Autofac 容器中注册 ServiceLocatorProvider,如下所示:

var lazyContainer = new Lazy<IContainer>(() => builder.Build());
builder.Register(x => new ServiceLocatorProvider(() => new AutofacServiceLoator(lazyContainer.Value)));

DependencyResolver.SetCurrent(new AutofacDependencyResolver(lazyContainer.Value);

在运行时,Autofac 抛出异常,因为我的 Request 之一依赖于我配置为由 HTTP 请求限定范围的 EF DbContext

诀窍是针对当前的 HTTP 请求 ILifetimeScope 注册 ServiceLocatorProvider。 值得庆幸的是,来自 Autofac 的错误消息是不言自明的:

No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance
was requested. This generally indicates that a component registered as per-HTTP request is being
requested by a SingleInstance() component (or a similar scenario.) Under the web integration
always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime,
never from the container itself.

发生这种情况是因为 AutofacServiceLocator 是由根容器提供的。 容器在请求解析 DbContext 时,不知道与当前 HTTP 请求相关联的内部范围。

使用 JustDecompile,我认为唯一实现 ILifetimeScopeProvider 接口的类是 AutofacDependencyResolver,您可以使用静态属性 AutofacDependencyResolver.Current 访问当前实例。

您可以按照错误消息中的说明使用AutofacDependencyResolver.Current.RequestLifetimeScope 访问当前的ILifetimeScope,因此最终注册如下所示:

builder
    .Register(x => new ServiceLocatorProvider(() => new AutofacServiceLocator(AutofacDependencyResolver.Current.RequestLifetimeScope)))
    .InstancePerHttpRequest();

InstancePerHttpRequest() 部分是可选的,但由于在整个 HTTP 请求期间ILifetimeScope 将是相同的,这会阻止 Autofac 创建 n 个 AutofacServiceLocator 实例。

希望这很清楚,如果您觉得有必要,请随时进行一些修改,因为我很难清楚地解释这一点。

【讨论】:

【参考方案2】:

我正在使用 Webapi 2 + Autofac + OWIN 并设法让它工作。这是我的代码:

这是我的 autofac 构造函数

        //Constructor
        var builder = new ContainerBuilder();

        builder.RegisterSource(new ContravariantRegistrationSource());
        builder.RegisterAssemblyTypes(typeof(IMediator).Assembly).AsImplementedInterfaces(); 
        builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).AsImplementedInterfaces();

        // Register Web API controller in executing assembly.
        builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).InstancePerRequest();


        var lazyContainer = new Lazy<IContainer>(() => builder.Build());
        var serviceLocatorProvider = new ServiceLocatorProvider(() => new AutofacServiceLocator(lazyContainer.Value));
        builder.RegisterInstance(serviceLocatorProvider);
        config.DependencyResolver = new AutofacWebApiDependencyResolver(lazyContainer.Value);


        // This should be the first middleware added to the IAppBuilder.
        app.UseAutofacMiddleware(lazyContainer.Value);

        // Make sure the Autofac lifetime scope is passed to Web API.
        app.UseAutofacWebApi(config);

这是我的命名空间:

using System; 
using System.Reflection;
using System.Web.Http;
using Autofac;
using CommonServiceLocator.AutofacAdapter.Unofficial;
using Autofac.Features.Variance;
using Autofac.Integration.WebApi;
using MediatR;
using Microsoft.Practices.ServiceLocation;
using Owin;

一切正常,不必显式注册每个 requestHandler 或 CommandHandler。由于我也浪费了很多时间来讨论它,我希望它能帮助遇到同样问题的其他人。过去的答案有助于找到这个答案。

更新:

好吧,我只是重构代码以删除所有惰性绑定,使其更简单。变化如下:

代替:

        var lazyContainer = new Lazy<IContainer>(() => builder.Build());
        var serviceLocatorProvider = new ServiceLocatorProvider(() => new AutofacServiceLocator(lazyContainer.Value));
        builder.RegisterInstance(serviceLocatorProvider);

只需使用:

        builder.RegisterType<AutofacServiceLocator>().AsImplementedInterfaces();
        var container = builder.Build();

        //ServiceLocator.SetLocatorProvider(serviceLocatorProvider);            
        config.DependencyResolver = new AutofacWebApiDependencyResolver(container);

【讨论】:

【参考方案3】:

在完成注册类型之前,您不能调用 Builder.Build。

在您的示例中,您在调用 builder.RegisterInstance 之前调用了 Builder.Build,这解释了为什么它无法在运行时推断类型。

我在今天遇到同样的问题后想出了这个,但它正在进行中,因为它还没有解决我的实现......

builder.RegisterSource(new ContravariantRegistrationSource());
        builder.RegisterAssemblyTypes(typeof(IMediator).Assembly).AsImplementedInterfaces();
        builder.RegisterAssemblyTypes(typeof(HomePageThumbnail).Assembly).AsImplementedInterfaces();

        var lifetimeScope = new Lazy<ILifetimeScope>(() => builder.Build());
        var lazy = new Lazy<IServiceLocator>(() => new AutofacServiceLocator(lifetimeScope.Value));

        var serviceLocatorProvider = new ServiceLocatorProvider(() => lazy.Value);
        builder.RegisterInstance(serviceLocatorProvider);


        DependencyResolver.SetResolver(new AutofacDependencyResolver(lifetimeScope.Value));

        app.UseAutofacMiddleware(lifetimeScope.Value);
        app.UseAutofacMvc();

我在单独的 Lazy&lt;T&gt; 中创建容器并传递它。

在构建和加载站点时,它无法推断在我的控制器操作中使用哪个处理程序来处理请求...

var response = _mediator.Send(new RecentActivityThumbnailsQuery());

引发异常...

    An exception of type 'Microsoft.Practices.ServiceLocation.ActivationException' occurred in Microsoft.Practices.ServiceLocation.dll but was not handled in user code

Additional information: Activation error occurred while trying to get instance of type IRequestHandler`2, key ""

这是使用 MediatR 中包含的 Autofac 示例项目中提供的相同的基于约定的注册,我也尝试过显式注册它....

builder.RegisterType<RecentActivityThumbnailsHandler>().As<IRequestHandler<RecentActivityThumbnailsQuery, RecentActivityThumbnailsResults>>();

当我得到它的工作时,我会更新。如果您在我做之前弄清楚,请做同样的事情。

【讨论】:

以上是关于如何在 ASP MVC 5 中将 MediatR 与 Autofac 一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Asp.Net Core Mvc 5.0 中将 sql 数据库与 ado.net 连接?

在 ASP.Net MVC 5 中将 javascript 数组从视图传递到控制器

如何在 ASP.NET MVC 应用程序中将图像上传到 cloudinary?

如何在 ASP.Net MVC 中将列表对象显示到视图中?

如何在 asp.net mvc4 中将 OpenID 迁移到 OAuth

如何在asp.net mvc中将Webcontrol表导出到excel