我有一个使用大量依赖注入的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中执行此操作?
答案 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将是单例,但它的依赖关系将在第一个请求之后被释放。当调用IDataService
或IPasswordHasher
时,后续请求将触发异常......“xyz已被处置。”