如何在自定义模型绑定器中validation我的模型?

我在这里询问了我用逗号分隔的数值的问题。

鉴于一些回复,我试图尝试实现我自己的模型绑定器,如下所示:

namespace MvcApplication1.Core { public class PropertyModelBinder : DefaultModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { object objectModel = new object(); if (bindingContext.ModelType == typeof(PropertyModel)) { HttpRequestBase request = controllerContext.HttpContext.Request; string price = request.Form.Get("Price").Replace(",", string.Empty); ModelBindingContext newBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType( () => new PropertyModel() { Price = Convert.ToInt32(price) }, typeof(PropertyModel) ), ModelState = bindingContext.ModelState, ValueProvider = bindingContext.ValueProvider }; // call the default model binder this new binding context return base.BindModel(controllerContext, newBindingContext); } else { return base.BindModel(controllerContext, bindingContext); } } //protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) //{ // //return base.CreateModel(controllerContext, bindingContext, modelType); // PropertyModel model = new PropertyModel(); // if (modelType == typeof(PropertyModel)) // { // model = (PropertyModel)base.CreateModel(controllerContext, bindingContext, modelType); // HttpRequestBase request = controllerContext.HttpContext.Request; // string price = request.Form.Get("Price").Replace(",", string.Empty); // model.Price = Convert.ToInt32(price); // } // return model; //} } } 

并更新了我的控制器类:

 namespace MvcApplication1.Controllers { public class PropertyController : Controller { public ActionResult Edit() { PropertyModel model = new PropertyModel { AgentName = "John Doe", BuildingStyle = "Colonial", BuiltYear = 1978, Price = 650000, Id = 1 }; return View(model); } [HttpPost] public ActionResult Edit([ModelBinder(typeof(PropertyModelBinder))] PropertyModel model) { if (ModelState.IsValid) { //Save property info. } return View(model); } public ActionResult About() { ViewBag.Message = "Your app description page."; return View(); } public ActionResult Contact() { ViewBag.Message = "Your contact page."; return View(); } } } 

现在,如果我用逗号输入价格,我的自定义模型绑定器将删除逗号,这就是我想要的,但validation仍然失败。 所以,问题是: 如何在我的自定义模型绑定器中进行自定义validation,以便可以避免使用逗号捕获的价格值 ? 换句话说,我怀疑我需要在我的自定义模型绑定器中做更多,但不知道如何和什么。 谢谢。 在新选项卡中打开屏幕截图以获得更好的视图。

更新:

所以,我在https://stackoverflow.com/a/2592430/97109尝试了@ mare的解决方案并更新了我的模型绑定器,如下所示:

 namespace MvcApplication1.Core { public class PropertyModelBinder : DefaultModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { object objectModel = new object(); if (bindingContext.ModelType == typeof(PropertyModel)) { HttpRequestBase request = controllerContext.HttpContext.Request; string price = request.Form.Get("Price").Replace(",", string.Empty); ModelBindingContext newBindingContext = new ModelBindingContext() { ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType( () => new PropertyModel() { Price = Convert.ToInt32(price) }, typeof(PropertyModel) ), ModelState = bindingContext.ModelState, ValueProvider = bindingContext.ValueProvider }; // call the default model binder this new binding context object o = base.BindModel(controllerContext, newBindingContext); newBindingContext.ModelState.Remove("Price"); newBindingContext.ModelState.Add("Price", new ModelState()); newBindingContext.ModelState.SetModelValue("Price", new ValueProviderResult(price, price, null)); return o; } else { return base.BindModel(controllerContext, bindingContext); } } //protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) //{ // //return base.CreateModel(controllerContext, bindingContext, modelType); // PropertyModel model = new PropertyModel(); // if (modelType == typeof(PropertyModel)) // { // model = (PropertyModel)base.CreateModel(controllerContext, bindingContext, modelType); // HttpRequestBase request = controllerContext.HttpContext.Request; // string price = request.Form.Get("Price").Replace(",", string.Empty); // model.Price = Convert.ToInt32(price); // } // return model; //} } } 

它有点工作,但如果我输入价格为0,模型会返回有效,这是错误的,因为我有一个范围注释,表示最低价格为1.在我的机智结束时。

更新:

为了测试具有复合类型的自定义模型绑定器。 我创建了以下视图模型类:

 using System.ComponentModel.DataAnnotations; namespace MvcApplication1.Models { public class PropertyRegistrationViewModel { public PropertyRegistrationViewModel() { } public Property Property { get; set; } public Agent Agent { get; set; } } public class Property { public int HouseNumber { get; set; } public string Street { get; set; } public string City { get; set; } public string State { get; set; } public string Zip { get; set; } [Required(ErrorMessage="You must enter the price.")] [Range(1000, 10000000, ErrorMessage="Bad price.")] public int Price { get; set; } } public class Agent { public string FirstName { get; set; } public string LastName { get; set; } [Required(ErrorMessage="You must enter your annual sales.")] [Range(10000, 5000000, ErrorMessage="Bad range.")] public int AnnualSales { get; set; } public Address Address { get; set; } } public class Address { public string Line1 { get; set; } public string Line2 { get; set; } } } 

这是控制器:

 using MvcApplication1.Core; using MvcApplication1.Models; using System.Web.Mvc; namespace MvcApplication1.Controllers { public class RegistrationController : Controller { public ActionResult Index() { PropertyRegistrationViewModel viewModel = new PropertyRegistrationViewModel(); return View(viewModel); } [HttpPost] public ActionResult Index([ModelBinder(typeof(PropertyRegistrationModelBinder))]PropertyRegistrationViewModel viewModel) { if (ModelState.IsValid) { //save registration. } return View(viewModel); } } } 

以下是自定义模型绑定器实现:

 using MvcApplication1.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace MvcApplication1.Core { public class PropertyRegistrationModelBinder : DefaultModelBinder { protected override object GetPropertyValue( ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) { if (propertyDescriptor.ComponentType == typeof(PropertyRegistrationViewModel)) { if (propertyDescriptor.Name == "Property") { var price = bindingContext.ValueProvider.GetValue("Property.Price").AttemptedValue.Replace(",", string.Empty); var property = new Property(); // Question 1: Price is the only property I want to modify. Is there any way // such that I don't have to manually populate the rest of the properties like so? property.Price = string.IsNullOrWhiteSpace(price)? 0: Convert.ToInt32(price); property.HouseNumber = Convert.ToInt32(bindingContext.ValueProvider.GetValue("Property.HouseNumber").AttemptedValue); property.Street = bindingContext.ValueProvider.GetValue("Property.Street").AttemptedValue; property.City = bindingContext.ValueProvider.GetValue("Property.City").AttemptedValue; property.State = bindingContext.ValueProvider.GetValue("Property.State").AttemptedValue; property.Zip = bindingContext.ValueProvider.GetValue("Property.Zip").AttemptedValue; // I had thought that when this property object returns, our annotation of the Price property // will be honored by the model binder, but it doesn't validate it accordingly. return property; } if (propertyDescriptor.Name == "Agent") { var sales = bindingContext.ValueProvider.GetValue("Agent.AnnualSales").AttemptedValue.Replace(",", string.Empty); var agent = new Agent(); // Question 2: AnnualSales is the only property I need to process before validation, // Is there any way I can avoid tediously populating the rest of the properties? agent.AnnualSales = string.IsNullOrWhiteSpace(sales)? 0: Convert.ToInt32(sales); agent.FirstName = bindingContext.ValueProvider.GetValue("Agent.FirstName").AttemptedValue; agent.LastName = bindingContext.ValueProvider.GetValue("Agent.LastName").AttemptedValue; var address = new Address(); address.Line1 = bindingContext.ValueProvider.GetValue("Agent.Address.Line1").AttemptedValue + " ROC"; address.Line2 = bindingContext.ValueProvider.GetValue("Agent.Address.Line2").AttemptedValue + " MD"; agent.Address = address; // I had thought that when this agent object returns, our annotation of the AnnualSales property // will be honored by the model binder, but it doesn't validate it accordingly. return agent; } } return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); } protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) { var model = bindingContext.Model as PropertyRegistrationViewModel; //In order to validate our model, it seems that we will have to manually validate it here. base.OnModelUpdated(controllerContext, bindingContext); } } } 

这是Razor视图:

 @model MvcApplication1.Models.PropertyRegistrationViewModel @{ ViewBag.Title = "Property Registration"; } 

Property Registration

Enter your property and agent information below.

@using (Html.BeginForm("Index", "Registration")) { @Html.ValidationSummary();

Property Info

House Number @Html.TextBoxFor(m => m.Property.HouseNumber)
Street @Html.TextBoxFor(m => m.Property.Street)
City @Html.TextBoxFor(m => m.Property.City)
State @Html.TextBoxFor(m => m.Property.State)
Zip @Html.TextBoxFor(m => m.Property.Zip)
Price @Html.TextBoxFor(m => m.Property.Price)

Agent Info

First Name @Html.TextBoxFor(m => m.Agent.FirstName)
Last Name @Html.TextBoxFor(m => m.Agent.LastName)
Annual Sales @Html.TextBoxFor(m => m.Agent.AnnualSales)
Agent Address L1@Html.TextBoxFor(m => m.Agent.Address.Line1)
Agent Address L2@Html.TextBoxFor(m => m.Agent.Address.Line2)
}

这里是global.asax文件,我在其中连接自定义模型绑定器。 顺便说一下,似乎不需要这一步,因为我注意到它没有这一步仍然有效。

 using MvcApplication1.Core; using MvcApplication1.Models; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; namespace MvcApplication1 { // Note: For instructions on enabling IIS6 or IIS7 classic mode, // visit http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); AuthConfig.RegisterAuth(); ModelBinders.Binders.Add(typeof(PropertyRegistrationViewModel), new PropertyRegistrationModelBinder()); } } } 

也许我做错了或做得不够。 我注意到以下问题:

  1. 虽然我只需要修改属性对象的Price值,但似乎我必须繁琐地填充模型绑定器中的所有其他属性。 我必须对代理属性的AnnualSales属性执行相同的操作。 无论如何,在模型绑定器中可以避免这种情况吗?
  2. 我原以为默认的BindModel方法会遵循我们对象属性的注释,并在调用GetPropertyValue后对其进行相应的validation,但事实并非如此。 如果我为Property对象的Price或Agent对象的AnnualSales输入一些超出范围的值,则模型返回有效。 换句话说,范围注释被忽略。 我知道我可以通过在自定义模型绑定器中覆盖OnModelUpdated来validation它们,但这太多了,而且,我有注释,为什么模型绑定器的默认实现不尊重它们只是因为我重写部分它?

@dotnetstep:你能对此提出一些见解吗? 谢谢。

  [HttpPost] public ActionResult Edit([ModelBinder(typeof(PropertyModelBinder))]PropertyModel model) { ModelState.Clear(); TryValidateModel(model); if (ModelState.IsValid) { //Save property info. } return View(model); } 

希望这会有所帮助。

您也可以尝试@Ryan解决方案。

这可能是您的Custom ModelBinder。 (在这种情况下,您不需要像我上面建议的那样更新编辑操作结果)

 public class PropertyModelBinder : DefaultModelBinder { protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) { if(propertyDescriptor.ComponentType == typeof(PropertyModel)) { if (propertyDescriptor.Name == "Price") { var obj= bindingContext.ValueProvider.GetValue("Price"); return Convert.ToInt32(obj.AttemptedValue.ToString().Replace(",", "")); } } return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); } } 

当您更新了绑定范围时。 我在评论中提出了我的建议。 此外,如果您使用ModelBinder for Property和Agent,那么您可以这样做。

 //In Global.asax ModelBinders.Binders.Add(typeof(Property), new PropertyRegistrationModelBinder()); ModelBinders.Binders.Add(typeof(Agent), new PropertyRegistrationModelBinder()); //Updated ModelBinder look like this. public class PropertyRegistrationModelBinder : DefaultModelBinder { protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) { if (propertyDescriptor.ComponentType == typeof(Property) || propertyDescriptor.ComponentType == typeof(Agent)) { if(propertyDescriptor.Name == "Price" || propertyDescriptor.Name == "AnnualSales") { var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue.Replace(",", string.Empty); return string.IsNullOrEmpty(value) ? 0 : Convert.ToInt32(value); } } return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); } } 

另外我想说你能找到很多与此相关的信息,你也可以用很多方法做同样的事情。 就像你引入适用于类属性的新属性进行绑定一样,你可以在类级别应用ModelBinder。

这不会完全回答你的问题,但无论如何我都会把它作为答案,因为我认为它解决了人们在你之前的问题中问你的问题。

在这个特定的例子中,它听起来好像你想要一个字符串。 是的,我知道这些值最终是数值(整数,似乎)值,但是当你使用像MVC这样的东西时,你必须记住你的视图使用的模型不必匹配模型这是您的业务逻辑的一部分。 我认为你正在遇到这个问题因为你试图混合这两个问题。

相反,我建议创建一个专门用于向最终用户显示的View的模型(ViewModel)。 添加各种数据注释,以帮助validation构成价格的STRING。 (您可以通过简单的正则表达式数据注释轻松完成所需的所有validation。然后,一旦模型实际提交给您的控制器(或任何其他数据提交给您的控制器)并且您知道它是有效的(通过数据注释),然后您可以将它转换为您对业务逻辑使用的模型所需的整数,并继续使用它(作为整数)。

通过这种方式,您可以避免遇到所有这些不必要的复杂问题(它确实有一个解决方案,但它并不真正适合MVC背后的思维模式),并且允许您在视图中实现所需的灵活性您的业​​务逻辑中的严格要求。

希望这是有道理的。 您可以在Web上搜索MVC中的ViewModel。 一个好的开始是ASP.NET MVC教程 。

通过在BindModel触发时更改,我的validation工作正常。 在您的代码中,您在PropertyModelBinder有以下行:

 object o = base.BindModel(controllerContext, newBindingContext); newBindingContext.ModelState.Remove("Price"); newBindingContext.ModelState.Add("Price", new ModelState()); newBindingContext.ModelState.SetModelValue("Price", new ValueProviderResult(price, price, null)); return o; 

我在返回对象之前立即base.BindModel (在重构上下文之后),现在validation按预期工作。 这是新代码:

 newBindingContext.ModelState.Remove("Price"); newBindingContext.ModelState.Add("Price", new ModelState()); newBindingContext.ModelState.SetModelValue("Price", new ValueProviderResult(price, price, null)); object o = base.BindModel(controllerContext, newBindingContext); return o;