【问题标题】:ASP.net core 3.1, add dependency injection support for controller actions auto bound [FromBody] parametersASP.net core 3.1,添加对控制器动作自动绑定[FromBody]参数的依赖注入支持
【发布时间】:2020-04-06 10:20:51
【问题描述】:

我知道如何直接注入控制器动作和控制器,方法是将服务添加到 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

这显示了如何创建自定义绑定器并让自定义提供程序提供注入。 似乎我需要从自动绑定中实现很多样板代码(在每种情况下对我来说都非常好),以便获得一些依赖注入。

如果这是唯一的选择,我希望你能给我指明一个更好的方向或停止我的追求。

【问题讨论】:

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


    【解决方案1】:

    截图

    如果内容类型是 JSON,并且您使用的是 Newtonsoft.Json,您可以使用自定义合约解析器 deserialize your model with dependency injection

    模型绑定

    否则,如果您需要支持其他内容类型等,则需要走复杂的路。

    特定型号,或仅型号FromBody

    使用模型绑定器并进行属性注入。几周前见my answer

    对于通用模型或其他来源的模型:

    您需要一个自定义模型绑定器提供程序。在模型绑定器提供程序中,您可以遍历现有模型绑定器提供程序,找到当前模型绑定上下文的模型绑定器,然后使用您自己的模型绑定器装饰器进行装饰,该装饰器可以为模型执行 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());
    });
    

    这适用于属性注入。

    对于构造函数注入,由于模型的创建仍然发生在内部模型绑定器中,因此您肯定需要比这个示例更多的代码,而且我没有尝试在这种情况下让构造函数注入工作。

    【讨论】:

    • 谢谢weichch的回答。我尝试了您的解决方案,但 _innerBinder.BindModelAsync 引发错误:System.InvalidOperationException:尝试激活“Project.Controllers.Controller.Requests.CustomBinders.DependencyInjectionModelBinder”时无法解析类型“Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder”的服务'。在 Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired) at lambda_method(Closure, IServiceProvider, Object[]) ....
    • @Mikkel 记得在 Startup.cs 的 ConfigureServices 方法中注册 IModelBinder 的实现吗?
    • 很奇怪。您不需要解析自定义模型绑定器,因为它是在示例中的自定义模型绑定器提供程序中创建的。有时间我去看看。
    • @sunero4 我已经添加了如 weichch 所示的 binderprovider,但是将 DependencyInjectionModelBinder 的具体实现添加到 DI 会导致循环依赖异常。 services.AddTransient&lt;IModelBinder, DependencyInjectionModelBinder&gt;();Requests.CustomBinders.DependencyInjectionModelBinder': A circular dependency was detected for the service of type '
    • @weichch 我的错误我已将[ModelBinder(BinderType = typeof(DependencyInjectionModelBinder)] 添加到我想要“绑定”的类中,当它试图将其用作模型绑定器时,它找不到它并崩溃了。但是将其排除在外就可以正常工作。对不起,我的困惑。但是我不太清楚如何在 // Do DI stuff 部分中进行“属性注入”。
    【解决方案2】:

    我后来选择做以下事情。

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

    服务注入到 api 操作的位置。

    然后我选择了非常小的服务,每个请求一个,以保持它非常分散。 如果这些服务需要一些共享代码,比如说来自存储库,那么我只需将其注入到这些较小的服务中。

    好处包括;在单元测试中很容易模拟和测试,并且在不影响其他操作的情况下保持简单的更改,因为它非常明确地声明这个“服务”/请求只使用一次。

    缺点是你必须创建很多类。 通过良好的文件夹结构,您可以减轻一些概览负担。

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

    【讨论】:

      猜你喜欢
      • 2011-12-15
      • 1970-01-01
      • 1970-01-01
      • 2017-01-04
      • 1970-01-01
      • 1970-01-01
      • 2018-09-14
      • 2015-12-10
      • 2021-10-30
      相关资源
      最近更新 更多