Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 864
Caster Troy Avatar asked Dec 11 '14 15:12

Caster Troy


2 Answers

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.

like image 140
Mickaël Derriey Avatar answered Sep 18 '22 14:09

Mickaël Derriey


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);
like image 29
user2532197 Avatar answered Sep 21 '22 14:09

user2532197