【问题标题】:MVC ModelState IsValid reports true when should be falseMVC ModelState IsValid 在应该为 false 时报告 true
【发布时间】:2014-05-07 16:28:58
【问题描述】:

我有以下 3 个类(现在包括所有代码)...

using System.ComponentModel.DataAnnotations;

namespace MyWebApp.Models
{
    public class ApplicationUser
    {
        [Display(Prompt = "Your Name", Name = "Contact Name"), Required(), StringLength(255, MinimumLength = 3, ErrorMessage = "Please enter a valid contact")]
        public string ContactName { get; set; }
        [Display(Name = "Username", Prompt = "Login As"), Required(), StringLength(255, MinimumLength = 3, ErrorMessage = "Your username must be at least 3 characters")]
        public string UserName { get; set; }
    }

    public class Account
    {
        [Display(Name = "Account Type", Prompt = "Account Type"), Required()]
        public int AccountType { get; set; }
        [Display(Name = "Organisation Name", Prompt = "Name of Organisation"), StringLength(255)]
        public string OrganisationName { get; set; }
    }

    public class RegisterViewModel
    {
        public ApplicationUser ApplicationUser { get; set; }
        public Account Account { get; set; }

        public RegisterViewModel()
        {
            ApplicationUser = new ApplicationUser();
            Account = new Account();
        }

        [Display(Name = "Password", Prompt = "Password"), Required(), DataType(DataType.Password), StringLength(255, MinimumLength = 5, ErrorMessage = "The password must be at least 7 characters")]
        public string Password { get; set; }

        [Display(Name = "Confirm Password", Prompt = "Confirm Password"), DataType(DataType.Password), Compare("Password", ErrorMessage = "Your confirmation doesn't match...")]
        public string PasswordConfirmation { get; set; }
    }
}

我的控制器看起来像这样...

using System.Web.Mvc;
using MyWebApp.Models;

namespace MyWebApp.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        // GET: /Account/
        [AllowAnonymous]
        public ActionResult Register()
        {
            RegisterViewModel mdl = new RegisterViewModel();
            return View(mdl);
        }

        [HttpPost]
        [AllowAnonymous]
        public ActionResult Register([Bind(Include = "Account.AccountType, ApplicationUser.ContactName, ApplicationUser.UserName, Password, Account.OrganisationName, Account.OrganisationRegistrationNumber, Account.AddressLine1, Account.AddressLine2, Account.AddressLine3, Account.City, Account.County, Account.Postcode, ApplicationUser.Email, ApplicationUser.PhoneNumber, PasswordConfirmation")]RegisterViewModel model)
        {
            if (ModelState.IsValid)
            {
                var user = model.ApplicationUser;
                var associatedAccount = model.Account;

                var u1 = Request.Form["ApplicationUser.UserName"];
                if (u1 == user.UserName)
                {
                    // we got it
                    ViewBag.Message = "We got it";
                }
                else
                {
                    // no we didn't
                    ViewBag.Message = "We failed!";
                }
                return View(model);
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }
    }
}

我的 Register.cshtml 视图如下所示...

@using MyWebApp.Models
@model MyWebApp.Models.RegisterViewModel

@{
    ViewBag.Title = "Register User";
}

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>New Account</h4>
        <hr />
        <p>@ViewBag.Message</p>
        @Html.ValidationSummary(true)
        <div class="col-md-6">
            <div class="form-group">
                @Html.LabelFor(model => model.ApplicationUser.ContactName, new { @class = "control-label col-md-5" })
                <div class="col-md-7">
                    @Html.EditorFor(model => model.ApplicationUser.ContactName, new { @class = "form-control" })
                    @Html.ValidationMessageFor(model => model.ApplicationUser.ContactName)
                </div>
            </div>
            <div class="form-group">
                @Html.LabelFor(model => model.ApplicationUser.UserName, new { @class = "control-label col-md-5" })
                <div class="col-md-7">
                    @Html.EditorFor(model => model.ApplicationUser.UserName, new { @class = "form-control" })
                    @Html.ValidationMessageFor(model => model.ApplicationUser.UserName)
                </div>
            </div>
            <div class="form-group">
                @Html.LabelFor(model => model.Password, new { @class = "control-label col-md-5" })
                <div class="col-md-7">
                    @Html.EditorFor(model => model.Password, new { @class = "form-control" })
                    @Html.ValidationMessageFor(model => model.Password)
                </div>
            </div>
            <div class="form-group">
                @Html.LabelFor(model => model.PasswordConfirmation, new { @class = "control-label col-md-5" })
                <div class="col-md-7">
                    @Html.EditorFor(model => model.PasswordConfirmation, new { @class = "form-control" })
                    @Html.ValidationMessageFor(model => model.PasswordConfirmation)
                </div>
            </div>
        </div>
        <div class="col-md-6">
            <div class="org-info">
                <div class="form-group">
                    @Html.LabelFor(model => model.Account.OrganisationName, new { @class = "control-label col-md-5" })
                    <div class="col-md-7">
                        @Html.EditorFor(model => model.Account.OrganisationName, new { @class = "form-control" })
                        @Html.ValidationMessageFor(model => model.Account.OrganisationName)
                    </div>
                </div>
            </div>
            <hr />
            <div class="form-group">
                <div class="col-md-8"></div>
                <div class="col-md-4">
                    <input type="submit" value="Sign Up" class="btn btn-default form-control" />
                </div>
            </div>
        </div>
    </div>
}

我的问题是,当用户点击注册而不在视图中输入任何详细信息时,只有密码和密码确认字段显示有问题,帐户或应用程序用户属性均未显示问题。

如果我输入密码和确认字段的详细信息并在 Register 方法中放置一个断点,即使我没有添加任何帐户或用户详细信息,ModelState.IsValid 值也是 true。

如果我输入了用户名,正在评估...

Request.Form["ApplicationUser.UserName"]

给出了正确的值,但我认为应该由 Bind 填充的 ApplicationUser.UserName 什么都没有!?

我怀疑这是我的 Bind 声明,但我尝试了 UserName、ApplicationUser.UserName 和 ApplicationUser_UserName,但似乎都有同样的问题。

这个问题来自我提出的另一个问题(下面的链接),那么我错过了什么?

Entity Framework 6 Reusing Data Annotations

请注意,我对这个特定实现中的问题感兴趣,而不是出于各种我不想深入讨论的原因提供替代实现。

【问题讨论】:

  • 你能展示一下你的观点吗?
  • 我从来没有像你这样使用过带有嵌入式类的视图模型,但我会为ApplicationUserAccount 添加一个[Required] 属性来做什么?
  • 请发表您的看法。我的概念证明在有和没有构造函数以及有和没有 Bind 属性的情况下都有效。它必须与您的视图以及您发布值的方式有关。
  • 刚刚添加了我正在使用的视图字段示例和更多信息。
  • 添加了完整的代码以允许人们复制和粘贴。

标签: c# asp.net-mvc entity-framework validation data-annotations


【解决方案1】:

我已下载您的代码并将其包含在一个新的空 MVC4 应用程序中。

坏消息是它没有用。

好消息是,只需稍作改动即可完美运行:

在您的控制器中,

  • 要么删除Register POST 操作参数的Bind 属性
  • 或使用这个:[Bind(Include = "Account, ApplicationUser, Password, PasswordConfirmation")]

还有一对关于你的代码的 cmets:

  • 除非您想明确包含或排除模型的一部分(通常出于安全原因),否则您不需要使用Bind 属性。您宁愿创建一个具有您需要的确切属性的 ViewModel(这样您键入和维护的代码更少!)。
  • 您不需要提供默认构造函数来初始化嵌套对象。 MVC 模型绑定器将自动实例化它们。事实上,我建议您不要这样做:如果您忘记在视图中包含嵌套对象的属性,则该嵌套对象应该为空,而不是具有空属性的对象。这会造成很多混乱!

【讨论】:

  • 感谢您的关注,非常感谢。我很高兴我的代码为您显示了这个问题。我想你的回答已经让我明白了。从您所说的来看,如果不在视图级别指定要绑定到的附加属性,就不可能单独绑定到内部对象的属性?这对我的原始数据注释重用问题有影响吗?
  • 对不起,我不明白你问题的第一部分(一个简单的例子就可以了!!)。至于原来的问题,它会工作得很好,你会发现没有问题:如果嵌套属性的类型(类)有一个伙伴类,它的注释被正确应用。我做了一个概念证明来测试它。所以你可以拥有实体、伙伴类、视图模型的组合......它会工作得很好。您可以修改您的示例项目,并将注释移至伙伴类,您会看到它完美运行。
  • 问题说明的第一部分...您的回答指出删除绑定有效,绑定到帐户和 ApplicationUser 有效(我同意,因为我只是按照您所说的,这很好),但绑定到 ApplicationUser。 UserName 不起作用,所以这告诉我你不能直接绑定到单个内部对象属性,但必须在 ViewModel 中创建另一个属性,例如字符串用户名 { 设置 { ApplicationUser.UserName=Value; } } 然后绑定到“用户名”。我只是问你是否同意这种说法?我现在就试试 MetadataType。
  • 我会让 cmets 与问题相关,并在另一个问题上评论 MetadataType 实现。
  • 如果您指的是 Bind 属性,是的,在 Bind 属性中您不能指定“子属性”:它只支持根级别的属性。问题是:为什么要使用 Bind 属性而不是默认绑定?如果您害怕过度发布,请使用仅具有所需属性的模型,或丢弃控制器中易受攻击的属性后缀。如果你使用伙伴类,你可以贴出一个属性比他的伙伴少的模型,它工作得很好。为什么要使用绑定?
【解决方案2】:

尝试在 RegisterViewModel 本身中包含 ApplicationUser 和 Account 属性。

    public class RegisterViewModel
{
    [Display(Prompt = "Your Name", Name = "Contact Name"), Required(), StringLength(255, MinimumLength = 3, ErrorMessage = "Please enter a valid contact")]
    public string ContactName { get; set; }
    [Display(Name = "Username", Prompt = "Login As"), Required(), StringLength(255, MinimumLength = 3, ErrorMessage = "Your username must be at least 3 characters")]
    public string UserName { get; set; }
[Display(Name = "Account Type", Prompt = "Account Type"), Required()]
    public AccountType AccountType { get; set; }
    [Display(Name = "Organisation Name", Prompt = "Name of Organisation"), StringLength(255)]
    public string OrganisationName { get; set; }
    public RegisterViewModel()
    {

    }

    [Display(Name = "Password", Prompt = "Password"), Required(), DataType(DataType.Password), StringLength(255, MinimumLength = 5, ErrorMessage = "The password must be at least 7 characters")]
    public string Password { get; set; }

    [Display(Name = "Confirm Password", Prompt = "Confirm Password"), Compare("Password", ErrorMessage = "Your confirmation doesn't match...")]
    public string PasswordConfirmation { get; set; }
}

【讨论】:

  • 谢谢,我知道可以将所有内容都包含在视图模型中,正如您从我帖子底部的链接中看到的那样,这样做是为了同时将内容保存在自己的封装对象中,因此前提条件是 Account 和 ApplicationUser 对象必须保持原样,重复使用它们的 Annotations。
【解决方案3】:

您可能想要删除构造函数(因为它们是必需的,如果它们为 null 则可以)

public RegisterViewModel()
{
    ApplicationUser = new ApplicationUser();
    Account = new Account();
}

并使ApplicationUserAccount 属性成为必需。

在绑定期间进行验证,如果这两个属性都没有绑定,那么您的模型状态将在设计上有效。

【讨论】:

    猜你喜欢
    • 2020-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多