C# 带有依赖项注入的MVC6自定义模型绑定器

C# 带有依赖项注入的MVC6自定义模型绑定器,c#,dependency-injection,autofac,asp.net-core-mvc,custom-model-binder,C#,Dependency Injection,Autofac,Asp.net Core Mvc,Custom Model Binder,现在,我的ViewModel如下所示: public class MyViewModel { private readonly IMyService myService; public ClaimantSearchViewModel(IMyService myService) { this.myService = myService; } } public class MyController : Controller { privat

现在,我的
ViewModel
如下所示:

public class MyViewModel
{
    private readonly IMyService myService;

    public ClaimantSearchViewModel(IMyService myService)
    {
        this.myService = myService;
    }
}
public class MyController : Controller
{
    private readonly IMyService myService;
    public HomeController(IMyService myService)
    {
        this.myService = myService;
    }

    public IActionResult Index()
    {
        var model = new MyViewModel(myService);

        return View(model);
    }

    [HttpPost]
    public async Task<IActionResult> Find()
    {
        var model = new MyViewModel(myService);
        await TryUpdateModelAsync(model);

        return View("Index", model);
    }
}
public class MyViewModel
{
    [FromServices]
    public IMyService myService { get; set; }

    public ClaimantSearchViewModel(IMyService myService)
    {
        this.myService = myService;
    }
}
使用此
ViewModel
的我的
Controller
如下所示:

public class MyViewModel
{
    private readonly IMyService myService;

    public ClaimantSearchViewModel(IMyService myService)
    {
        this.myService = myService;
    }
}
public class MyController : Controller
{
    private readonly IMyService myService;
    public HomeController(IMyService myService)
    {
        this.myService = myService;
    }

    public IActionResult Index()
    {
        var model = new MyViewModel(myService);

        return View(model);
    }

    [HttpPost]
    public async Task<IActionResult> Find()
    {
        var model = new MyViewModel(myService);
        await TryUpdateModelAsync(model);

        return View("Index", model);
    }
}
public class MyViewModel
{
    [FromServices]
    public IMyService myService { get; set; }

    public ClaimantSearchViewModel(IMyService myService)
    {
        this.myService = myService;
    }
}
现在,调用第一个
Index
方法可以很好地工作(使用

在我的
启动类
)中,但执行
POST
索引(MyViewModel模型)
会为该对象定义一个
无参数构造函数
异常。我意识到可以使用我的
DI
自定义模型绑定器将是最有可能的解决方案。。。但是我找不到任何关于如何在这里开始的帮助。请帮我解决这个问题,特别是对于
mvc6
中的
Autofac
,我们在这里得到了答案:

答案是使用:[FromServices]

我的模型最终看起来像这样:

public class MyViewModel
{
    private readonly IMyService myService;

    public ClaimantSearchViewModel(IMyService myService)
    {
        this.myService = myService;
    }
}
public class MyController : Controller
{
    private readonly IMyService myService;
    public HomeController(IMyService myService)
    {
        this.myService = myService;
    }

    public IActionResult Index()
    {
        var model = new MyViewModel(myService);

        return View(model);
    }

    [HttpPost]
    public async Task<IActionResult> Find()
    {
        var model = new MyViewModel(myService);
        await TryUpdateModelAsync(model);

        return View("Index", model);
    }
}
public class MyViewModel
{
    [FromServices]
    public IMyService myService { get; set; }

    public ClaimantSearchViewModel(IMyService myService)
    {
        this.myService = myService;
    }
}
虽然将该属性
公开
是令人伤心的,但它比必须使用
自定义模型活页夹
要轻松得多

另外,假设您应该能够将
[FromServices]
作为Action方法中参数的一部分传递,它确实解析了类,但这破坏了模型绑定。。。我的所有属性都没有映射。它看起来是这样的:(但同样,这不起作用,所以使用上面的示例)

更新1

使用
[FromServices
]属性后,我们决定在所有
视图模型中注入属性并不是我们想要的方式,特别是在考虑使用测试进行长期维护时。因此,我们决定删除
[FromServices]
属性,并让我们的自定义模型绑定器正常工作:

public class IoCModelBinder : IModelBinder
{
    public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
    {
        var serviceProvider = bindingContext.OperationBindingContext.HttpContext.RequestServices;

        var model = serviceProvider.GetService(bindingContext.ModelType);
        bindingContext.Model = model;

        var binder = new GenericModelBinder();
        return binder.BindModelAsync(bindingContext);
    }
}
就这样。(甚至不确定是否需要
options.ModelBinders.Clear();

更新2 在经过各种迭代之后(在帮助下),最终结果如下:

public class IoCModelBinder : IModelBinder
{
    public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
    {   // For reference: https://github.com/aspnet/Mvc/issues/4196
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

        if (bindingContext.Model == null && // This binder only constructs viewmodels, avoid infinite recursion.
                (
                    (bindingContext.ModelType.Namespace.StartsWith("OUR.SOLUTION.Web.ViewModels") && bindingContext.ModelType.IsClass)
                        ||
                    (bindingContext.ModelType.IsInterface)
                )
            )
        {
            var serviceProvider = bindingContext.OperationBindingContext.HttpContext.RequestServices;
            var model = serviceProvider.GetRequiredService(bindingContext.ModelType);

            // Call model binding recursively to set properties
            bindingContext.Model = model;
            var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(bindingContext);

            bindingContext.ValidationState[model] = new ValidationStateEntry() { SuppressValidation = true };

            return result;
        }

        return await ModelBindingResult.NoResultAsync;
    }
}
更新3: 这是
模型绑定器
及其
提供程序
的最新版本,可与
ASP.NET Core 2.X
一起使用:

public class IocModelBinder : ComplexTypeModelBinder
{
    public IocModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory) : base(propertyBinders, loggerFactory)
    {
    }

    protected override object CreateModel(ModelBindingContext bindingContext)
    {
        object model = bindingContext.HttpContext.RequestServices.GetService(bindingContext.ModelType) ?? base.CreateModel(bindingContext);

        if (bindingContext.HttpContext.Request.Method == "GET")
            bindingContext.ValidationState[model] = new ValidationStateEntry { SuppressValidation = true };
        return model;
    }
}

public class IocModelBinderProvider : IModelBinderProvider
{
    private readonly ILoggerFactory loggerFactory;

    public IocModelBinderProvider(ILoggerFactory loggerFactory)
    {
        this.loggerFactory = loggerFactory;
    }

    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (!context.Metadata.IsComplexType || context.Metadata.IsCollectionType) return null;

        var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
        foreach (ModelMetadata property in context.Metadata.Properties)
        {
            propertyBinders.Add(property, context.CreateBinder(property));
        }
        return new IocModelBinder(propertyBinders, loggerFactory);
    }
}

这个问题用ASP.NET Core标记,下面是我们针对dotnet Core 3.1的解决方案

我们的解决方案概述:该项目需要使
icCustomerService
可用于在请求管道中自动创建的对象。需要此功能的类使用接口标记,
IUsesCustomerService
。然后,绑定器在创建对象时检查该接口,并处理特殊情况

使用系统;
使用System.Collections.Generic;
使用System.Linq;
使用System.Threading.Tasks;
使用Microsoft.AspNetCore.Mvc.ModelBinding;
使用Microsoft.Extensions.Logging;
命名空间为project.Infrastructure.DependencyInjection
{
/// 
///这是一个到binder类的简单传递类。
///它从上下文中收集一些信息并进行传递。
/// 
公共类项目模型绑定器提供程序:IModelBinderProvider
{
公共项目模型绑定器提供程序()
{
}
公共IModelBinder GetBinder(ModelBinderProviderContext)
{
伊洛格;
if(上下文==null)
{
抛出新ArgumentNullException(nameof(context));
}
//归还的活页夹是a,但我
//不确定提前返回可能会导致什么副作用。
如果(!context.Metadata.IsComplexType | | context.Metadata.IsCollectionType)
{
返回null;
}
var propertyBinders=new Dictionary();
foreach(context.Metadata.Properties中的ModelMetadata属性)
{
Add(property,context.CreateBinder(property));
}
ilogger=(ILoggerFactory)context.Services.GetService(typeof(ILoggerFactory));
返回新的项目模型绑定器(PropertyBinder、ilogger);
}
}
/// 
///自定义模型活页夹。
///允许截取端点方法以调整对象构造
///(允许自动设置ASP.NET为端点创建的对象的属性)。
///此处,这用于确保正确设置。
/// 
公共类项目模型绑定器:ComplexTypeModelBinder
{
公共项目模型绑定器(IDictionary Property绑定器、iLogger工厂和Logger工厂)
:底座(不动产缓冲器、伐木厂)
{
}
/// 
///方法来构造对象。这通常调用默认构造函数。
///此方法不设置属性值,而是设置管道中其他位置处理的属性值,
///这里处理的任何特殊属性除外。
/// 
///上下文。
///新创建的对象。
受保护的重写对象CreateModel(ModelBindingContext bindingContext)
{
if(bindingContext==null)
抛出新ArgumentNullException(nameof(bindingContext));
var customerService=(icCustomerService)bindingContext.HttpContext.RequestServices.GetService(typeof(icCustomerService));
bool setcustomerService=false;
对象模型;
if(typeof(IUsesCustomerService).IsAssignableFrom(bindingContext.ModelType))
{
setcustomerService=true;
}
//我想你也可以在这里调用Activator.CreateInstance。
//最终结果是构造了一个对象,但尚未设置任何属性。
model=base.CreateModel(bindingContext);
if(设置客户服务)
{
((iuseCustomerService)模型).SetcustomerService(customerService);
}
收益模型;
}
}
}
然后在启动代码中,确保设置
addmvcopions