[英]how can I bind a complex class to a view, while preserving my custom validation attributes?
In my project I have a model class that uses another class, like in the sample below. 在我的项目中,我有一个使用另一个类的模型类,如下面的示例所示。 One of the properties in the model depends for validation on on of the properties of a child object -- in this sample, LastName property depends for validation on the value of the Address.PostalCode property.
模型中的一个属性取决于对子对象属性的验证 - 在此示例中,LastName属性依赖于对Address.PostalCode属性值的验证。 I implemented a custom validation attribute to validate my LastName property and it works great.
我实现了一个自定义验证属性来验证我的LastName属性,它运行良好。
public class User
{
public static ValidationResult ValidateLastName(string lastName, ValidationContext context)
{
// Grab the model instance
var user = context.ObjectInstance as User;
if (user == null)
throw new NullReferenceException();
// Cross-property validation
if (user.Address.postalCode.Length < 10000)
return new ValidationResult("my LastName custom validation message.");
return ValidationResult.Success;
}
[Display(Name = "Last name")]
[CustomValidationAttribute(typeof(User), "ValidateLastName")]
public string LastName { get; set; }
[Display(Name = "First name")]
public string FirstName { get; set; }
[Display(Name = "Address:")]
[CustomValidationAttribute(typeof(User), "ValidateAddress")]
public AddressType Address { get; set; }
}
public class AddressType
{
public string streetName = "";
public string streetNumber = "";
public string postalCode = "";
}
The problem is in the controller the Address property does not get constructed from the view, and it is always null. 问题是在控制器中,Address属性不是从视图构造的,并且它始终为null。 In this sample, user.Address is always null, regardless of what I send in the view.
在此示例中,无论我在视图中发送什么,user.Address始终为null。 Here is the controller code.
这是控制器代码。
[HttpPost]
public ActionResult Create(User user)
{
if (ModelState.IsValid)
{
// creation code here
return RedirectToAction("Index");
}
else
{
return View(user);
}
}
Here is the view: 这是观点:
<div class="editor-label">
@Html.LabelFor(model => model.Address.postalCode)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Address.postalCode)
@Html.ValidationMessageFor(model => model.Address.postalCode)
</div>
To resolve this, I created a custom dummy binder to map the fields in the view to the properties in the model like so: 为了解决这个问题,我创建了一个自定义虚拟绑定器,将视图中的字段映射到模型中的属性,如下所示:
public class UserBinder : IModelBinder
{
private string GetValue(ModelBindingContext bindingContext, string key)
{
var result = bindingContext.ValueProvider.GetValue(key);
return (result == null) ? null : result.AttemptedValue;
}
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
User instance = new User();
instance.FirstName = GetValue(bindingContext, "FirstName"); //controllerContext.HttpContext.Request["FirstName"];
instance.LastName = GetValue(bindingContext, "LastName"); //controllerContext.HttpContext.Request["LastName"];
instance.Address = new AddressType();
string streetName = controllerContext.HttpContext.Request["Address.streetName"];
//ModelStateDictionary mState = bindingContext.ModelState;
//mState.Add("LastName", new ModelState { });
//mState.AddModelError("LastName", "There's an error.");
instance.Address.streetName = streetName;
...
return instance;
}
The binder works fine, but the validation attributes do not work anymore. 绑定器工作正常,但验证属性不再起作用。 I think there must be a better way to do the binding than this, is there?
我认为必须有一个更好的方法来进行绑定比这个,是吗?
This binder is just mapping LastName to LastName and Address.streetName to Address.streetName, I imagine there should be a way to accomplish this without having to write all this tedious code and without breaking the custom validation mechanism. 这个绑定器只是将LastName映射到LastName,将Address.streetName映射到Address.streetName,我想应该有一种方法可以实现这一点,而不必编写所有这些繁琐的代码而不破坏自定义验证机制。
You need to use properties instead of public fields in order for the default model binding to work properly. 您需要使用属性而不是公共字段才能使默认模型绑定正常工作。
Change your AddressType
class to: 将您的
AddressType
类更改为:
public class AddressType
{
public string streetName { get; set; }
public string streetNumber { get; set; }
public string postalCode { get; set; }
}
One solution is to use properties instead of public fields in my child class -- thanks to Oded for the answer! 一种解决方案是在我的子类中使用属性而不是公共字段 - 感谢Oded的答案!
Another solution is to call TryValidateModel in the controller, this enables my validation code even with the binder present. 另一个解决方案是在控制器中调用TryValidateModel,即使存在绑定器,也可以启用我的验证代码。
[HttpPost]
public ActionResult Create(User user)
{
if (TryValidateModel(user))
{
// creation code here
return RedirectToAction("Index");
}
else
{
return View(user);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.