anton.a.popov
anton.a.popov

Reputation: 1

Autofac's Contextual binding not working in ASP.Core 2.0

Recently I had to take advantage of the contextual binding that Autofac supports but somehow in ASP.NET Core 2.0 it's just not working. I followed the example (options 2 and 3) on the How do I pick a service implementation by context? FAQ page, however I managed to make it work only in ASP.NET MVC 5 and ASP.NET WEB API 2 projects. Example:

public interface ISender
{
  void Send(Destination dest, Content content);
}

// We can implement the interface for different
// "sending strategies":
public class PostalServiceSender : ISender { ... }
public class EmailNotifier : ISender { ... }

public class ShippingProcessor
{
  public ShippingProcessor(ISender shippingStrategy) { ... }
}

public class CustomerNotifier
{
  public CustomerNotifier(ISender notificationStrategy) { ... }
}

The following binding configuration failed to work:

var builder = new ContainerBuilder();

builder.RegisterType<PostalServiceSender>()
       .As<ISender>()
       .AsSelf();
builder.RegisterType<EmailNotifier>()
       .As<ISender>()
       .AsSelf()
       .InstancePerLifetimeScope();

builder.RegisterType<DefaultSender>()
           .As<ISender>()
           .InstancePerLifetimeScope();

builder.RegisterType<ShippingController>()
       .WithParameter(
         new ResolvedParameter(
           (pi, ctx) => pi.ParameterType == typeof(ISender),
           (pi, ctx) => ctx.Resolve<PostalServiceSender>()))
           .InstancePerLifetimeScope();
builder.RegisterType<CustomersController>();
       .WithParameter(
         new ResolvedParameter(
           (pi, ctx) => pi.ParameterType == typeof(ISender),
           (pi, ctx) => ctx.Resolve<EmailNotifier>()))
           .InstancePerLifetimeScope();
var container = builder.Build();

What is expected from the above binding configurations is the DefaultSender class to be the the default implementation of ISender. Only PostalServiceSender and CustomerNotifier are configured to receive a different implementation of ISender. Unfortunately the DefaultSender implementation is passed anywhere ISender is requested. Has anyone experienced such behavior in .NET Core 2.0?

Upvotes: 0

Views: 181

Answers (1)

Cyril Durand
Cyril Durand

Reputation: 16187

Your configuration is great and everything should work fine but for ASP.net core controller.

Controllers in ASP.net core are not created by Autofac but byb ASP.net core :

By default, ASP.NET Core will resolve the controller parameters from the container but doesn’t actually resolve the controller from the container. This usually isn’t an issue but it does mean:

  • the lifecycle of the controller is handled by the framework, not the request lifetime.
  • The lifecycle of controller constructor parameters is handled by the request lifetime.
  • Special wiring that you may have done during registration of the controller (like setting up property injection) won’t work.

    You can change this by specifying AddControllersAsServices() when you register MVC with the service collection. Doing that will automatically register controller types into the IServiceCollection when you call builder.Populate(services).

    >> http://autofac.readthedocs.io/en/latest/integration/aspnetcore.html#controllers-as-services

  • All you have to do is to call AddControllersAsServices

    // Add controllers as services so they'll be resolved.
    services.AddMvc().AddControllersAsServices();
    

    BTW even if your code works, it is not the best practice to register the same service multiple time and rely on default order.

    builder.RegisterType<PostalServiceSender>()
           .Keyed<ISender>("postal");
    builder.RegisterType<EmailNotifier>()
           .Keyed<ISender>("email")
           .InstancePerLifetimeScope();
    builder.RegisterType<DefaultSender>()
           .As<ISender>()
           .InstancePerLifetimeScope();       
    

    and your controller like this :

    builder.RegisterType<ShippingController>()
           .WithParameter(
             new ResolvedParameter(
               (pi, ctx) => pi.ParameterType == typeof(ISender),
               (pi, ctx) => ctx.ResolveNamed<ISender>("postal")))
           .InstancePerLifetimeScope();
    builder.RegisterType<CustomersController>()
           .WithParameter(
             new ResolvedParameter(
               (pi, ctx) => pi.ParameterType == typeof(ISender),
               (pi, ctx) => ctx.ResolveNamed<ISender>("email")))
           .InstancePerLifetimeScope();
    

    Upvotes: 2

    Related Questions