ASP.NET MVC модель привязки и проверки вопроса - PullRequest
9 голосов
/ 01 мая 2009

Я пытаюсь использовать MVC для нового проекта, пройдя весь блок со всеми примерами, учебными пособиями и тому подобным. Однако мне трудно понять, где должны происходить определенные вещи.

В качестве примера у меня есть сущность под названием Профиль. Этот объект содержит обычный тип профиля, а также свойство DateOfBirth типа DateTime. В форме HTML поле даты рождения делится на 3 поля. Теперь я знаю, что могу использовать связыватель пользовательской модели, чтобы справиться с этим, но что, если введенная дата не является действительной датой? Должен ли я проверять это в связующем модели? Должна ли вся моя проверка идти в связывателе модели? Можно ли проверять только несколько вещей в связывателе модели и проверять остальное в контроллере или самой модели?

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

namespace WebSite.Models
{
    public class ProfileModelBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            DateTime birthDate;

            var form = controllerContext.HttpContext.Request.Form;
            var state = controllerContext.Controller.ViewData.ModelState;

            var profile = new Profile();
            profile.FirstName = form["FirstName"];
            profile.LastName = form["LastName"];
            profile.Address = form["Address"];
            profile.Address2 = form["Address2"];
            profile.City = form["City"];
            profile.State = form["State"];
            profile.Zip = form["Zip"];
            profile.Phone = form["Phone"];
            profile.Email = form["Email"];
            profile.Created = DateTime.UtcNow;
            profile.IpAddress = controllerContext.HttpContext.Request.UserHostAddress;

            var dateTemp = string.Format("{0}/{1}/{2}",
                form["BirthMonth"], form["BirthDay"], form["BirthYear"]);

            if (string.IsNullOrEmpty(dateTemp))
                state.AddModelError("BirthDate", "Required");
            else if (!DateTime.TryParse(dateTemp, out birthDate))
                state.AddModelError("BirthDate", "Invalid");
            else
                profile.BirthDate = birthDate;

            return profile;
        }        
    }
}

Основываясь на приведенном выше примере кода, как бы вы сделали сообщение проверки для поля из 3 частей? В приведенном выше случае я использую совершенно отдельный ключ, который на самом деле не соответствует полю в форме, потому что я не хочу, чтобы сообщение об ошибке отображалось рядом со всеми 3 полями. Я только хочу, чтобы оно появилось справа от поля "Год".

Ответы [ 6 ]

5 голосов
/ 02 мая 2009

Я думаю, что целесообразно сделать проверку в связывателе модели. Как указывает Крейг, проверка в основном является собственностью вашего бизнес-домена:

  1. Иногда ваша модель - это просто глупая модель представления, а не бизнес-объект
  2. Существуют различные механизмы, которые вы можете использовать, чтобы внедрить знания о валидации в подшивку модели.

Томас дает вам пример № 1.

Пример # 2 - когда вы декларативно описываете знания проверки с использованием атрибутов (таких как атрибут DataAnnotation [Обязательный]) или внедряете некоторую службу проверки бизнес-уровня в связыватель пользовательской модели. В этих ситуациях связующее для моделей является идеальным местом для проверки.

При этом привязка модели (поиск, преобразование и перетасовка данных в объект) и проверка (данные соответствуют нашим спецификациям) являются двумя отдельными проблемами. Вы можете утверждать, что они должны быть отдельными фазами / компонентами / точками расширения, но у нас есть то, что у нас есть, хотя DefaultModelBinder делает некоторое различие между этими двумя обязанностями. Если все, что вам нужно, это предоставить некоторую проверку для определенного типа объекта, которую вы можете извлечь из DefaultModelBinder и переопределить метод OnPropertyValidating для проверок уровня свойств или OnModelUpdated, если вам нужно целостное представление.

Вот код, который у меня есть сейчас, но это просто не выглядит правильно для меня. Кажется грязный или вонючий.

Для вашего конкретного кода я бы попытался написать привязку модели только для DateTime. Связыватель модели по умолчанию может позаботиться о привязке имени, фамилии и т. Д. И делегировать привязку пользовательской модели, когда она достигнет свойства DateTime в Профиле. Кроме того, попробуйте использовать valueProvider в bindingContext вместо перехода непосредственно к форме. Эти вещи могут дать вам больше гибкости.

Дополнительные мысли здесь: 6 Советы по связыванию модели ASP.NET MVC .

4 голосов
/ 02 мая 2009

Иногда модель является моделью представления, а не моделью предметной области. В этом случае вы могли бы извлечь выгоду из разделения этих двух элементов и спроектировать модель представления в соответствии с вашим видом.

Теперь вы можете позволить модели представления проверить ввод и проанализировать три поля в DateTime. Затем он может обновить модель домена:

public ActionResult SomeAction(ViewModel vm)
{
    if (vm.IsValid)
    {
        var dm = repositoryOrSomething.GetDomainModel();
        vm.Update(dm);
    }

    // more code...
}
2 голосов
/ 01 мая 2009

У меня точно такая же ситуация была на днях ... ниже приведен код привязки моей модели. В основном это связывает все DateTime? поля модели в поля месяца / дня / года из формы (если это возможно) Итак, да, я добавлю сюда валидацию, поскольку это представляется целесообразным.

public class DateModelBinder : DefaultModelBinder  
    {

        protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
        {

            if (propertyDescriptor.PropertyType == typeof(DateTime?))
            {
                string DateMonth = _GetDateValue(bindingContext, propertyDescriptor.Name + "Month");
                string DateDay = _GetDateValue(bindingContext, propertyDescriptor.Name + "Day");
                string DateYear = _GetDateValue(bindingContext, propertyDescriptor.Name + "Year");
                // Try to parse the date if we have at least a month, day or year
                if (!String.IsNullOrEmpty(DateMonth) || !String.IsNullOrEmpty(DateDay) || !String.IsNullOrEmpty(DateYear))
                {
                    DateTime fullDate;
                    CultureInfo enUS = new CultureInfo("en-US");
                    // If we can parse it, set the model property
                    if (DateTime.TryParse(DateMonth + "/" + DateDay + "/" + DateYear,
                                         enUS,
                                         DateTimeStyles.None, out fullDate))
                    {
                        SetProperty(controllerContext, bindingContext, propertyDescriptor, (DateTime?)fullDate);
                    }
                    // The date is invalid, so we need to add a model error
                    else
                    {
                        string ModelPropertyName = bindingContext.ModelName;
                        if(ModelPropertyName != "")
                        {
                            ModelPropertyName += ".";
                        }
                        ModelPropertyName += propertyDescriptor.Name;
                        bindingContext.ModelState.AddModelError(ModelPropertyName, "Invalid date supplied for " + propertyDescriptor.Name);
                    }
                }
                return;
            }
            base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
        }

        // Get a property from binding context
        private string _GetDateValue(ModelBindingContext bindingContext, string key)
        {
            ValueProviderResult valueResult;
            bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName + "." + key, out valueResult);
            //Didn't work? Try without the prefix if needed...  
            // && bindingContext.FallbackToEmptyPrefix == true
            if (valueResult == null)
            {
                bindingContext.ValueProvider.TryGetValue(key, out valueResult);
            }
            if (valueResult == null)
            {
                return null;
            }
            return (string)valueResult.ConvertTo(typeof(string));
        }

    }

Примечание: у меня были некоторые проблемы с привязкойContext.FallbackToEmptyPrefix, всегда ложным ... не могу найти никакой полезной информации по этому поводу, но вы поняли идею.

1 голос
/ 01 мая 2009

Проверка должна проводиться в нескольких местах, в зависимости от функциональности каждого места. Например, если средство связывания модели не может найти отправленные значения в правильном значении DateTime, то средство связывания может добавить ошибку состояния модели. Если, с другой стороны, ваша бизнес-логика требует, чтобы дата находилась в определенном диапазоне, это было бы неуместно и для связующего устройства модели; это должно быть на уровне бизнес-логики. Контроллеры могут также потенциально добавлять ошибки проверки, если, например, модель редактирования не может быть преобразована в модель объекта.

Среда проверки, такая как xVal, делает это намного проще.

0 голосов
/ 20 декабря 2013

Я устал от создания маленьких ViewModels небольшого назначения, которые касались только части моей доменной модели шириной в милю.

Таким образом, я разработал свой собственный метод решения этой проблемы. Моя ViewModel - это typeOf DomainModel, и я использую пользовательскую связующую модель , чтобы гарантировать, что ее свойства идентичности загружаются первыми - после того как идентичность установлена ​​- он запускает DomainModel.Load, а оставшаяся часть действия привязки по существу выполняет 'сливаться'.

Опять же, когда моя ViewModel привязана (например, к форме POST), после того, как установлены обязательные поля, содержащие идентификатор, - он немедленно загружает модель домена из базы данных. Мне просто нужно было придумать замену для DefaultModelBinder. Моя пользовательская привязка модели , размещенная здесь в StackOverflow , позволяет вам контролировать порядок привязки свойств.

Как только я могу гарантировать, что свойства идентичности связаны, (внутренняя часть моей модели представления прослушивает завершение установки идентификаторов), я запускаю загрузку моей доменной модели, так как остальные свойства связаны, они перезаписываются, т.е. «слияние» с моделью загруженного домена.

В принципе, у меня могут быть все мои различные виды бритвы, независимо от того, предоставляют ли они 5 полей формы или 50 полей модели ... все подчиняются действию контроллера, которое выглядит следующим образом (предоставлено, я все еще делаю отдельные действия, где это необходимо сделать соответствующие пользовательские деловые штучки .. но дело в том, что мои действия контроллера сосредоточены и лаконичны)

<HttpPost()>
<Authorize(Roles:="MYCOMPANY\activeDirRoleForEditing")>
Function Edit(<Http.FromBody()> ByVal mergedModel As OrderModel) As ActionResult
    'notice: NO loading logic here - it already happened during model binding
    'just do the right thing based upon resulting model state
    If Me.ModelState.IsValid Then

        mergedModel.SaveAndReload("MyServiceWebConfigKey")

        ViewBag.SuccessMessage = String.Format("You have successfully edited the order {0}", mergedModel.Id)

        Return View("Edit", mergedModel)
    Else
        ViewBag.ErrorText = String.Format("Order {0} not saved. Check for errors and correct.", mergedModel.Id)
        Return View("Edit", mergedModel)
    End If
End Function
0 голосов
/ 01 мая 2009

Пример приложения Contact Manager на сайте http://www.asp.net/mvc содержит отличное описание разделения логики проверки на уровень обслуживания от контроллера и модели.

Хорошо, прочитайте

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...