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