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
     ```