ModelState报告为ASP.NET MVC 2中模型上的私有属性无效

时间:2010-07-12 15:56:22

标签: asp.net-mvc architecture modelbinders modelstate

我正在使用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只能在构造对象时设置,对我来说这会让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>

好的,这就是所有的代码了!

所以这就是问题,在初始get请求之后视图会很好。但是当用户将表单发回时,比如在编辑其显示名称之后,ModelState无效。这是因为UserName属性上有一个私有的setter。然而,这是设计的,为了安全性和语义,我不希望他们更改用户名,因此setter是私有的。但是,由于我已将必需属性添加到属性中,因此未设置它会失败!

问题是模型绑定是否应该将此报告为验证错误?!由于该属性是私有的,我设计它不设置,因此通过设计我不希望模型绑定器设置它,但我不希望验证错误。我认为它应该只为它设置的属性产生验证错误。

好的,我到目前为止提出的解决方案......

将该物业公之于众。

如果我这样做,我会打开自己,允许为现有用户更改用户名。我必须在某个地方添加额外的逻辑来捕捉这个,不是很好。我还必须在动作方法上添加一个Bind Exclude来阻止任何顽皮的人试图通过帖子设置它。

删除错误

我相信我可以从ModelState字典中删除错误,这次会很好,但我认为这会引入一些代码气味,因为我必须为所有具有私有设置器的对象添加此代码。我可能会忘记!!

强烈地在界面上输入我的视图

我已经读到有些人将他们的视图绑定到他们模型的接口,这是ModelView接口到业务模型对象的王者。我喜欢这个想法,但是我放弃了自动绑定,并且需要在我的web层中使用它们的构造函数复制我的模型对象,不确定吗?!有关此问题的一些信息http://www.codethinked.com/post/2010/04/12/Easy-And-Safe-Model-Binding-In-ASPNET-MVC.aspx

使用模型视图

这对我来说似乎不干嘛?!如果我没有适合的现有模型对象(例如我使用注册模型视图),我很乐意使用它们。

CustomModelBinder

我的首选,但我不确定我知道自己在做什么!如果我可以让绑定器只绑定它可以设置的属性,那么我会笑!

人们怎么想?关于上述选项的评论,任何其他解决方案,我对我的架构都不合适吗?!

谢谢:)

3 个答案:

答案 0 :(得分:2)

我设计的它没有被设置,因此在设计上我不希望模型绑定器设置它,但我不想要验证错误。我认为它应该只产生验证它可以设置的属性的错误。

在此处阅读有关此设计决策的更多信息:

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

有趣的是,大多数人抱怨与你的抱怨完全相反。 ;)

您基本上告诉系统应始终设置无法设置的内容。所以我不会说MVC工作不正确或类似的东西。你刚刚编写了一个不可能的场景。


总的来说,你刚刚达到了metadatabuddy技术的痛点。主要是需要对新的和编辑方案进行不同的验证。

如果我这样做,我打开自己,允许为现有用户更改用户名。我必须在某处添加额外的逻辑来捕捉这个,不是很好。我还需要添加一个绑定动作方法以阻止任何顽皮的人试图通过帖子设置它。

恕我直言,你对这些代码的改变过度了。您将向单个方法调用添加一个简单的字符串。什么是重要的?我会采取实用的方法。

答案 1 :(得分:2)

我使用视图模型,因为它最适合这项工作。不要认为DRY意味着您不能在两个对象上重复属性,将其视为“不要复制逻辑或在两个地方保留相同的数据”。在这种情况下,处理模型绑定的语义与您的域模型不匹配,因此您需要一种方法来翻译它。

答案 2 :(得分:0)

jfar在布拉德·威尔逊的帖子中发布了一个很好的链接,布拉德评论......

  

你仍然可以进行部分编辑,但是   你不能做任何部分验证   更多。所以如果你排除绑定   与[必需]的东西   属性,然后验证将失败。   你有几个选择可以解决   这样:

     
      
  • 使用完全镜像表单数据的视图模型

  •   
  • 在调用前预先填写[必需]但未绑定的数据字段   (试试)UpdateModel这样的   验证将成功(即使   你不打算做什么   那个数据)

  •   
  • 允许发生验证错误,然后将其删除   完成验证后的ModelState,   因为他们是不恰当的错误。

  •   

我的案例似乎符合“部分编辑”的情况,我不希望更新某些字段。

我会将这些视为解决方案。