私は 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;
}
}
}
私が目指した設計では、オブジェクトが構築された後、特定のプロパティのみを編集できます。たとえば、 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; }
}
}
}
次に、Web レイヤーで、ユーザーがこのオブジェクトを編集できるようにします。したがって、プロファイル コントローラーには次の 2 つのアクション メソッドがあります。
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 インターフェースの王様です。このアイデアは気に入っていますが、自動バインドが失われ、Web レイヤーでモデル オブジェクトとそのコンストラクターを複製する必要があります。これに関する情報はhttp://www.codethinked.com/post/2010/04/12/Easy-And-Safe-Model-Binding-In-ASPNET-MVC.aspxにあります。
モデル ビューを使用する
これは私にはDRYに見えませんか?! 適合する既存のモデル オブジェクトがない場合は、喜んでこれらを使用します (たとえば、サインアップ モデル ビューを使用します)。
CustomModelBinder
私の好みのオプションですが、自分が何をしているのかわかりません!! 設定できるプロパティにのみバインドするようにバインダーを取得できたら、笑ってしまいます!!
人々はどう思いますか?上記のオプションに関するコメント、その他の解決策はありますか?私のアーキテクチャとは的外れですか?!
ありがとう :)