Состояние ModelState признано недопустимым для частных свойств модели в ASP.NET MVC 2

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

Хорошо, у меня есть мой чистый User poco в моей библиотеке классов модели ...

namespace Model
{    
    public partial class User
    {
        public virtual int Id { get; private set; }
        public virtual string UserName { get; private set; }
        public virtual string DisplayName { get; set; }
        public virtual string Email { get; set; }

        public User(string displayName, string userName)
            : this()
        {
            DisplayName = displayName;
            UserName = userName;
        }
    }
}

Дизайн, который я выбрал, позволяет редактировать только определенные свойства после того, как объект был построен. Например, UserName можно установить только тогда, когда объект построен, для меня это имеет смысл OO, но является ключом к моей проблеме, поэтому я хотел выделить его здесь.

Затем у меня есть «класс приятеля», который определяет метаданные проверки для моего класса User ...

namespace Model
{
[MetadataType(typeof(UserMetadata))]
public partial class User
{
    class UserMetadata
    {
        [Required]
        public virtual int Id { get; set; }

        [Required]
        public virtual string UserName { get; set; }

        [Required]
        public virtual string DisplayName { get; set; }

        [RegularExpression(@"^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$", ErrorMessage = "Invalid address")]
        public virtual string Email { get; set; }
    }
}

}

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

namespace Web.Controllers
{
    public class ProfileController : Controller
    {
        [Authorize]
        public ActionResult Edit()
        {
            var user = _session.Single<User>(x => x.UserName == HttpContext.User.Identity.Name );
            return View(user);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        [Authorize]
        [TransactionFilter]
        public ActionResult Edit(User updatedUser)
        {
            // Get the current user to update.
            var user = _session.Single<User>(x => x.UserName == HttpContext.User.Identity.Name);

            if (ModelState.IsValid)
            {
                TryUpdateModel(user);
                // Update store...                
            }
            return View(updatedUser);
        }
    }
}

У этого есть строго типизированное представление ...

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Model.User>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Edit
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <%=Html.Script("jquery.validate.js")%>
    <%=Html.Script("MicrosoftMvcJQueryValidation.js")%>
    <%=Html.Script("MvcFoolproofJQueryValidation.js")%>
    <div class="container">
        <div class="column span-14">
        <% using (Html.BeginForm()) {%>
            <%= Html.AntiForgeryToken() %>
            <fieldset>
                <%: Html.DisplayFor(model => model.UserName) %>
                <%= Html.Label("Display Name") %>
                <%= Html.HiddenFor(model => model.DisplayName)%>
                <%= Html.ValidationMessageFor(model => model.DisplayName)%>
                <%= Html.Label("Email address") %>
                <%= Html.EditorFor(model => model.Email)%>
                <%= Html.ValidationMessageFor(model => model.Email)%>
                <%= Html.HiddenFor(model => model.UserName)%>
                <p>
                    <input type="submit" value="Save" />
                </p>
            </fieldset>
        </div>
        <div class="clear"></div>
        <% } %>
    </div>
</asp:Content>

Хорошо, вот и весь код !!

Итак, вот проблема: представление отображается нормально после первоначального запроса на получение. Но когда пользователь отправляет форму обратно, скажем, после редактирования своего отображаемого имени, ModelState НЕ является действительным. Это связано с тем, что свойство UserName имеет частный установщик. Однако это сделано намеренно, из соображений безопасности и семантики я никогда не хочу, чтобы они меняли свое имя пользователя, поэтому установщик является частным. Однако, поскольку я добавил к свойству атрибут Required, он не работает, поскольку не установлен!

Вопрос в том, должна ли привязка модели сообщать об этом как об ошибке валидации или нет ?! Поскольку свойство является частным, я спроектировал так, чтобы его нельзя было задавать, поэтому по дизайну я не ожидаю, что связыватель модели будет его устанавливать, но мне не нужна ошибка проверки. Я думаю, что он должен вызывать ошибки проверки только для свойств, которые он МОЖЕТ установить.

Хорошо, возможные решения, которые я придумал до сих пор ..

Сделайте собственность общедоступной.

Если я сделаю это, я откроюсь, чтобы разрешить изменение имени пользователя для существующего пользователя. Мне пришлось бы где-то добавить дополнительную логику, чтобы уловить это, не очень хорошо. Мне также пришлось бы добавить Bind Exclude к методу действия, чтобы остановить любых непослушных людей, пытающихся установить его через сообщение.

Удалить ошибку

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

Сильно напишите мою точку зрения на интерфейс

Я читал, что некоторые люди привязывают свое представление к интерфейсу своей модели, это король интерфейса ModelView для объекта бизнес-модели. Мне нравится эта идея, но я теряю автоматическую привязку и мне нужно дублировать объекты моей модели с их конструкторами в моем веб-слое, не уверен в этом ?! Некоторая информация об этом здесь http://www.codethinked.com/post/2010/04/12/Easy-And-Safe-Model-Binding-In-ASPNET-MVC.aspx.

Использовать виды модели

Мне это просто не кажется СУХИМ ?! Я счастлив использовать их, если у меня нет подходящего объекта модели (например, я использую представление модели регистрации).

CustomModelBinder

Мой предпочтительный вариант, но я не уверен, что знаю, что делаю !! Если бы я мог просто привязать связыватель только к свойствам, которые он может устанавливать, я бы рассмеялся !!

Что думают люди? Комментарии к вышеперечисленным вариантам, любые другие решения, я не в порядке со своей архитектурой ?!

Спасибо :)


person j3ffb    schedule 12.07.2010    source источник


Ответы (3)


"Я спроектировал так, чтобы он не задавался, поэтому по дизайну я не ожидаю, что связыватель модели будет его устанавливать, но мне не нужна ошибка проверки. Я думаю, что она должна вызывать только ошибки проверки для свойств что он МОЖЕТ установить. "

Подробнее об этом дизайнерском решении читайте здесь:

http://bradwilson.typepad.com/blog/2010/01/input-validation-vs-model-validation-in-aspnet-mvc.html

Интересно, что большинство людей жаловались на полную противоположность тому, на что жалуются вы. ;)

Вы в основном говорите системе, что всегда нужно настраивать то, что не может быть установлено. Поэтому я бы не сказал, что MVC работает неправильно или что-то в этом роде. Вы просто кодируете невозможный сценарий.


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

"Если я сделаю это, я открою себя, чтобы разрешить изменение имени пользователя для существующего пользователя. Мне пришлось бы где-то добавить дополнительную логику, чтобы уловить это, на самом деле не очень хорошо. Мне также пришлось бы добавить исключение привязки для метод действия, чтобы остановить непослушных людей, пытающихся установить его через сообщение. "

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

person John Farrell    schedule 12.07.2010
comment
Спасибо за ссылку jfar, чтение было очень интересным. Я не думаю, что говорю о проверке модели и ввода. Я согласен, что вся модель должна пройти валидацию. Однако я думаю, что проблема в том, что ModelSate.isValid подталкивает вас к созданию общедоступных сеттеров для всех необходимых свойств. Так что на самом деле он ориентирован только на ModelViews. Я не считаю наличие требуемых свойств с частными сеттерами в моей модели невозможным сценарием кодирования, скорее проблемой безопасности. Устанавливаю через конструктор. Я рассмотрю вариант TryUpdateModel. - person j3ffb; 13.07.2010
comment
@ j3ffb Конечно. В MVC 1, если вы не можете привязать свойство, все будет в порядке. Теперь это не так. - person John Farrell; 13.07.2010
comment
В любом случае, похоже, он был разработан для использования с ModelView, где все свойства являются общедоступными. - person j3ffb; 13.07.2010

Я бы использовал модель представления, потому что она лучше всего подходит для работы. Не думайте о DRY, означающем, что вы не можете повторять свойства двух объектов, думайте об этом как о «не дублируйте логику или не сохраняйте идентичные данные в двух местах». В этом случае семантика работы с привязкой модели не соответствует вашей модели предметной области, поэтому вам нужен способ ее перевода.

person Ryan    schedule 12.07.2010
comment
Спасибо, Райан, я понимаю, о чем вы говорите, я полагаю, мне просто не нравится идея повторения моих объектов ради сокрытия некоторых свойств, просто кажется, что интерфейсы для этого нужны;) - person j3ffb; 13.07.2010

jfar опубликовал хорошую ссылку на сообщение Брэда Уилсона, где Брэд комментирует ...

Вы по-прежнему можете выполнять частичное редактирование, но вы больше не можете выполнять частичную проверку. Поэтому, если вы исключите привязку чего-либо с атрибутом [Required], проверка не удастся. У вас есть несколько вариантов решения этой проблемы:

  • Используйте модель представления, которая точно отражает данные формы

  • Предварительно заполните [Обязательно], но несвязанные поля данными перед вызовом (Попробуйте) UpdateModel, чтобы проверка прошла успешно (даже если вы не собираетесь что-либо делать с этими данными)

  • Позвольте ошибкам проверки произойти, а затем удалите их из ModelState после завершения проверки, поскольку они являются несоответствующими ошибками.

Мой случай, кажется, вписывается в случай «частичного редактирования», когда я не хочу, чтобы некоторые поля обновлялись.

Я рассмотрю их как решения.

person j3ffb    schedule 13.07.2010