ModelState объявлен недействительным для частных свойств модели в ASP.NET MVC 2 - PullRequest
1 голос
/ 12 июля 2010

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

Хорошо, у меня есть чистое пользовательское 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;
        }
    }
}

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

Затем у меня есть «класс приятелей», который определяет метаданные проверки для моего класса 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>

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

Так вот в чем проблема, представление отображается нормально после первоначального запроса get. Но когда пользователь отправляет форму обратно, скажем, после редактирования своего отображаемого имени, 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

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

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

Спасибо:)

Ответы [ 3 ]

2 голосов
/ 12 июля 2010

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

2 голосов
/ 12 июля 2010

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

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

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

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

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


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

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

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

0 голосов
/ 13 июля 2010

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

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

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

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

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

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

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

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