如何使用ASP.NET Core依赖注入避免大量[FromService]参数?

时间:2017-01-24 16:31:53

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

我有一个使用大量依赖注入的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中执行此操作?

1 个答案:

答案 0 :(得分:3)

正如评论中所指出的,如果你在一个控制器动作中有如此多的依赖关系,它就会非常好地抽象出一个非常抽象的代码:你的控制器正在做的比它应该做的更多。

理想情况下,控制器操作应该是每个操作只需几行代码(经验法则,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已被处置。”