Как я могу связать сложный класс с представлением, сохраняя мои собственные атрибуты проверки?
В моем проекте у меня есть класс модели, который использует другой класс, как в примере ниже. Одно из свойств в модели зависит от проверки свойств дочернего объекта - в этом примере свойство LastName зависит для проверки от значения свойства Address.PostalCode. Я реализовал пользовательский атрибут проверки для проверки моего свойства 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 = "";
}
Проблема в контроллере, свойство Address не создается из представления, и оно всегда равно null. В этом примере user.Address всегда имеет значение null, независимо от того, что я отправляю в представлении. Вот код контроллера.
[HttpPost]
public ActionResult Create(User user)
{
if (ModelState.IsValid)
{
// creation code here
return RedirectToAction("Index");
}
else
{
return View(user);
}
}
Вот мнение:
<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>
Чтобы решить эту проблему, я создал собственную фиктивную привязку, чтобы сопоставить поля в представлении со свойствами в модели следующим образом:
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;
}
Связыватель работает нормально, но атрибуты проверки больше не работают. Я думаю, что должен быть лучший способ сделать связывание, чем это, не так ли?
Этот механизм связывания просто отображает LastName в LastName и Address.streetName в Address.streetName, я думаю, что должен быть способ сделать это без необходимости писать весь этот утомительный код и не нарушать пользовательский механизм проверки.
2 ответа
Одним из решений является использование свойств вместо открытых полей в моем дочернем классе - спасибо Одеду за ответ!
Другое решение состоит в том, чтобы вызвать TryValidateModel в контроллере, это позволяет мой код проверки даже при наличии связующего.
[HttpPost]
public ActionResult Create(User user)
{
if (TryValidateModel(user))
{
// creation code here
return RedirectToAction("Index");
}
else
{
return View(user);
}
}
Вам нужно использовать свойства вместо открытых полей, чтобы привязка модели по умолчанию работала правильно.
Измени свой AddressType
класс для:
public class AddressType
{
public string streetName { get; set; }
public string streetNumber { get; set; }
public string postalCode { get; set; }
}