我用逗号分隔的数字值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作为价格,模型会返回有效,这是错误的,因为我有一个范围注释,表示最低价格是1.在我的机智结束。< / p>
更新
为了测试具有复合类型的自定义模型绑定器。我创建了以下视图模型类:
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());
}
}
}
也许我做错了或做得不够。我注意到以下问题:
@dotnetstep:你能否对此提出一些见解?谢谢。
答案 0 :(得分:21)
[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。
答案 1 :(得分:2)
我通过更改BindModel
触发时间来验证您的工作正常。在您的代码中,您在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
(在重建上下文之后),现在验证按预期工作。这是新代码:
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;
答案 2 :(得分:1)
这不能完全回答你的问题,但无论如何我都会把它作为答案,因为我认为它解决了人们在你上一个问题中提出的问题。
在这个特定的例子中,它听起来好像你想要一个字符串。是的,我知道这些值最终是数值(整数,似乎)值,但是当你使用像MVC这样的东西时,你必须记住视图使用的模型不必匹配模型这是您业务逻辑的一部分。我认为你遇到了这个问题,因为你试图混合这两个。
相反,我建议创建一个专门用于向最终用户显示的View的模型(ViewModel)。添加各种数据注释,以帮助验证构成价格的STRING。 (您可以通过简单的regular expression data annotation轻松完成所需的所有验证。然后,一旦模型实际提交给您的控制器(或其他任何数据提交给您的控制器)并且您知道它是有效的(通过数据注释),然后您可以将它转换为您对业务逻辑使用的模型所需的整数,并继续使用它(作为整数)。
通过这种方式,您可以避免遇到所有这些不必要的复杂性(它确实有一个解决方案,但它并不真正符合MVC背后的思维方式),并且可以让您实现所需的灵活性。根据您的业务逻辑中的严格要求,您的观点。
希望这是有道理的。您可以在Web上搜索MVC中的ViewModel。一个好的开始是ASP.NET MVC tutorials。