1

我正在使用 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 意义,但这是我的问题的关键,所以我想在这里强调它。

然后我有一个“伙伴类”,它为我的用户类定义了验证元数据......

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 层中,我想让我的用户能够编辑这个对象。所以我的配置文件控制器中有以下两种操作方法。

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 属性上有一个私有设置器。然而这是设计使然,出于安全和语义的考虑,我不希望他们更改用户名,因此 setter 是私有的。但是,由于我已将Required 属性添加到属性中,因此它未设置而失败!

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

好的,到目前为止我已经提出了可能的解决方案..

公开财产。

如果我这样做,我会敞开心扉,允许为现有用户更改用户名。我必须在某处添加额外的逻辑才能捕捉到这一点,这不是很好。我还必须在操作方法上添加一个绑定排除,以阻止任何顽皮的人试图通过帖子设置它。

删除错误

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

针对界面强烈键入我的视图

我读过一些人将他们的视图绑定到他们模型的接口,这是模型视图接口到业务模型对象的王者。我喜欢这个想法,但我失去了自动绑定,需要在我的 web 层中复制我的模型对象及其构造函数,不确定吗?!一些信息在这里http://www.codethinked.com/post/2010/04/12/Easy-And-Safe-Model-Binding-In-ASPNET-MVC.aspx

使用模型视图

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

自定义模型绑定器

我的首选,但我不确定我知道我在做什么!!如果我能让活页夹只绑定到它可以设置的属性,那我会笑的!

人们怎么想?对上述选项的评论,任何其他解决方案,我的架构是否与我的架构格格不入?!

谢谢 :)

4

3 回答 3

2

我已经设计为不设置它,因此根据设计我不希望模型绑定器设置它,但我不想要验证错误。我认为它应该只对它可以的属性产生验证错误设置。

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

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

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

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


总的来说,您刚刚遇到了 metadatabuddy 技术的痛点。主要是需要对新场景和编辑场景进行不同的验证。

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

恕我直言,您对这些代码更改的暴饮暴食。您将向单个方法调用添加一个简单的字符串。有什么大不了的?我会在这里采取务实的方法。

于 2010-07-12T17:37:56.360 回答
2

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

于 2010-07-12T17:49:51.713 回答
0

jfar 发布了一个很好的链接,指向 Brad Wilson 的帖子,其中 Brad 评论...

您仍然可以进行部分编辑,但不能再进行部分验证。因此,如果您排除使用 [Required] 属性绑定某些内容,则验证将失败。你有几个选择来解决这个问题:

  • 使用完全反映表单数据的视图模型

  • 在调用 (Try)UpdateModel 之前使用数据预先填写 [Required] 但未绑定的字段,以便验证成功(即使您不打算对这些数据做任何事情)

  • 允许发生验证错误,然后在验证完成后将它们从 ModelState 中删除,因为它们是不适当的错误。

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

我将研究这些作为解决方案。

于 2010-07-13T18:57:02.053 回答