Я использую 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
Мой предпочтительный вариант, но я не уверен, что знаю, что делаю !! Если бы я мог просто привязать связыватель только к свойствам, которые он может устанавливать, я бы рассмеялся !!
Что думают люди? Комментарии к вышеперечисленным вариантам, любые другие решения, я не в порядке со своей архитектурой ?!
Спасибо :)