【问题标题】:How do you avoid lots of [FromService] parameters with ASP.NET Core dependency injection?如何使用 ASP.NET Core 依赖注入避免大量 [FromService] 参数?
【发布时间】:2017-01-25 01:21:18
【问题描述】:

我有一个使用大量依赖注入的 ASP.NET Core 项目。

问题是这些开始叠加在我的控制器操作上:

public async Task LoginAsync(
    [FromBody] LoginModel login,
    [FromServices] IConnectionMultiplexer redis,
    [FromServices] ISerialiserFactory serialiser,
    [FromServices] IDataService dataService,
    [FromServices] ILookupNormalizer normaliser,
    [FromServices] IPasswordHasher hasher,
    ...

我可以将它们放在构造函数中,但大多数方法不使用它们,并且那些使用它们的方法并不总是使用它们。

我可以直接实例化它们,但是我失去了在启动时注入它们的能力。

有没有更简单的方法来获取这些注入的服务?理想情况下,我想这样称呼:

// It turns out I need the injected serialiser
var serialiser = services.Get<ISerialiserFactory>();

在 ASP.NET Core 中有没有办法做到这一点?

【问题讨论】:

  • 如果大多数方法不使用这些依赖项,也许值得将您的控制器分成两个控制器?
  • @SergeyBerezovskiy 很有可能,但这会导致我的大多数控制器在需要所有这些服务时被拆分用于极端情况(可能是 1% 的请求)。
  • @Keith:重新考虑你的软件设计怎么样?如果你在一个动作中需要像IPasswordHasher 这样的服务,这意味着你的抽象很糟糕。控制器和动作应该只协调而不执行任何业务逻辑!将您的 Login 方法重构为服务,将依赖项注入新服务并将 ONE 服务注入您的操作/控制器。这篇博文可能对 Mark Seemann 有用,他已经写了一本关于 DI 的综合书籍;)blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices 或在 SO 上查看他的答案
  • 也不要使用var serialiser = services.Get&lt;ISerialiserFactory&gt;();,它是服务定位器模式和反模式。难以测试/模拟
  • @Tseng 是的,LoginAsync 调用实际包含业务逻辑的方法 - 一个新服务是我真正想要的,但它需要一些单例服务和其他存在那个行动。这就是为什么我确定这些 [FromService] 参数堆栈是错误的,但完全不清楚如何通过业务逻辑获取这些参数。

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


【解决方案1】:

正如 cmets 中所指出的,如果您在单个控制器操作中有如此多的依赖项,那么它是对糟糕抽象代码的一个很好的叹息:您的控制器做得比它应该做的更多。

理想情况下,控制器操作应该只是每个操作几行代码(经验法则,10-15 行代码)。如果你有更多,你可能在里面做了很多事情。

控制器操作应该只接受来自用户的输入(表单或 WebApi-esque),对其进行验证并将其委托给服务以及处理 http 状态代码。

public class LoginService : ILoginService
{
    public IConnectionMultiplexer redis,
    public ISerialiserFactory serialiser,
    public IDataService dataService,
    public ILookupNormalizer normaliser,
    public IPasswordHasher hasher

    public LoginService(/* inject your services here */) 
    {

    }

    public async Task<bool> Login(LoginModel login) 
    {
        // Do your logic here and perform the login

        return /*true or false*/;
    }
}

然后将其注入您的控制器或您的操作:

[HttpPost]
public async Task<IActionResult> LoginAsync([FromBody]LoginModel login, [FromServices]ILoginService loginService) 
{
    // Validate input, only rough validation. No business validation here
    if(!Model.IsValid) 
    {
        return BadRequest(Model);
    }

    bool success = await loginService.Login(model);

    if(success) 
    {
        return RedirectTo("Login");
    }

    return Unauthorized();
}

如果你得到的代码比这多,那就是代码异味。特别是如果你做一些逻辑等。你的控制器应该尽可能薄。控制器很难测试(与我的示例中的ILoginService 相比)。

您永远不必在任何时候调用new LoginService(...)(除非您创建了一个抽象工厂)。

此外,您应该始终更喜欢使用构造函数注入。仅在一项操作中需要服务时使用[FromServices]。如果在多个操作中需要它,请始终使用构造函数注入

public LoginController : Controller
{
    public ILoginService loginService;

    public LoginController(ILoginService loginService)
    {
        if(loginService==null)
            throw new ArgumentNullException(nameof(loginService));

        this.loginService = loginService
    }

    public async Task<IActionResult> LoginAsync([FromBody]LoginModel login)
    {
        // Do your stuff from above
        ...
        bool success = await loginService.Login(login);
        ...
    }
}

如果依赖项有不同的生命周期也没有问题,只要主对象的生命周期比它的依赖项的生命周期短。

即如果您的上述依赖项之一是作用域的,那么您的 ILoginService 也必须是作用域的。它将在请求结束时处理。

services.AddSingleton<ISerialiserFactory, ...>();
services.AddSingleton<IConnectionMultiplexer, ...>();
services.AddScoped<IDataService, ...>();
services.AddScoped<ILookupNormalizer, ...>();
services.AddScoped<IPasswordHasher, ...>();
services.AddScoped<ILoginService, LoginService>();

这样就可以了。

services.AddSingleton<ISerialiserFactory, ...>();
services.AddSingleton<IConnectionMultiplexer, ...>();
services.AddScoped<IDataService, ...>();
services.AddScoped<ILookupNormalizer, ...>();
services.AddScoped<IPasswordHasher, ...>();

// This will create trouble
services.AddSingleton<ILoginService, LoginService>();

但这不会。现在, ILoginService 将是单例的,但它的依赖项将在第一次请求后被释放。后续请求在调用IDataServiceIPasswordHasher...“xyz 已被处理”时会触发异常。

【讨论】:

  • 干杯(+1 和回答)看起来正是我所缺少的。
猜你喜欢
  • 1970-01-01
  • 2018-09-03
  • 2016-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-24
  • 1970-01-01
相关资源
最近更新 更多