ASP.net 核心 3.1,添加对控制器动作自动绑定 [FromBody] 参数的依赖注入支持
ASP.net core 3.1, add dependency injection support for controller actions auto bound [FromBody] parameters
我知道如何通过将服务添加到 IServiceprovider 然后框架为我处理它,并且在控制器操作的情况下我可以添加 [Microsoft.AspNetCore.Mvc.FromServices]
和它会将服务注入特定的操作。
但这需要我的控制器明确知道底层参数需要什么,我认为这是一种不必要且可能有害的耦合。
我想知道是否有可能接近以下内容:
[HttpPost]
public async Task<ActionResult> PostThings([FromBody]ParameterClassWithInjection parameter) {
parameter.DoStuff();
...}
public class ParameterClassWithInjection{
public readonly MyService _myService;
public ParameterClassWithInjection(IMyService service){ _myService = service;}
public void DoStuff(){ _myService.DoStuff(); }
}
我只在文档中找到了有关自定义模型活页夹的内容。
https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-3.1#custom-model-binder-sample
这显示了如何创建自定义绑定器并让自定义提供程序提供注入。
似乎我需要从自动绑定中实现大量样板代码(在每种情况下对我来说都非常好)以获得一些依赖注入。
如果这是唯一的选择,我希望你能为我指明更好的方向,或者让我的探索停止。
快捷方式
如果内容类型是 JSON 并且您正在使用 Newtonsoft.Json,您可以 deserialize your model with dependency injection 使用自定义合同解析器。
模型绑定
否则,如果您需要支持其他内容类型等,则需要采取复杂的方式。
特定型号,或仅限型号FromBody
:
使用模型绑定器并进行 属性 注射。参见 几周前。
对于通用模型或其他来源的模型:
您需要自定义模型活页夹提供程序。在模型活页夹提供程序中,您可以遍历现有的模型活页夹提供程序,找到当前模型绑定上下文的模型活页夹,然后用您自己的模型活页夹装饰器对其进行装饰,它可以为模型进行 DI。
例如:
public class DependencyInjectionModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
// Get MVC options.
var mvcOptions = context.Services.GetRequiredService<IOptions<MvcOptions>>();
// Find model binder provider for the current context.
IModelBinder binder = null;
foreach (var modelBinderProvider in mvcOptions.Value.ModelBinderProviders)
{
if (modelBinderProvider == this)
{
continue;
}
binder = modelBinderProvider.GetBinder(context);
if (binder != null)
{
break;
}
}
return binder == null
? null
: new DependencyInjectionModelBinder(binder);
}
}
// Model binder decorator.
public class DependencyInjectionModelBinder : IModelBinder
{
private readonly IModelBinder _innerBinder;
public DependencyInjectionModelBinder(IModelBinder innerBinder)
{
_innerBinder = innerBinder;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
await _innerBinder.BindModelAsync(bindingContext);
if (bindingContext.Result.IsModelSet)
{
var serviceProvider = bindingContext.HttpContext.RequestServices;
// Do DI stuff.
}
}
}
// Register your model binder provider
services.AddControllers(opt =>
{
opt.ModelBinderProviders.Insert(
0, new DependencyInjectionModelBinderProvider());
});
这适用于 属性 注入。
对于构造函数注入,因为模型的创建仍然发生在内部模型绑定器中,你肯定需要比这个例子更多的代码,而且我还没有尝试让构造函数注入在这种情况下工作。
后来我选择了做以下事情。
[HttpPost]
public async Task<ActionResult> PostThings([FromBody]ParameterClassWithInjection parameter, [FromServices] MyService) {
await MyService.DoStuff(parameter);
...}
服务注入 api 操作的位置。
然后我选择了非常小的服务,每个请求一个,以保持非常分散。
如果这些服务随后需要一些共享代码,比方说来自存储库,那么我只需将其注入到这些较小的服务中。
好处包括;在单元测试中模拟和测试非常容易,并且在不影响其他操作的情况下保持简单的更改,因为它非常明确地声明此“服务”/请求仅使用一次。
缺点是您必须创建很多 类。
通过良好的文件夹结构,您可以减轻一些概览负担。
- MyControllerFolder
-Controller
-Requests
- MyFirstRequsetFolder
- Parameter.cs
- RequestService.cs
```
我知道如何通过将服务添加到 IServiceprovider 然后框架为我处理它,并且在控制器操作的情况下我可以添加 [Microsoft.AspNetCore.Mvc.FromServices]
和它会将服务注入特定的操作。
但这需要我的控制器明确知道底层参数需要什么,我认为这是一种不必要且可能有害的耦合。
我想知道是否有可能接近以下内容:
[HttpPost]
public async Task<ActionResult> PostThings([FromBody]ParameterClassWithInjection parameter) {
parameter.DoStuff();
...}
public class ParameterClassWithInjection{
public readonly MyService _myService;
public ParameterClassWithInjection(IMyService service){ _myService = service;}
public void DoStuff(){ _myService.DoStuff(); }
}
我只在文档中找到了有关自定义模型活页夹的内容。 https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-3.1#custom-model-binder-sample
这显示了如何创建自定义绑定器并让自定义提供程序提供注入。 似乎我需要从自动绑定中实现大量样板代码(在每种情况下对我来说都非常好)以获得一些依赖注入。
如果这是唯一的选择,我希望你能为我指明更好的方向,或者让我的探索停止。
快捷方式
如果内容类型是 JSON 并且您正在使用 Newtonsoft.Json,您可以 deserialize your model with dependency injection 使用自定义合同解析器。
模型绑定
否则,如果您需要支持其他内容类型等,则需要采取复杂的方式。
特定型号,或仅限型号FromBody
:
使用模型绑定器并进行 属性 注射。参见
对于通用模型或其他来源的模型:
您需要自定义模型活页夹提供程序。在模型活页夹提供程序中,您可以遍历现有的模型活页夹提供程序,找到当前模型绑定上下文的模型活页夹,然后用您自己的模型活页夹装饰器对其进行装饰,它可以为模型进行 DI。
例如:
public class DependencyInjectionModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
// Get MVC options.
var mvcOptions = context.Services.GetRequiredService<IOptions<MvcOptions>>();
// Find model binder provider for the current context.
IModelBinder binder = null;
foreach (var modelBinderProvider in mvcOptions.Value.ModelBinderProviders)
{
if (modelBinderProvider == this)
{
continue;
}
binder = modelBinderProvider.GetBinder(context);
if (binder != null)
{
break;
}
}
return binder == null
? null
: new DependencyInjectionModelBinder(binder);
}
}
// Model binder decorator.
public class DependencyInjectionModelBinder : IModelBinder
{
private readonly IModelBinder _innerBinder;
public DependencyInjectionModelBinder(IModelBinder innerBinder)
{
_innerBinder = innerBinder;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
await _innerBinder.BindModelAsync(bindingContext);
if (bindingContext.Result.IsModelSet)
{
var serviceProvider = bindingContext.HttpContext.RequestServices;
// Do DI stuff.
}
}
}
// Register your model binder provider
services.AddControllers(opt =>
{
opt.ModelBinderProviders.Insert(
0, new DependencyInjectionModelBinderProvider());
});
这适用于 属性 注入。
对于构造函数注入,因为模型的创建仍然发生在内部模型绑定器中,你肯定需要比这个例子更多的代码,而且我还没有尝试让构造函数注入在这种情况下工作。
后来我选择了做以下事情。
[HttpPost]
public async Task<ActionResult> PostThings([FromBody]ParameterClassWithInjection parameter, [FromServices] MyService) {
await MyService.DoStuff(parameter);
...}
服务注入 api 操作的位置。
然后我选择了非常小的服务,每个请求一个,以保持非常分散。 如果这些服务随后需要一些共享代码,比方说来自存储库,那么我只需将其注入到这些较小的服务中。
好处包括;在单元测试中模拟和测试非常容易,并且在不影响其他操作的情况下保持简单的更改,因为它非常明确地声明此“服务”/请求仅使用一次。
缺点是您必须创建很多 类。 通过良好的文件夹结构,您可以减轻一些概览负担。
- MyControllerFolder
-Controller
-Requests
- MyFirstRequsetFolder
- Parameter.cs
- RequestService.cs
```