首页 > 解决方案 > 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?

标签: c#asp.net-core-mvcautofacasp.net-core-2.0asp.net-core-webapi

解决方案


您的配置很好,一切都应该正常工作,但对于 ASP.net 核心控制器。

ASP.net core 中的控制器不是由 ASP.net core 创建的,Autofac而是由 ASP.net core 创建的:

默认情况下,ASP.NET Core 将从容器中解析控制器参数,但实际上并不从容器中解析控制器。这通常不是问题,但它确实意味着:

  • 控制器的生命周期由框架处理,而不是请求生命周期。
  • 控制器构造函数参数的生命周期由请求生命周期处理。
  • 您在控制器注册期间可能进行的特殊接线(例如设置属性注入)将不起作用。

    您可以通过指定AddControllersAsServices()何时向服务集合注册 MVC 来更改此设置。这样做会IServiceCollection在您调用时自动将控制器类型注册到builder.Populate(services).

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

  • 你所要做的就是打电话AddControllersAsServices

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

    顺便说一句,即使您的代码有效,多次注册同一个服务并依赖默认顺序也不是最佳做法。

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

    和你的控制器是这样的:

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

    推荐阅读