• MVC验证10-到底用哪种方式实现客户端服务端双重异步验证


    本篇将通过一个案例来体验使用MVC的Ajax.BeginForm或jQuery来实现异步提交,并在客户端和服务端双双获得验证。希望能梳理、归纳出一个MVC异步验证的通用解决思路。本篇主要涉及:

    1、通过Ajax.BeginForm()方式,返回部分视图显示验证信息。
    2、通过jQuery+Html.BeginForm()方式,返回部分视图显示验证信息。
    3、通过jquery,返回json字符串,json字符串中包含部分视图及验证信息。

    此外,如下2篇是本文的"兄弟篇",只不过没有像本篇这样把多种实现方式放在一个案例中实现。

    MVC验证08-jQuery异步验证:通过jquery,返回字符串,并把错误信息精确显示到指定html元素。
    MVC验证09-使用MVC的Ajax.BeginForm方法实现异步验证:通过Ajax.BeginForm方式,返回部分视图显式验证信息。

      准备工作

    □ 实现客户端验证所需的js文件

    不管js文件是放在_Layout.cshtml中,还是放在具体的视图页,也不管BundleConfig.cs中捆版了那些js和css。以下js文件是必须的:
    1、jquery的某个版本
    2、jquery.validate.js
    3、jquery.validate.unobtrusive.js

    □ 实现客户端验证的配置

    在网站Web.config中,相关的属性必须设置为true:

      <appSettings>
          ...
        <add key="ClientValidationEnabled" value="true" />
        <add key="UnobtrusiveJavaScriptEnabled" value="true" />
      </appSettings>

    □ 即将用到的View Model

    using System.ComponentModel.DataAnnotations;
    using jan.Extension;
     
    namespace jan.Models
    {
        public class Customer
        {
            [Required]
            [ValidUserNameAttribue(ErrorMessage = "用户名只能为darren")]
            [Display(Name = "用户名")]
            public string UserName { get; set; }
        }
    }

    □ 自定义验证特性ValidUserNameAttribue

    using System.ComponentModel.DataAnnotations;
     
    namespace jan.Extension
    {
        public class ValidUserNameAttribue : ValidationAttribute
        {
            public override bool IsValid(object value)
            {
                //只有同时满足2个条件就让通过,否则验证失败
                return (value != null && value.ToString() == "darren");
            }
        }
    }

      1、通过Ajax.BeginForm方式,返回部分视图显示验证信息

    □ 1、1 Index.cshtml视图

    如果把Index.cshtml看作主视图的话,需要异步获取的内容放在部分视图中,主视图通过Html.Partial()来显示部分视图内容。

    @model jan.Models.Customer
     
    @{
        ViewBag.Title = "Index";
        Layout = "~/Views/Shared/_Layout.cshtml";
    }
     
    @DateTime.Now:  Index.cshtml视图被渲染
    <hr/>
     
    <div id="FormContainer">
            @Html.Partial("_Form")
    </div>

    □ 1.2 部分视图_Form.cshtml,验证失败返回的部分视图

    用Ajax.BeginForm()方法实现。

    @model jan.Models.Customer
     
    @DateTime.Now: _Form.cshtml视图被渲染
    <hr/>
     
    @using (Ajax.BeginForm("ValidCustomer", new AjaxOptions() { UpdateTargetId = "FormContainer", OnSuccess = "$.validator.unobtrusive.parse('form');" }))
    {
        <p>
            @Html.LabelFor(m => m.UserName):
            @Html.EditorFor(m => m.UserName)
        </p>
        <p style="color:red;">
            @Html.ValidationMessageFor(m => m.UserName)
        </p>
        <input type="submit" value="提交"/>
    }
     

    UpdateTargetId = "FormContainer"中的FormContainer是主视图的div,部分视图异步提交返回的内容显示到id为FormContainer的div中。
    OnSuccess = "$.validator.unobtrusive.parse('form');" 每次提交完后再初始化表单,准备下一次被提交。

    □ 1.3 _Success.cshtml,验证成功返回的部分视图

    @model jan.Models.Customer
    @Model.UserName 是有效的

    □ 1.4 HomeController

    using System.Web.Mvc;
    using jan.Models;
     
    namespace jan.Controllers
    {
        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                return View(new Customer());
            }
     
            [HttpPost]
            public ActionResult ValidCustomer(Customer customer)
            {
                return PartialView(!ModelState.IsValid ? "_Form" : "_Success", customer);
            }
        }
    }
     

    □ 1.5 效果

    提交之前:

    1.1


    没有填写任何内容,提交报错:

    1.2


    没有输入darren,提交报错:

    1.3


    输入正确,提交成功:

    1.4

      2、通过jQuery+Html.BeginForm方式,返回部分视图显示验证信息

    □ 2.1 Index.cshtml视图

    增加了一个动态显示加载的div,使用了jquery ui的progress dialog,提交的时候显示加载图片。

    展开


    □ 2.2 部分视图_Form.cshtml,验证失败返回的部分视图

    点击"提交"触发jquery中的表单提交事件。

    @model jan.Models.Customer
     
    @DateTime.Now: _Form.cshtml视图被渲染
    <hr/>
     
    @using (Html.BeginForm("ValidCustomer", "Home"))
    {
        
        <p style="color:red;">
             @Html.ValidationMessageFor(m => m.UserName)
        </p>
        
        <p>
            @Html.LabelFor(m => m.UserName):
            @Html.EditorFor(m => m.UserName)
        </p>
     
        <input type="submit" value="提交" />
    }
     

    □ 2.3 _Success.cshtml,验证成功返回的部分视图

    @model jan.Models.Customer
    @Model.UserName 是有效的

    □ 2.4 HomeController

    其中的Thread.Sleep(2000)是模拟请求时间稍长,前台视图显式加载效果。

    using System.Web.Mvc;
    using jan.Models;
     
    namespace jan.Controllers
    {
        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                return View(new Customer());
            }
     
            [HttpPost]
            public ActionResult ValidCustomer(Customer customer)
            {
                Thread.Sleep(2000); 
                return PartialView(!ModelState.IsValid ? "_Form" : "_Success", customer);
            }
        }
    }
     

    □ 2.5 效果

    提交之前:

    2.1


    没有填写任何内容,提交报错:

    2.2


    没有输入darren,提交报错:

    2.2没有冒泡之前


    虽然报错,但注意到存在一个问题:地址变成了/Home/ValidCustomer?而在Index.cshtml的jquery中,让每次提交成功后,返回的部分视图渲染到Index.cshtml的id为FormContainer的div中。为什么?

    每次返回部分视图被渲染到Index.cshtm中id为FormContainer的div中,这部分属动态内容,而类似$("form").on("submit", function (event)这样的写法,对动态内容是无效的。根据"DOM冒泡"的事实,应该把submit事件注册给form的父元素,当点击form中的提交按钮,根据"DOM冒泡",触发了form父元素的submit事件,而包含在form父元素下的所有动态内容,此时会受到submit事件的影响。Index.cshtm中完整js如下:

    展开

    再次输入错误的用户名,提交,也报错,但没有跳转到/Home/ValidCustomer。

    2.2没有冒泡之后


    输入正确,提交成功:

    1.4

      3、通过jquery,返回json字符串,json字符串中包含部分视图及验证信息

    □ 3.1 HomeController

    返回给前台json字符串,一个key用来提示是否验证成功,一个key是部分视图的html元素字符串。

    using System.Web.Mvc;
    using jan.Extension;
    using jan.Models;
    using System.Threading;
     
    namespace jan.Controllers
    {
        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                return View(new Customer());
            }
     
            [HttpPost]
            public ActionResult ValidCustomer(Customer customer)
            {
                Thread.Sleep(2000);
                if (!ModelState.IsValid)
                {
                    return Json(new { vd = false, pv = this.RenderPartialViewToString("_Form", customer) });
                }
                return Json(new { vd = true, pv = this.RenderPartialViewToString("_Success", customer) });
            }
        }
    }
     

    □ 3.2 需要一个扩展方法,能把部分视图、model、以及错误信息转换成字符串

    展开

    □ 3.3 Index.cshtml视图

    这一次,不再需要部分视图,所有的提交和返回数据都发生在一个视图页面上。

    注意: 
    不要直接给提交按钮注册click事件,$('#btn').on("click", function(event),这样会对动态生成的内容无效。而应该这样写:$('#FormContainer').on("click","#btn", function(event)

    展开

    □ 3.4 效果

    没有输入任何信息,提交,报错:

    3.1

    输入错误用户名,提交:

    3.2


    发现,当第二次输入错误信息,提交,竟然跳转到了Home/ValidCustomer,而且返回的是json字符串。为什么?

    当第一输入错误信息,提交到控制器方法,当验证失败,会执行return Json(new { vd = false, pv = this.RenderPartialViewToString("_Form", customer) });返回的是_Form.cshtml部分视图,虽然第一次验证失败,没有跳转,但实际上,Index.cshtml的<div id="FormContainer">中已经有了_Form.cshtml部分视图视图内容:

    展开



    审查第一次提交后的html元素,如图:

    3.3

    解决方法:
    让控制器返回部分视图字符串的时候,不要再返回_Form.cshtml部分视图字符串。
    可以自定义针对Customer的一个视图,该视图中不仅有input,提交按钮,而且还有错误信息。

    关于自定义MVC视图引擎、视图,请参考:
    自定义MVC视图引擎ViewEngine 创建Model的专属视图     

    ■ 3.4.1 实现IView接口

    using jan.Models;
    using System.Web.Mvc;
    using System.Web.Mvc.Html;
     
    namespace jan.Extension
    {
        public class CustomerView : IView
        {
            
            public void Render(ViewContext viewContext, System.IO.TextWriter writer)
            {
                var allErrors = viewContext.ViewData.ModelState["UserName"].Errors;
                string msg = string.Empty;
                foreach (ModelError error in allErrors)
                {
                    msg = msg + error.ErrorMessage + " ";
                }
                //自定义输出视图的html格式
                writer.Write("<p style='color:red'>"+msg+"</p>");
                writer.Write("<p>用户名:<input type='text' id='UserName' /></p>");
                writer.Write("<p><input type='button' id='btn' value='提交' /></p>");
            }
        }
    }
     

    可以在ViewContext中根据某个属性拿到错误信息:viewContext.ViewData.ModelState["UserName"].Errors。

    ■ 3.4.2 实现IViewEngine接口

    让ViewEngine工作的时候,如果发现部分视图名是CustomerView,就返回自定义视图。

    using System;
    using System.Web.Mvc;
     
    namespace jan.Extension
    {
        public class CustomerViewEngine : IViewEngine
        {
            public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
            {
                if (partialViewName == "CustomerView")
                {
                    return new ViewEngineResult(new CustomerView(), this);
                }
                else
                {
                    return new ViewEngineResult(new String[]{"针对Customer的视图还没创建!"});
                }
            }
     
            public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
            {
                if (viewName == "CustomerView")
                {
                    return new ViewEngineResult(new CustomerView(), this);
                }
                else
                {
                    return new ViewEngineResult(new String[] { "针对Customer的视图还没创建!" });
                }
            }
     
            public void ReleaseView(ControllerContext controllerContext, IView view)
            {
            }
        }
    }
     

    ■ 3.4.3 Controller的静态扩展方法

    关键代码:ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
    这时候,ViewEngines一旦找到部分视图名称是CustomerView,就会返回自定义视图的ViewEngineResult,最终写进流,返回自定义视图字符串。

    展开

    ■ 3.4.4 =HomeController中,验证失败,返回自定义部分视图

    using System.Web.Mvc;
    using jan.Extension;
    using jan.Models;
    using System.Threading;
     
    namespace jan.Controllers
    {
        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                return View(new Customer());
            }
     
            [HttpPost]
            public ActionResult ValidCustomer(Customer customer)
            {
                Thread.Sleep(2000);
                if (!ModelState.IsValid)
                {
                    //return Json(new { vd = false, pv = this.RenderPartialViewToString("_Form", customer) });
                    return Json(new { vd = false, pv = this.RenderPartialViewToString("CustomerView", customer) });
                }
                return Json(new { vd = true, pv = this.RenderPartialViewToString("_Success", customer) });
            }
        }
    }
     

    当第二次提交错误信息时,不会跳转:
    3.4

      总结

    当涉及到表单异步提交的:
    1、优先考虑使用MVC自带的Ajax.BeginForm()方法,较快。
    2、其次考虑"jQuery+Html.BeginForm()方式",较慢,因为需要等待Html.BeginForm()提交。

    当涉及不到表单,只涉及部分属性异步提交的:
    1、优先考虑“MVC验证08-jQuery异步验证”:通过jquery,返回字符串,并把错误信息精确显示到指定html元素。
    2、其次考虑本篇的"通过jquery,返回json字符串,json字符串中包含部分视图及验证信息"。

  • 相关阅读:
    NOI2015 品酒大会
    BJOI2017 喷式水战改
    代码注释
    mysql zip 安装 和 修改密码
    Jrebel 永久免费激活步骤
    layui 在springboot2.x 时,页面展示不了layui的问题
    最小生成树
    loj 10117 简单题(cqoi 2006)
    vijos 1512 SuperBrother打鼹鼠
    vijos 清点人数
  • 原文地址:https://www.cnblogs.com/darrenji/p/3585644.html
Copyright © 2020-2023  润新知