【问题标题】:How to configure services based on request in ASP.NET Core如何在 ASP.NET Core 中根据请求配置服务
【发布时间】:2019-01-08 14:35:50
【问题描述】:

在 ASP.NET Core 中,我们可以在启动期间注册所有依赖项,这些依赖项在应用程序启动时执行。然后将注册的依赖注入到控制器构造函数中。

public class ReportController
{
    private IReportFactory _reportFactory;

    public ReportController(IReportFactory reportFactory)
    {
        _reportFactory = reportFactory;
    }

    public IActionResult Get()
    {
        vart report = _reportFactory.Create();
        return Ok(report);
    }
}

现在我想根据当前请求中的数据(用户授权级别或随请求传递的查询字符串中的某个值)注入 IReportFactory 的不同实现。

问题:ASP.NET Core 中是否有任何内置的抽象(中间件),我们可以在其中注册另一个接口实现?

如果没有内置功能,可能的方法是什么?

更新IReportFactory 接口为例。实际上,我在不同的地方注入了一堆低级接口。现在我希望根据请求数据注入这些低级接口的不同实现。

public class OrderController
{
    private IOrderService _orderService;

    public OrderController(IOrderService orderService)
    {
        _orderService = orderService;
    }

    public IActionResult Create()
    {
        var order = _orderService.Create();
        return Ok(order);
    }
}    

 public class OrderService
 {
    private OrderBuilder _orderBuilder;
    private IShippingService _shippingService; // This now have many different implementations

    public OrderService(
        OrderBuilder _orderBuilder,
        IShippingService _shippingService)
    {
        _orderService = orderService;
        _shippingService = shippingService;
    }

    public Order Create()
    {
        var order = _orderBuilder.Build();
        var order.ShippingInfo = _shippingService.Ship();
        return order;
    }
 }  

因为我们知道我们需要在应用程序的入口点使用哪个实现(我认为控制器操作可以被视为应用程序的入口点),我们希望已经在那里注入正确的实现 - 不需要对已经存在的设计进行任何更改。

【问题讨论】:

  • 你已经有工厂了。使用带有参数的工厂方法Create 来检索特定实例。如果您需要不同的工厂工厂(为什么?),那么您可以使用抽象工厂模式。
  • 你可以注册同一个接口的多个实现,你会得到一个IEnumerable<IInterface>注入。

标签: c# asp.net-core dependency-injection


【解决方案1】:

不,你不能。 IServiceCollection 在应用程序启动期间填充,并在调用 Configure 方法之前构建。之后(正在构建容器),注册不能再更改。

但是,您可以实现抽象工厂,无论是作为工厂方法还是作为接口/类。

// Its required to register the IHttpContextAccessor first
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IReportService>(provider => {
    var httpContext = provider.GetRequired<IHttpContextAccessor>().HttpContext;

    if(httpContext.User.IsAuthorized) 
    {
        return new AuthorizedUserReportService(...);
        // or resolve it provider.GetService<AuthorizedUserReportService>()
    }

    return new AnonymousUserReportService(...);
    // or resolve it provider.GetService<AnonymousUserReportService>()
});

或者使用abstract factory class

【讨论】:

  • 如果创建AuthorizedUserReportService 实现IDisposed,ASP.NET Core会在退出范围时自动处理它还是应该手动完成(如何?)?
  • 应该可以,但只能说一次看代码。但是我现在正在上班的路上
  • 是的,它应该处理掉。毕竟它是一个工厂方法,它只告诉它是如何实例化的,而不是它的生命周期(这就是注册方法的作用,AddScoped/AddTransient/AddSingleton
  • 部分 DI 框架可以在构建 Service Provider 后进行注册。然后您可以在中间件中注册新的依赖项。例如,DryIoC 可以做到这一点。不过,这需要对现有项目进行重大更改。
  • 请注意:使用 services.AddHttpContextAccessor();扩展方法而不是 services.AddSingleton();
【解决方案2】:

恐怕你不能通过简单的依赖注入直接达到目标,因为依赖注入是在启动阶段配置的,也就是说,所有的服务和实现在请求来之前就已经配置好了。

但是,您可以注入一个 Create Service 委托,以便我们可以在运行时创建所需的服务实现实例。

例如,如果我们有一个IReportFactory 接口和两个实现:

public interface IReportFactory
{
    object Create();
}

public class ReportFactory1 : IReportFactory
{
    public object Create()
    {
        return new { F = 1, };
    }
}
public class ReportFactory2 : IReportFactory {
    public object Create()
    {
        return new { F = 2, }; 
    }
}

由于我们希望将来获得所需的实现,我们需要先注册实现。

services.AddScoped<ReportFactory1>();
services.AddScoped<ReportFactory2>();

这就是神奇发生的地方:

  1. 我们不注册IReportFactory
  2. 我们只是添加一个Func&lt;HttpContext,IReportFactory&gt;,它是一个CreateReportFactoryDelegate

    公共委托 IReportFactory CreateReportFactoryDe​​legate(Microsoft.AspNetCore.Http.HttpContext context);

我们也需要将 CreateReportFactoryDe​​legate 添加到服务中。

services.AddScoped<CreateReportFactoryDelegate>(sp => {
    // return the required implemention service by the context;
    return context => {
        // now we have the http context ,
        // we can decide which factory implemention should be returned;
        // ...
        if (context.Request.Path.ToString().Contains("factory1")) {
            return sp.GetRequiredService<ReportFactory1>();
        }
        return sp.GetRequiredService<ReportFactory2>();
    };
});

现在,我们可以将CreateReportFactoryDelegate 注入控制器:

public class HomeController : Controller
{
    private CreateReportFactoryDelegate _createReportFactoryDelegate;

    public HomeController(CreateReportFactoryDelegate createDelegate) {
        this._createReportFactoryDelegate = createDelegate;
        // ...
    }

    public async Task<IActionResult> CacheGetOrCreateAsync() {

        IReportFactory reportFactory = this._createReportFactoryDelegate(this.HttpContext);
        var x=reportFactory.Create();

        // ...
        return View("Cache", cacheEntry);
    }
}

【讨论】:

    【解决方案3】:

    可以在 Startup.cs 中使用 HttpContextAccessor

    services.AddHttpContextAccessor();
    services.AddScoped<IYourService>(provider =>
                {
                    var contextAccessor = provider.GetService<IHttpContextAccessor>();
                    var httpContext = contextAccessor.HttpContext;
                    
                    var contextVariable = httpContext. ...
                    
                    // Return implementation of IYourService that corresponds to your contextVariable
    
                  
                });
    

    【讨论】:

      猜你喜欢
      • 2018-03-09
      • 2021-08-08
      • 2019-12-09
      • 1970-01-01
      • 2020-12-20
      • 2017-08-21
      • 1970-01-01
      • 1970-01-01
      • 2017-07-21
      相关资源
      最近更新 更多