ASP.NET MVC 5 表单验证和错误处理

3

我正在尝试在一个简单的联系表单上实现数据验证和错误处理。当我添加 ModelState.IsValid 检查时,我陷入了进退两难的境地。我已经查看了其他类似的问题,但仍然无法理解。我正在从 Web Forms 转向 MVC,感到困难重重。我试图根据发生的情况切换 HTML 元素 - 成功/错误消息等等。目前甚至没有验证起作用。

目前我只是想使服务器端验证工作,但我很乐意听取如何添加客户端验证的建议;例如,是否需要使用 jQuery 进行此操作,还是有其他内置的选项?

视图:

@using (Html.BeginForm("Contact", "Home", FormMethod.Post))
{
    if (ViewData["Error"] == null && ViewData["Success"] == null)
    {
        <h3>Send us an email</h3>
        Html.ValidationSummary(true);
        <div class="form-group">
            <label class="sr-only" for="contact-email">Email</label>
            <input type="text" name="email" placeholder="Email..."   
                class="contact-email" id="contact-email">
        </div>
        <div class="form-group">
            <label class="sr-only" for="contact-subject">Subject</label>
            <input type="text" name="subject" placeholder="Subject..."
                class="contact-subject" id="contact-subject">
        </div>
        <div class="form-group">
             <label class="sr-only" for="contact-message">Message</label>
             <textarea name="message" placeholder="Message..." 
                 class="contact-message" id="contact-message"></textarea>
        </div>
        <button type="submit" class="btn">Send it</button>
        <button type="reset" class="btn">Reset</button>
    }   
    else if (ViewData["Error"] == null && ViewData["Success"] != null)
    {
        <h4>We will get back to you as soon as possible!</h4>
        <p>
            Thank you for getting in touch with us. If you do not hear 
            from us within 24 hours, that means we couldn't contact you 
           at the email provided. In that case, please feel free to call 
           us at (xxx) xxx-xxxx at any time.
        </p>
    }
    else if (ViewData["Error"] != null)
    {
        <h3>Oops!</h3>
        <p>
            We apologize. We seem to be having some problems.
        </p>
        <p>
            Please come back and try again later. Alternatively, 
            call us anytime at (xxx) xxx-xxxx.
        </p>
    } 
}

模型:

public class ContactModel
{
    [Required(ErrorMessage = "Email address is required")]
    [EmailAddress(ErrorMessage = "Invalid Email Address")]
    public string Email { get; set; }

    [Required(ErrorMessage = "Subject is required")]
    public string Subject { get; set; }

    [Required(ErrorMessage = "Message is required")]
    public string Message  { get; set; }
}

控制器:

[HttpPost]        
public ActionResult Contact(ContactModel contactModel)
{
    if (ModelState.IsValid)
    {
        try
        {
            MailMessage message = new MailMessage();

            using (var smtp = new SmtpClient("mail.mydomain.com"))
            {
                // Standard mail code here

                ViewData["Success"] = "Success";
             }
         }
         catch (Exception)
         {
             ViewData["Error"] 
                 = "Something went wrong - please try again later.";
                    return View("Error");
         }
     }
     return View();
}

错误视图:

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Error</title>
</head>
<body>
    <hgroup>
        <h1>Error.</h1>
        <h2>An error occurred while processing your request.</h2>
    </hgroup>
</body>
</html>

更新-05/09/2017

根据Guruprasad的回答,如果ModelState.IsValid评估为false,那么表单上没有报告验证错误消息。

注意:我不想向用户报告系统错误,因此必须更改AddModelError签名以不使用“Extension ex”参数:ModelState.AddModelError("Error", "Server side error occurred");

还要注意,此时我仅在服务器端尝试验证(尚未解决客户端验证问题)。由于未显示任何模型错误,因此我已将Contact.cshtml视图更新如下-我已包括Bootstrap .has-error.help-block CSS规则用于验证错误:

@using (Html.BeginForm("Contact", "Home", FormMethod.Post))
{
    <h3>Send us an email</h3>
    Html.ValidationSummary(true);
    <div class="form-group has-error">
        <label class="sr-only" for="contact-email">Email</label>
        @Html.TextBoxFor(m => m.Email, new { type = "text", name = "email",
            placeholder = "Email..", @class = "contact-email" })
        @Html.ValidationMessageFor(model => model.Email, String.Empty, 
            new { @class="help-block" })
    </div>
    <div class="form-group has-error">
        <label class="sr-only" for="contact-subject">Subject</label>
        @Html.TextBoxFor(m => m.Subject, new { type = "text", 
            name = "subject", 
            placeholder = "Subject..", @class = "contact-subject" })
        @Html.ValidationMessageFor(model => model.Subject, String.Empty, 
            new { @class = "help-block" })
    </div>
    <div class="form-group has-error">
        <label class="sr-only" for="contact-message">Message</label>
        @Html.TextAreaFor(m => m.Message, new { name = "message", 
            placeholder = "Message..", @class = "contact-message" })
        @Html.ValidationMessageFor(model => model.Message, String.Empty, 
            new { @class = "help-block" })
    </div>
    <button type="submit" class="btn">Send it</button>
    <button type="reset" class="btn">Reset</button>

    if (ViewData["Success"] != null)
    {
        <h4>We will get back to you as soon as possible!</h4>
        <p>
            Thank you for getting in touch with us. If you do not hear 
            from us within 24 hours, that means we couldn't contact you 
            at the email provided. In that case, please feel free to 
            call us at (xxx) xxx-xxxx at any time.
        </p>
    }
}

你是否已经添加了 jQuery 验证脚本? - Bat_Programmer
是的,作为项目模板的一部分。它是版本1.11.1 - 我应该更新吗? - IrishChieftain
1
你调试过了吗?当 ModelState.IsValid 为 false 时,有什么错误存在?另外,如果你不想让系统错误报告给用户,那么可以在 web.config 文件中进行配置,在其中为不同的错误代码设置自定义视图。另外,请查看 ExceptionFilter,你可以扩展它以显示不同的自定义视图来处理不同类型的错误。如果你在这方面遇到任何问题,请告诉我。 - Guruprasad J Rao
感谢您的反馈,我在添加了@Html.ValidationMessageFor后成功显示了模型错误。 - IrishChieftain
1个回答

10

这里有多个需要了解的事情,让我一点一点来。

  • 很好,你已经设计好了你的model,但是你的view如何知道它有一个model可以绑定自己,并且在提交form内容时,server如何知道有一个model需要接收。因此,在第一次实例中,您需要构建您的视图,绑定model。要在view中绑定model,您需要首先在顶部获取/声明引用,让视图知道,好的,这里有一个model可以生成我的view

  • 好吧,如果您将ValidationSummary设置为true,那么我建议您使用ModelState.AddModelError而不是使用ViewData传递错误消息,并让ValidationSummary处理。附带说明,您可能还想处理此问题,您可以通过该帖子中提到的答案来解决相同的问题。如果您不使用或不想使用Html.ValidationSummary,那么您可以坚持使用当前视图。

  • 现在,要显示Success消息,您可以使用TempDataViewData,并按照您现在视图中的相同结构进行操作。这里有另一篇文章让您处理它。

  • 最后,也是最重要的view部分是将model属性绑定到view元素。使用Razor View扩展帮助程序为您的model生成view。您有@Html.TextBoxFor@Html.TextAreaFor等,还有@Html.TextBox@Html.TextArea,它们不是用于绑定model properties,而只是生成一个普通的HTML view。您可以在这些helpers中添加其他html属性,如下面更新的view所示。我建议您深入挖掘这些帮助程序提供的重载。

所以这就是您更新后的视图。

@model SOTestApplication.Models.ContactModel   @*getting model reference*@

@using (Html.BeginForm("Contact", "Home", FormMethod.Post))
{
    <h3>Send us an email</h3>
    Html.ValidationSummary(true);
    <div class="form-group">
        <label class="sr-only" for="contact-email">Email</label>
        @Html.TextBoxFor(m => m.Email, new { type = "text", name = "email", placeholder = "Email..", @class = "contact-email" })
        @*Usage of helpers and html attributes*@
    </div>
    <div class="form-group">
        <label class="sr-only" for="contact-subject">Subject</label>
        @Html.TextBoxFor(m => m.Subject, new { type = "text", name = "subject", placeholder = "Subject..", @class = "contact-subject" })
    </div>
    <div class="form-group">
        <label class="sr-only" for="contact-message">Message</label>
        @Html.TextAreaFor(m => m.Message, new { name = "message", placeholder = "Message..", @class = "contact-message" })
    </div>
    <button type="submit" class="btn">Send it</button>
    <button type="reset" class="btn">Reset</button>
}
if (ViewData["Success"] != null)
{
    <h4>We will get back to you as soon as possible!</h4>
    <p>
        Thank you for getting in touch with us. If you do not hear
        from us within 24 hours, that means we couldn't contact you
        at the email provided. In that case, please feel free to call
        us at (xxx) xxx-xxxx at any time.
    </p>
}

控制器端验证

这部分看起来很不错,没有什么需要特别提醒的。但是基于我之前提到的一些问题,建议您在错误信息中使用 ModelState.AddModelError 而不是使用 ViewData。在视图中消除您的 if 条件,这样即使在 postback 后,联系表单 仍然存在。如果您想在 服务器端 验证后保留值,则只需在 post 方法中将 model 回传给您的 view 即可。更新后的 Controller 如下:

[HttpPost]
public ActionResult Contact(ContactModel contactModel)
{
     if (ModelState.IsValid)
     {
          try
          {
              MailMessage message = new MailMessage();
              using (var smtp = new SmtpClient("mail.mydomain.com"))
              {
                  // Standard mail code here
                  ViewData["Success"] = "Success";
              }
          }
          catch (Exception)
          {
              ModelState.AddModelError("Server side error occurred", ex.Message.ToString());
          }
     }
     return View(contactModel); //this will persist user entered data on validation failure
}

客户端验证

就这一部分而言,您需要在应用程序中设置更多内容。

  • 您需要将 Html.EnableClientValidation(true);Html.EnableUnobtrusiveJavaScript(true); 添加到您的应用程序中。有各种可能的添加方式。您可以在 Web.config 文件的 appSettings 下全局应用此项,或者您可以根据下面更新的 View 示例,在特定的 view 中添加此项。

在 Web.Config 中进行全局应用示例:

<appSettings>
  <add key="ClientValidationEnabled" value="true" />
  <add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
  • 如果你已经注意到了位于 App_Start 目录下的 BundleConfig.cs 文件,你会看到下面的默认条目。这些是 jquery 的内容,负责你的客户端验证。

jQueryjQueryVal 条目

bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                    "~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                    "~/Scripts/jquery.unobtrusive*",
                    "~/Scripts/jquery.validate*"));
  • 下一步是将这些文件添加为引用/使用@section Scripts将这些呈现在_Layout.cshtml或任何特定的view中。当您在_Layout.cshtml中包含此内容时,在其他视图中使用此layout时,这些脚本/捆绑包也会呈现出来。所以基本上,您可以自己决定在哪里呈现这些。

例如,我会在添加对model的引用后很快将它们呈现在Contact.cshtml view中。

@section Scripts
{
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/jqueryval")
}
  • 在此,让这个工作的最后一件事是你需要使用@Html.ValidationMessageFor Razor扩展,并让MVC绑定特定属性上的错误消息。还需要为这些错误消息在View中显示,需要为模型中的每个属性指定ErrorMessage,就像您现在为模型中的每个属性使用Required(ErrorMessage=...一样。如果您详细了解这些内容,则还有更多内容可供了解。

添加适当验证后更新的视图。

@model SOTestApplication.Models.ContactModel
@section Scripts
{
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/jqueryval")
}
@using (Html.BeginForm("Contact", "Contacts", FormMethod.Post))
{

    <h3>Send us an email</h3>
    Html.ValidationSummary(true);
    Html.EnableClientValidation(true);
    Html.EnableUnobtrusiveJavaScript(true);
    <div class="form-group">
        <label class="sr-only" for="contact-email">Email</label>
        @Html.TextBoxFor(m => m.Email, new { type = "text", name = "email", placeholder = "Email..", @class = "contact-email" })
        @Html.ValidationMessageFor(m => m.Email)
    </div>
    <div class="form-group">
        <label class="sr-only" for="contact-subject">Subject</label>
        @Html.TextBoxFor(m => m.Subject, new { type = "text", name = "subject", placeholder = "Subject..", @class = "contact-subject" })
        @Html.ValidationMessageFor(m => m.Subject)
    </div>
    <div class="form-group">
        <label class="sr-only" for="contact-message">Message</label>
        @Html.TextAreaFor(m => m.Message, new { name = "message", placeholder = "Message..", @class = "contact-message" })
        @Html.ValidationMessageFor(m => m.Message)
    </div>
    <button type="submit" class="btn">Send it</button>
    <button type="reset" class="btn">Reset</button>
    if (ViewData["Success"] != null)
    {
        <h4>We will get back to you as soon as possible!</h4>
        <p>
            Thank you for getting in touch with us. If you do not hear
            from us within 24 hours, that means we couldn't contact you
            at the email provided. In that case, please feel free to call
            us at (xxx) xxx-xxxx at any time.
        </p>
    }
}

希望这些要点能够解决你大部分的疑问。愉快地编码.. :)

2
非常详细的回答。非常感谢您的努力。我今天稍后会尝试这个 :) - IrishChieftain
1
嗨Guruprasad,我不得不更新问题,因为你的解决方案对我没有用 - 仍然只在服务器端工作。 - IrishChieftain
我已经成功地渲染出了成功消息,但是表单字段(填充)仍然在回传时出现在成功消息上方?我将if(ViewDate[])代码块从using块中取出(根据您的示例),并在其前面添加了“@”。但是在回传时,我仍然会看到填充的表单字段和成功消息一起呈现... - IrishChieftain
1
那没有任何效果,但我通过在视图中整个using{}块周围放置if/else来使其工作。感谢您的帮助! :) - IrishChieftain
1
随时可以的伙计.. 开心编程.. :) - Guruprasad J Rao
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接