Как я могу связать сложный класс с представлением, сохраняя мои собственные атрибуты проверки?

В моем проекте у меня есть класс модели, который использует другой класс, как в примере ниже. Одно из свойств в модели зависит от проверки свойств дочернего объекта - в этом примере свойство 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; }
}
Другие вопросы по тегам