MVC验证10-到底用哪种方式实现客户端服务端双重异步验证
原文: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 效果
提交之前:
没有填写任何内容,提交报错:
没有输入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="" height="" 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())
{
//根据控制器上下文和部分视图名称得到ViewEngineResult
var 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); //把流中的内容转成string
return 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="" height="" 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())
{
//根据控制器上下文和部分视图名称得到ViewEngineResult
var 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); //把流中的内容转成string
return 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-到底用哪种方式实现客户端服务端双重异步验证的更多相关文章
- spring mvc获取路径参数的几种方式 - 浅夏的个人空间 - 开源中国社区
body { font-family: "Microsoft YaHei UI","Microsoft YaHei",SimSun,"Segoe UI ...
- Spring MVC中forward请求转发2种方式(带参数)
Spring MVC中forward请求转发2种方式(带参数) http://www.51gjie.com/javaweb/956.html
- 登录验证全局控制的几种方式(session)
在登陆验证或者其他需要用到session全局变量的时候,归结起来,主要有以下三种较方便的实现方式.(其中个人较喜欢使用第一种实现方法) 一,在一个公共类里创建一个公共方法,然后需要验证的页面都调用这个 ...
- ASP.NET MVC之下拉框绑定四种方式(十)
前言 上两节我们讲了文件上传的问题,关于这个上传的问题还未结束,我也在花时间做做分割大文件处理以及显示进度的问题,到时完成的话再发表,为了不耽误学习MVC其他内容的计划,我们今天开始好好讲讲关于MVC ...
- asp.net mvc表单提交的几种方式
asp.net MVC中form提交和控制器接受form提交过来的数据 MVC中form提交和在控制器中怎样接受 1.cshtml页面form提交2.控制器处理表单提交数据4种方式方法1:使用传统的R ...
- java之spring mvc之Controller配置的几种方式
这篇主要讲解 controller配置的几种方式. 1. URL对应 Bean 如果要使用此类配置方式,需要在XML中做如下样式配置 <!-- 配置handlerMapping --> & ...
- C# MVC提交表单的四种方式(转)
Mvc 提交表单的4种方法全程详解(转) 一,MVC HtmlHelper方法 Html.BeginForm(actionName,controllerName,method,htmlAttribu ...
- spring mvc 返回json数据的四种方式
一.返回ModelAndView,其中包含map集 /* * 返回ModelAndView类型的结果 * 检查用户名的合法性,如果用户已经存在,返回false,否则返回true(返回json数据,格式 ...
- ElasticSearch搜索数据到底有几种方式?
Elasticsearch允许三种方式执行搜索请求: GET请求正文: curl -XGET "http://localhost:9200/app/users/_search" - ...
随机推荐
- javascript事件和事件处理
于js期间事件处理被分成三个步骤: 1.发生事件 2.启动事件处理程序 3.事件处理程序做出反应 事件处理程序的调用 1.在javascript中 在javascript中调用事件处理程序,首先要获得 ...
- VOJ 1067 Warcraft III 守望者的烦恼 (矩阵高速功率+dp)
主题链接 明显的 dp[n] = dp[n-k] + dp[n-k+1] + ... +dp[n-1]; 然后要用矩阵来优化后面的状态转移. 也就是矩阵 0 1 0 0 a b 0 0 ...
- CSDN个人空间能再烂吗?
CSDN空间你敢再烂么? 从CSDN博客跳转到CSDN个人空间的入口还算明显,可是想从个人空间跳转到博客,可真是众里寻他千百度.跳转接口怎么寻都寻不到.根本没有这个跳转的入口.唯一的途径仅仅能从写博文 ...
- SVN目录对号图标(更新、冲突)不显示
长谈想知道,大约SVN这些冲突.变化.加入.不显示问题etc目录下的复选图标,退房的在线信息,多数说的更改icon的settings,后来,一点点仔细阅读SVN配有英文说明文档,我相信,改变是有点问题 ...
- how tomcat works 札记(两)----------一个简单的servlet集装箱
app1 (看着眼前这章建议读者,看how tomcat works 札记(一个)----------一个简单的webserver http://blog.csdn.net/dlf123321/art ...
- 记一次tomcat故障排查(转)
1~1024之间的端口号是保留端口,通常是为特定目的预留的.虽然你的问题不是由于保留端口引起的,但是仍然建议你不要随意使用保留端口作为自定义服务的端口,如果你能早早遵循这一规则压根就不会遇到这个问题. ...
- Codeforces Round #254 (Div. 2):A. DZY Loves Chessboard
A. DZY Loves Chessboard time limit per test 1 second memory limit per test 256 megabytes input stand ...
- 大约sql声明优化
最近做的mysql数据库优化,并sql声明优化指南.我写了一个小文件.这种互相鼓励有关! 数据库参数获得的性能优化升级都在一起只占数据库应用系统的性能改进40%左右.其余60%的系统性能提升所有来自相 ...
- ajaxfileupload.js插件结合一般处理文件实现Ajax无刷新上传
先上几张图更直观展示一下要实现的功能.本功能主要通过Jquery ajaxfileupload.js插件结合ajaxUpFile.ashx一般应用程序处理文件实现Ajax无刷新上传功能,结合NPOI2 ...
- 离robots.txt启动网络爬虫之旅
要成为一个网络爬虫或搜索引擎(在这里,共同蜘蛛)它不会陌生,在搜索引擎爬虫的第一个文件或者访问该网站上浏览robots.txt该.robots.txt文件讲述了蜘蛛server哪些文件要观看正在. 当 ...