本篇将通过一个案例来体验使用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 效果

提交之前:

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

没有输入darren,提交报错:

输入正确,提交成功:

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

□ 2.1 Index.cshtml视图

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

展开@model jan.Models.Customer@{ViewBag.Title = "Index";Layout = "~/Views/Shared/_Layout.cshtml";
}@DateTime.Now:  Index.cshtml视图被渲染
<hr/><div id="ProgressDialog" style="text-align: center; padding: 50px;"><img src="@Url.Content("~/Content/ajax-loader.gif")" width="128" height="15" alt="Loading" />
</div><div id="FormContainer">@Html.Partial("_Form")
</div>@section scripts
{<script type="text/javascript">$(function() {$("#ProgressDialog").dialog({autoOpen: false,draggable: false,modal: true,resizable: false,title: "加载中......",closeOnEscape: false,open: function () { $(".ui-dialog-titlebar-close").hide(); } //隐藏关闭按钮});$("form").on("submit", function (event) {event.preventDefault();var form = $(this);$("#ProgressDialog").dialog("open");$.ajax({url: form.attr('action'),type: "POST",data: form.serialize(),success: function (data) {$("#FormContainer").html(data);$.validator.unobtrusive.parse("form");},error: function (jqXhr, textStatus, errorThrown) {alert("Error '" + jqXhr.status + "' (textStatus: '" + textStatus + "', errorThrown: '" + errorThrown + "')");},complete: function () {$("#ProgressDialog").dialog("close");}});});});</script>
}

□ 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 效果

提交之前:

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

没有输入darren,提交报错:

虽然报错,但注意到存在一个问题:地址变成了/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如下:

展开@section scripts
{<script type="text/javascript">$(function() {$("#ProgressDialog").dialog({autoOpen: false,draggable: false,modal: true,resizable: false,title: "加载中......",closeOnEscape: false,open: function () { $(".ui-dialog-titlebar-close").hide(); } //隐藏关闭按钮});$('#FormContainer').on("submit","form", function(event){event.preventDefault();var form = $(this);$("#ProgressDialog").dialog("open");$.ajax({url: form.attr('action'),type: "POST",data: form.serialize(),success: function (data) {$("#FormContainer").html(data);$.validator.unobtrusive.parse("form");},error: function (jqXhr, textStatus, errorThrown) {alert("Error '" + jqXhr.status + "' (textStatus: '" + textStatus + "', errorThrown: '" + errorThrown + "')");},complete: function () {$("#ProgressDialog").dialog("close");}});});});</script>
}

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

输入正确,提交成功:

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、以及错误信息转换成字符串

展开using System.IO;
using System.Web.Mvc;namespace jan.Extension
{public static class ControllerExtension{/// <summary>/// 把部分视图转换成string/// </summary>/// <param name="controller">当前控制器</param>/// <param name="viewName">当前部分视图名称</param>/// <returns>返回字符串</returns>public static string RenderPartialViewToString(this Controller controller, string viewName){return controller.RenderPartialViewToString(viewName, null);}/// <summary>/// 把部分视图转换成string/// </summary>/// <param name="controller">当前控制器</param>/// <param name="viewName">当前部分视图</param>/// <param name="model">Model</param>/// <returns>返回字符串</returns>public static string RenderPartialViewToString(this Controller controller, string viewName, object model){if (string.IsNullOrEmpty(viewName))//如果部分视图名称没有viewName = controller.ControllerContext.RouteData.GetRequiredString("action");//action名称就是部分视图名称controller.ViewData.Model = model;using (var sw = new StringWriter()){//根据控制器上下文和部分视图名称得到ViewEngineResultvar viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);//根据控制器的上下文+ViewData+ViewEngineResult中的View构建ViewContext对象实例var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);//把ViewContext对象实例写到流中viewResult.View.Render(viewContext, sw);//把流中的内容转成stringreturn sw.GetStringBuilder().ToString();}}}
}

□ 3.3 Index.cshtml视图

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

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

展开@model jan.Models.Customer@{ViewBag.Title = "Index";Layout = "~/Views/Shared/_Layout.cshtml";
}@DateTime.Now:  Index.cshtml视图被渲染
<hr/><div id="ProgressDialog" style="text-align: center; padding: 50px;"><img src="@Url.Content("~/Content/ajax-loader.gif")" width="128" height="15" alt="Loading" /></div><div id="SuccessDialog" style="text-align: center; padding: 50px;"><div id="SuccessContainer"></div></div><div id="FormContainer"><p style="color:red;">@Html.ValidationMessageFor(m => m.UserName)</p>   <p>@Html.LabelFor(m => m.UserName):@Html.EditorFor(m => m.UserName)</p><input type="button" id="btn" value="提交"/>
</div>@section scripts
{<script type="text/javascript">$(function() {$("#ProgressDialog").dialog({autoOpen: false,draggable: false,modal: true,resizable: false,title: "加载中......",closeOnEscape: false,open: function () { $(".ui-dialog-titlebar-close").hide(); } //隐藏关闭按钮});$('#FormContainer').on("click","#btn", function(event){event.preventDefault();var form = $(this);$("#ProgressDialog").dialog("open");var msg = { UserName: $.trim($('#UserName').val()) };$.ajax({url: '@Url.Action("ValidCustomer", "Home")',type: "POST",data: msg,success: function (data) {if (data.vd) {//与后台返回的json字符串对应$("#SuccessContainer").html(data.pv);$("#SuccessDialog").dialog("open");} else {$("#FormContainer").html(data.pv);$.validator.unobtrusive.parse("form");//这个可忽略,不用表单}},error: function (jqXhr, textStatus, errorThrown) {alert("Error '" + jqXhr.status + "' (textStatus: '" + textStatus + "', errorThrown: '" + errorThrown + "')");},complete: function () {$("#ProgressDialog").dialog("close");}});});});</script>
}

□ 3.4 效果

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

输入错误用户名,提交:

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

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

展开@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="提交" />
}

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

解决方法:
让控制器返回部分视图字符串的时候,不要再返回_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,最终写进流,返回自定义视图字符串。

展开using System.IO;
using System.Web.Mvc;namespace jan.Extension
{public static class ControllerExtension{/// <summary>/// 把部分视图转换成string/// </summary>/// <param name="controller">当前控制器</param>/// <param name="viewName">当前部分视图名称</param>/// <returns>返回字符串</returns>public static string RenderPartialViewToString(this Controller controller, string viewName){return controller.RenderPartialViewToString(viewName, null);}/// <summary>/// 把部分视图转换成string/// </summary>/// <param name="controller">当前控制器</param>/// <param name="viewName">当前部分视图</param>/// <param name="model">Model</param>/// <returns>返回字符串</returns>public static string RenderPartialViewToString(this Controller controller, string viewName, object model){if (string.IsNullOrEmpty(viewName))//如果部分视图名称没有viewName = controller.ControllerContext.RouteData.GetRequiredString("action");//action名称就是部分视图名称controller.ViewData.Model = model;using (var sw = new StringWriter()){//根据控制器上下文和部分视图名称得到ViewEngineResultvar viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);//根据控制器的上下文+ViewData+ViewEngineResult中的View构建ViewContext对象实例var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);//把ViewContext对象实例写到流中viewResult.View.Render(viewContext, sw);//把流中的内容转成stringreturn sw.GetStringBuilder().ToString();}}}
}

■ 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) });
        }
    }
}
 

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

总结

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

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

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

  1. 创建线程池的四种方式_创建线程到底有几种方式?

    很多时候,在项目中使用线程的情况很少,导致很多人只停想起最常见的两种创建线程的方法,即继承Thread类和实现Runnable接口. 而网络上大家有人认为是三种实现方式,也有人认为是四种实现,下面我们 ...

  2. vue路由传参到底有几种方式

    看了很多篇博客,路由传参说有几种方式的都有,看得我都晕了,都不知道到底有几种了,因为很多博客分析的角度不同,所以把我搞得有点乱,最后还是通过实践来搞清楚了,其实本身就是那么几种形式,但方式其实就两种q ...

  3. spring mvc 返回json数据的四种方式

    一.返回ModelAndView,其中包含map集 /** 返回ModelAndView类型的结果* 检查用户名的合法性,如果用户已经存在,返回false,否则返回true(返回json数据,格式为{ ...

  4. java mvc controller_java之spring mvc之Controller配置的几种方式

    这篇主要讲解 controller配置的几种方式. 1. URL对应 Bean 如果要使用此类配置方式,需要在XML中做如下样式配置 2. 为 URL 分配 Bean 使用一个统一配置集合,对各个 U ...

  5. ASP.NET MVC教程四:ASP.NET MVC中页面传值的几种方式

    准备 在Models文件夹里面新添加Student实体类,用来模拟从Controller向View传递数据,Student类定义如下: using System; using System.Colle ...

  6. 绕过安卓SSL验证证书的常见四种方式

    在此之前,移动端应用程序会直接忽略掉所有的SSL错误,并允许攻击者拦截和修改自己的通信流量.但是现在,很多热门应用程序至少会检查证书链是否是一个有效可信任的证书机构(CA)颁发的.作为一名渗透测试人员 ...

  7. .net mvc core网站发布的几种方式

    简单的说下.netcore发布程序的几种方法:1.vs项目里自带的发布功能 . 2.dos下dotnet publish命令,发布的位置可以是exe形式,也可以部署在iis服务器上,发布的程序可以是依 ...

  8. 三种方式实现电脑端同时登陆多个微信

    现在越来越人都有两个甚至多个微信号,有时候我们需要登录在电脑回复消息,那么怎样才能在一个电脑上同时登录两个微信甚至是多个微信号(微信多开)呢?希望下列三种方法能对各位博友有帮助. 方法一:微信网页版实 ...

  9. 百度地图测加载的两种 方式 直接加载和异步加载

    1.直接加载 <html> <head><meta http-equiv="Content-Type" content="text/html ...

最新文章

  1. 特斯拉超级计算机Dojo
  2. android百度地图轨迹实现,android 获取GPS经纬度在百度地图上绘制轨迹
  3. android源码分析-Zygote
  4. QuickBI助你成为分析师-数据建模(一)
  5. https安全传输揭秘
  6. vue中 this.$set的用法
  7. Sublime Text for Mac的快捷键
  8. 想学好C语言?先把基础打好再说吧!
  9. Lync Server 2010迁移至Lync Server 2013部署系列 Part13:DNS记录变更
  10. Socket Programming
  11. 计算机windows安全如何打开,windows安全中心如何关闭
  12. Exce批量发送邮件功能:发件人设置的操作
  13. 计算机组成与原理名词解释,计算机组成原理名词解释与简答
  14. #108 – The Logical Tree(逻辑树)
  15. 用火狐浏览器看b站视频默认没有声音
  16. 移动短信回执怎么开通_发送什么指令到10086开通短信回执?
  17. pfam的使用-自用
  18. 百度云高速下载器 kinhdown
  19. 赛迪智库丨谁能成功抢位操作系统,谁就能掌握未来汽车产业发展的主动权
  20. 数据分析师兼职副业平台大量招聘

热门文章

  1. org.openqa.selenium.StaleElementReferenceException
  2. JQuery Datatable用法
  3. Office 365管理员指引 9 ——Lync 自定义会议邀请
  4. MyBatis一对多双向关联——MyBatis学习笔记之七
  5. (备忘)Java数据类型中String、Integer、int相互间的转换
  6. 关于(警告: No configuration found for the specified action)解决方案
  7. 天天Linux-Ctrl+S快捷键锁定屏幕的问题
  8. 3加2大专计算机专业考什么,3加2学校有什么专业 初中生怎么报考3+2
  9. JavaScript初学者编程题(3)
  10. java 创建servlet_javaweb02-创建第一个Servlet