现在我的ViewModel
看起来是这样的:
public class MyViewModel
{
private readonly IMyService myService;
public ClaimantSearchViewModel(IMyService myService)
{
this.myService = myService;
}
}
消耗这个的my
Controller
看起来是这样的: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);
}
}
我需要我的
ViewModel
看起来是这样的:public class MyController : Controller
{
private readonly IServiceProvider servicePovider;
public MyController(IServiceProvider servicePovider)
{
this.servicePovider = servicePovider;
}
public IActionResult Index()
{
var model = servicePovider.GetService(typeof(MyViewModel));
return View(model);
}
[HttpPost]
public IActionResult Index(MyViewModel model)
{
return View(model);
}
}
现在,调用第一个
Controller
方法很好(使用builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource(x => x.Name.Contains("ViewModel")));
在my
Index
)中,但是执行Startup class
到POST
会给您一个Index(MyViewModel model)
异常。我意识到使用我的No parameterless constructor defined for this object
的custom model binder
将是最有可能的解决方案…但我找不到任何帮助,甚至无法开始这里。请帮助我,特别是在DI
中的Autofac
。 最佳答案
我们得到了答案:https://github.com/aspnet/Mvc/issues/4167
答案是使用:[fromservices]
我的模特最后看起来是这样的:
public class MyViewModel
{
[FromServices]
public IMyService myService { get; set; }
public ClaimantSearchViewModel(IMyService myService)
{
this.myService = myService;
}
}
虽然让这个属性
public
很难过,但它比使用custom model binder
要少得多。而且,假设您应该能够将
[FromServices]
作为action方法中参数的一部分传递,它确实解析了类,但这会破坏模型绑定…我的财产都没有测绘出来。看起来是这样的:(但同样,这不起作用,所以使用上面的例子)public class MyController : Controller
{
... same as in OP
[HttpPost]
public IActionResult Index([FromServices]MyViewModel model)
{
return View(model);
}
}
更新1
在使用
[FromServices
]属性之后,我们决定所有ViewModels
中的属性注入不是我们想要的方式,特别是在考虑使用测试进行长期维护时。因此,我们决定删除[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);
}
}
它在
Startup
ConfigureServices
方法中注册如下: services.AddMvc().AddMvcOptions(options =>
{
options.ModelBinders.Clear();
options.ModelBinders.Add(new IoCModelBinder());
});
就这样。(甚至不确定是否需要
options.ModelBinders.Clear();
。)更新2
在经历了各种使其工作的迭代(在帮助下https://github.com/aspnet/Mvc/issues/4196)之后,下面是最终结果:
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;
}
}
很明显,您想用
OUR.SOLUTION...
代替namespace
来注册我们的ViewModels
: services.AddMvc().AddMvcOptions(options =>
{
options.ModelBinders.Insert(0, new IoCModelBinder());
});
更新3:
这是
Model Binder
及其与Provider
一起工作的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);
}
}
然后在
Startup
中:services.AddMvc(options =>
{
// add IoC model binder.
IModelBinderProvider complexBinder = options.ModelBinderProviders.FirstOrDefault(x => x.GetType() == typeof(ComplexTypeModelBinderProvider));
int complexBinderIndex = options.ModelBinderProviders.IndexOf(complexBinder);
options.ModelBinderProviders.RemoveAt(complexBinderIndex);
options.ModelBinderProviders.Insert(complexBinderIndex, new IocModelBinderProvider(loggerFactory));