How do I use MediatR with Autofac in ASP MVC 5?

5.2k views Asked by At

The author provides an example of how to use MediatR in a console application using 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);

I took this example and attempted to make it work with ASP MVC 5 and the Autofac.Mvc5 package:

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));

When I run the web application, I get an error page telling me that the ServiceLocationProvider dependency has not been registered. What am I doing wrong?

I suspect that the problem is due to the fact that I am registering the ServiceLocatorProvider instance after calling Build - in the author's example, the Build method is invoked afterwards thanks to Lazy<>. I do not know how to work around this, though.

3

There are 3 answers

0
Mickaël Derriey On

I had issues to properly register both the Mediator and ServiceLocatorProvider classes.
I pulled my hair for a while, but finally managed to get around it.

My mistake was to register ServiceLocatorProvider against the root Autofac container, like this:

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

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

At runtime, Autofac was throwing an exception because one of my Request had a dependency on my EF DbContext that I configured to be scoped by HTTP request.

The trick is to register the ServiceLocatorProvider against the current HTTP request ILifetimeScope.
Thankfully, the error message from Autofac is self explanatory:

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.

This happens because the AutofacServiceLocator was fed with the root container.
The container, when asking to resolve the DbContext, has no knowledge of an inner - associated with to the current HTTP request - scope.

Using JustDecompile, I sax that the only class implementing the ILifetimeScopeProvider interface was AutofacDependencyResolver, and you can access the current instance with the static property AutofacDependencyResolver.Current.

You can access the current ILifetimeScope by using AutofacDependencyResolver.Current.RequestLifetimeScope as explained in the error message, so in the end the registration looks like:

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

The InstancePerHttpRequest() part is optional but since the ILifetimeScope will be the same during the whole HTTP request, this prevents Autofac from creating n instances of AutofacServiceLocator.

Hope this was clear, don't hesitate to make some edits if you feel like it's necessary, because I have a hard time explaining this clearly.

0
Baldy On

You cannot call Builder.Build before you finish registering types.

in your example you are calling Builder.Build before you call builder.RegisterInstance which explains why it cannot infer the type at runtime.

I came up with this after hitting the same issue today, but it is work in progress as it doesnt resolve my implementations yet...

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();

I am creating the container in a separate Lazy<T> and passing that around.

While this builds and the site loads, it cannot infer which handler to use for the request in my controller action...

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

which raises the exception...

    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 ""

This is using the same convention based registration provided in the Autofac examples project included in MediatR, and i have also tried registering it explicitly....

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

I will update when i get it working. Please do the same if you figure it out before i do.

0
user2532197 On

I am using Webapi 2 + Autofac + OWIN and manage to get it working. Here is my code:

Here is my autofac Constructor

        //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);

Here are my namespaces:

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;

Everything worked fine and did not had to explict register every requestHandler or CommandHandler. Since I also lost a LOT of time to put it togueter I do hope that it will help others having the same issue. Past answers were helpfull to get to this one.

UPDATE:

Well, I just refactor de code to remove all the lazy binding making it much simpler. Bellow are the changes:

Instead of:

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

Just use :

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

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