【发布时间】:2017-02-15 21:02:22
【问题描述】:
我询问了一个关于逗号分隔数值 here 的问题。
鉴于一些回复,我尝试按如下方式实现我自己的模型绑定器:
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();
}
}
}
现在,如果我用逗号输入价格,我的自定义模型绑定器将删除逗号,这就是我想要的,但验证仍然失败。所以,问题是:如何在我的自定义模型绑定器中进行自定义验证,从而可以避免使用逗号捕获的价格值?换句话说,我怀疑我需要在我的自定义模型绑定器中做更多事情,但不知道如何以及做什么。谢谢
更新:
所以,我在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 作为价格,模型返回为有效,这是错误的,因为我有一个 Range 注释,它说最低价格是 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";
}
<h2>Property Registration</h2>
<p>Enter your property and agent information below.</p>
@using (Html.BeginForm("Index", "Registration"))
{
@Html.ValidationSummary();
<h4>Property Info</h4>
<text>House Number</text> @Html.TextBoxFor(m => m.Property.HouseNumber)<br />
<text>Street</text> @Html.TextBoxFor(m => m.Property.Street)<br />
<text>City</text> @Html.TextBoxFor(m => m.Property.City)<br />
<text>State</text> @Html.TextBoxFor(m => m.Property.State)<br />
<text>Zip</text> @Html.TextBoxFor(m => m.Property.Zip)<br />
<text>Price</text> @Html.TextBoxFor(m => m.Property.Price)<br />
<h4>Agent Info</h4>
<text>First Name</text> @Html.TextBoxFor(m => m.Agent.FirstName)<br />
<text>Last Name</text> @Html.TextBoxFor(m => m.Agent.LastName)<br />
<text>Annual Sales</text> @Html.TextBoxFor(m => m.Agent.AnnualSales)<br />
<text>Agent Address L1</text>@Html.TextBoxFor(m => m.Agent.Address.Line1)<br />
<text>Agent Address L2</text>@Html.TextBoxFor(m => m.Agent.Address.Line2)<br />
<input type="submit" value="Submit" name="submit" />
}
这是我连接自定义模型绑定器的 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());
}
}
}
也许我做错了什么或做得不够。我注意到以下问题:
- 虽然我只需要修改属性对象的 Price 值,但似乎我必须繁琐地填充模型绑定器中的所有其他属性。我必须对代理属性的 AnnualSales 属性执行相同的操作。无论如何,这可以在模型活页夹中避免吗?
- 我原以为默认的 BindModel 方法会尊重我们对对象属性的注释,并在调用 GetPropertyValue 后相应地验证它们,但事实并非如此。如果我为 Property 对象的 Price 或 Agent 对象的 AnnualSales 输入了一些超出范围的值,则模型返回有效。换句话说,范围注释被忽略。我知道我可以通过覆盖自定义模型绑定器中的 OnModelUpdated 来验证它们,但这是太多的工作,而且,我有注释,为什么模型绑定器的默认实现不尊重它们只是因为我覆盖了部分呢?
@dotnetstep:您能对此发表一些见解吗?谢谢。
【问题讨论】:
-
您能显示价格属性上使用的数据注释吗?
-
是的,我刚刚尝试了@mare 的解决方案。它有点工作。但是,如果我输入 0 作为价格,模型返回为有效,这是错误的,因为我有一个 Range 注释,它说最低价格是 1。嗯,令人沮丧。
标签: c# validation asp.net-mvc-4