简体   繁体   English

ASP.net 核心 3.1,添加对 controller 动作自动绑定 [FromBody] 参数的依赖注入支持

[英]ASP.net core 3.1, add dependency injection support for controller actions auto bound [FromBody] parameters

I know how to inject into controller actions and the controller directly, by adding the service to the IServiceprovider and then the framework just handles it for me and in the case of controller actions I could add [Microsoft.AspNetCore.Mvc.FromServices] and it would inject the service to the specific action. I know how to inject into controller actions and the controller directly, by adding the service to the IServiceprovider and then the framework just handles it for me and in the case of controller actions I could add [Microsoft.AspNetCore.Mvc.FromServices] and it将服务注入特定操作。

But that requires my controller to know specifically what the underlying parameter would need, which is a coupling I find unnecessary and potentially harmful.但这需要我的 controller 明确知道底层参数需要什么,这是我认为不必要且可能有害的耦合。

I would like to know if it is possible to have something close to the following:我想知道是否有可能接近以下内容:

[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(); }
}

I have only found something about a custom model binder in the docs.我只在文档中找到了一些关于自定义 model 活页夹的信息。 https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-3.1#custom-model-binder-sample https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-3.1#custom-model-binder-sample

This shows how you can create a custom binder and have a custom provider supply the injection.这显示了如何创建自定义绑定器并让自定义提供程序提供注入。 It just seems I need to implement a lot of boilerplate code from the automatic binding (which works absolutely fine for me in every case) in order to get some dependency injection.似乎我需要从自动绑定中实现很多样板代码(在每种情况下对我来说都非常好),以便获得一些依赖注入。

I would hope you could point me in a better direction or put my quest to a rest if this is the only option.如果这是唯一的选择,我希望你能给我指出一个更好的方向,或者把我的任务放在 rest 上。

Shotcut截图

If the content type is JSON and you are using Newtonsoft.Json, you could deserialize your model with dependency injection using a custom contract resolver.如果内容类型是 JSON 并且您使用的是 Newtonsoft.Json,您可以使用自定义合同解析器通过依赖注入反序列化您的 model

Model binding Model 绑定

Otherwise, if you need to support other content types etc, you need to go a complex way.否则,如果您需要支持其他内容类型等,则需要以复杂的方式 go。

For specific model, or models only FromBody :对于特定 model 或仅型号FromBody

Use a model binder and do property injection.使用 model binder 并进行属性注入。 See my answer couple weeks ago.几周前见我的回答

For generic model or models from other sources:对于通用 model 或其他来源的型号:

You need a custom model binder provider.您需要一个自定义 model 活页夹提供程序。 In the model binder provider, you could iterate through existing model binder providers, find the model binder for the current model binding context, then decorate it with your own model binder decorator which can do DI for models. In the model binder provider, you could iterate through existing model binder providers, find the model binder for the current model binding context, then decorate it with your own model binder decorator which can do DI for models.

For example:例如:

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());
});

This works for property injection.这适用于属性注入。

For constructor injection, because creation of model still happens in the inner model binder, you definitely need more code than this example, and I haven't tried to get constructor injection working in this case.对于构造函数注入,由于 model 的创建仍然发生在内部 model 绑定程序中,因此您肯定需要比此示例更多的代码,并且我没有尝试在这种情况下使构造函数注入工作。

I later chose to do the following.我后来选择做以下事情。

[HttpPost]
public async Task<ActionResult> PostThings([FromBody]ParameterClassWithInjection parameter, [FromServices] MyService) {
await MyService.DoStuff(parameter);  
...}

Where the service is injected into the api action.将服务注入 api 操作的位置。

I then opted for having very small services, one per request to keep it very split up.然后我选择了非常小的服务,每个请求一个,以保持它非常分散。 If these services then needed some shared code, let's say from a repository then I simply injected that into these smaller services.如果这些服务需要一些共享代码,比如说来自存储库,那么我只需将其注入到这些较小的服务中。

Upsides includes;好处包括; it is very easy to mock and test in unit tests, and keeps changes simple without affecting other actions because it is very explicitly stated that this "service"/request is only used once.在单元测试中模拟和测试非常容易,并且在不影响其他操作的情况下保持简单的更改,因为它非常明确地声明此“服务”/请求仅使用一次。

Downside is that you have to create a lot of classes.缺点是你必须创建很多类。 With nice folder structuring you can mitigate some of the overview burden.通过良好的文件夹结构,您可以减轻一些概览负担。

 - MyControllerFolder
    -Controller
    -Requests
     - MyFirstRequsetFolder
       - Parameter.cs
       - RequestService.cs
     ```

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM