ASP.NET MVC 框架验证每个传递给操作的数据是否有效,控制器操作可以通过查询ModelState来检查请求是否有效,例如,保存有效数据到数据库、后缀返回包含错误提示信息的原始表单给用户。
这里是AuctionsController.Create操作,用于判断ModelState的有效性后进行“保存或者返回” 操作:
[HttpPost] public ActionResult Create(Auction auction) { if(ModelState.IsValid) { var db = new EbuyDataContext(); db.Auction.Add(auction); db.SaveChanges(); return RedirecToAction("Index"); } return View(auction); }
假设我们的交易将持续至少一天时间,换句话说,交易的截止日期比当前的日期多一天。AuctionsController.Create操作可以在保存之前验证这个逻辑,当然在错误的时候给出提示信息:
[HttpPost] public ActionResult Create(Auction auction) { if(auction.EndTime <= DateTime.Now.AddDays(1)) { ModelState.AddModelError( "EndTime", "Auction must be at least one day long" ); } if(ModelState.IsValid) { var db = new EbuyDataContext(); db.Auction.Add(auction); db.SaveChanges(); return RedirecToAction("Index"); } return View(auction); }
这个方法确实有效,但是它没有分离应用程序的关注点,控制器不应该包含这种业务逻辑,该业务逻辑属于模型,所以要把业务逻辑移到模型里。
使用数据声明指定业务规则;
确保数据有效性(数据验证)是开发人员必须面对的问题。通常情况下,开发人员都是尽可能借助现有的框架来完成数据验证工作。
这个需求非常普遍,实际上,微软提供了非常高效、便捷的数据验证API,称为数据标注,属于.NET框架的核心部分;
ASP.NET MVC 模型绑定提供了数据标记支持,不需要任何额外的配置,为了学习ASP.NET MVC数据标记,我们来看Auction类的验证应用过程,为了使用验证逻辑,首先要考虑什么是期望的Auction属性值。哪些字段是必须的? 哪些字段必须在特定的范围内才算有效?
必填字段
因为auction 的Title 和Description对出售的商品至关重要,所以要给这两个属性标记RequiredAttribute,以便验证数据的有效性;
[Required] public string Title{get;set;} [Required] public string Description{get;set;}
除了标记字段为必须的(Required)外,还可以确保字符长度满足了最短长度的StringLengthAttribute属性标记的要求,例如,如果决定了auction标题的长度最多50个字符,就可以通过这个属性来强化控制:
[Required,StringLength(50)] public string Title{get;set;}
如果用户提交Title长度超过50个字符,ASP.NET MVC 模型验证就会失败。
验证范围:
接下来要考虑商品的起始价格:由StartPrice属性表示,使用decimal类型,因为decimal是一个值类型,StartPrice默认为0,所以就没必要标记为必填字段,但是交易的起始价格有个逻辑超过了必填字段的范围,因为这个逻辑值,永远不能为负值,负值意味着卖家一开始就是欠账的!为了处理这个问题,需要使用RangeAttribute属性来标记StartPrice字段,最小值是1.因为RangeAttribute需要一个最大值,所以可以指定一个最大值作为上限:
[Range(1,1000] public decimal StartPrice{get;set;}
这个例子的区间使用了double类型,RangeAttribute标记属性还有一个重载(Range(Type type,string min,string max))可以满足任何实现了IComparable的类型。最好的例子就是日期范围的验证,例如,确保日期晚于某个特定的时间点:
[Range(typeof(DateTime),"1/1/2015","12/31/9999")] public DateTime EndTime{get;set;}
这个例子确保EndTime属性的值晚于2015年1月1日。
备注:.NET标记属性参数必须是编译时的值,在运行时无法修改,而且不允许使用像DateTime.Now这样的值来设置范围,必须使用固定的日期,比如1/1/1/2015。
虽然无法保证以后的日期有效,但是至少可以保证目前的设置在有效的日期范围内。
自定义错误信息:
最后要重点提到的是,数据标记提供了ErrorMessage属性,可以指定返回给用户的错误信息,而不是由Data Annotations API生成的默认信息。现在把使用数据标记属性的模型都指定值。
下面的类应该包含了上面讨论的所有数据标记情况:
public class Auction { [Required] [StringLength(50,ErrorMessage="Title cannot be longer than 50 characters")] public string Title{get;set;} [Required] public string Description{get;set;} [Range(1,1000,ErrorMessage="The auction's starting price must be at least 1")] public decimal StartPrice{get;set;} public decimal CurrentPrice{get;set;} public DateTime EndTime{get;set;} }
既然模型里已经定义了所有的验证逻辑,那么就再看看控制器和视图是如何返回错误信息给用户的。
显示验证错误:
可以通过在Create操作里设置断点来查看验证规则,只要提交无效的值,然后观察ModelState属性来验证错误是什么就可以了,事实上,控制器返回Create视图,而不是保存新交易数据,这恰恰证明了验证规则的正确性以及验证框架的有效性。虽然“Create”视图显示了无效红边框,但还是没返回任何错误提示信息来明确告诉用户哪里错了,所以,我们需要注意这个问题。
这是显示Title属性的代码:
<p> @Html.LabelFor(model=>model.Title) @Html.EditorFor{model=>model.Title} @ViewData.ModelState["Title"] </p>
我们需要做的就是添加与Title相关的验证信息。最简单的方式就是查看ModelState----通过View Data.ModelState和ViewData.ModelState["Title"] 返回的与Title属性相关的错误提示信息。
我们可以迭代这个集合把错误信息渲染到页面上,代码如下:
<p> @Html.LabelFor(model=>model.Title) @Html.EditorFor{model=>model.Title} @foreach(var error in ViewData.ModelState["Title"].Errors) { <span class="error">@error.ErrorMessage</span> } </p>
虽然这个方法不错,但是ASP.NET MVC提供了更好的方法来渲染特定属性的错误提示信息:Html.ValidationMessage(string modelName)帮助方法。它可以让我们省去上面复杂的循环代码,只需要一行调用代码就可以达到同样的效果:
@Html.ValidationMessageFor(model=>model.Title)
为模型里的每个属性都添加调用Html.ValidationMessage()方法的代码。ASP.NET MVC会在相关的控件右边渲染出与验证有关的错误提示信息。
除了使用属性级别的Html.ValidationMessage()帮助方法外,ASP.NET MVC也提供了Html.ValidationSummary()。它可以帮助我们在一个地方渲染显示验证异常信息(例如,表单的顶部)。给用户一个摘要提示信息,以方便用户修正错误,正确提交表单。
这个帮助方法使用起来也相当简单,只需要在想调用它的地方加一行代码即可:
@using(Html.BeginForm()) { @Html.ValidationSummary() <p> @Html.LabelFor(model=>model.Title) @Html.EditorFor{model=>model.Title} @Html.ValidationMessageFor(model=>model.Title) </p> <!--表单的其余字段--> }
当用户提交无效的值时,我们可以在两个地方看到错误提示信息,即顶部的汇总信息(调用Html.ValidationSummary())和控件后边的错误提示信息(调用Html.ValidationMessage()),
如果想避免重复显示错误信息,则可以修改调用Html.ValidationMessage()的代码,指定更短的自定义错误提示信息,比如简单的星号*,如下:
<p> @Html.LabelFor(model=>model.Title) @Html.EditorFor{model=>model.Title} @Html.ValidationMessageFor(model=>model.Title,"*") </p>
下面是使用了验证标签后的Cteate视图文件的所有代码:
<h2>Create Aution</h2> @using(Html.BeginForm()) { @Html.ValidationSummary() <p> @Html.LabelFor(model=>model.Title) @Html.EditorFor{model=>model.Title} @Html.ValidationMessageFor(model=>model.Title,"*") </p> <p> @Html.LabelFor(model=>model.Description) @Html.EditorFor{model=>model.Description} @Html.ValidationMessageFor(model=>model.Description,"*") </p> <p> @Html.LabelFor(model=>model.StartPrice) @Html.EditorFor{model=>model.StartPrice} @Html.ValidationMessageFor(model=>model.StartPrice,"*") </p> <p> @Html.LabelFor(model=>model.EndTime) @Html.EditorFor{model=>model.EndTime} @Html.ValidationMessageFor(model=>model.EndTime,"*") </p> }
目前展示的验证都是在服务端代码验证,需要在服务器和客户端之间进行几回合的交互过程才能验证数据的有效性,并返回渲染过的视图效果。
下面演示下,我在代码中的实现:
构成表单:
@using (Html.BeginForm("requestVM", "VM", FormMethod.Post, new { id = "RequestForm" })) { @Html.AntiForgeryToken() <h1 class="resource_title creat_resource"> <input class="close" type="button" id="btnCancel" value=" " />新建资源</h1> <ul class="form_des vm form_des1"> <li><span>@Html.LabelFor(m => m.vmName)</span>@Html.EditorFor(model => model.vmName)@*@Html.ValidationMessageFor(m=>m.vmName)*@</li> <li><span>@Html.LabelFor(m => m.vmUserName)</span>@Html.EditorFor(model => model.vmUserName)@*@Html.ValidationMessageFor(m=>m.vmUserName)*@</li> <li><span>@Html.LabelFor(m => m.vmUserPassword)</span>@Html.PasswordFor(model => model.vmUserPassword)@*@Html.ValidationMessageFor(m=>m.vmUserPassword)*@</li> <li><span>@Html.LabelFor(m => m.ConfirmPassword)</span>@Html.PasswordFor(model => model.ConfirmPassword)@*@Html.ValidationMessageFor(m=>m.ConfirmPassword)*@</li> <li><span>@Html.LabelFor(m => m.vmCloudID)</span>@Html.DropDownListFor(model => model.vmCloudID, ViewBag.CloudList as List<SelectListItem>)@*@Html.ValidationMessageFor(m=>m.vmTemplateID)*@</li> <li><span>@Html.LabelFor(m => m.vmSubNetID)</span><div id="divAzureSubNet"></div>@*@Html.ValidationMessageFor(m=>m.vmTemplateID)*@</li> <li><span>@Html.LabelFor(m => m.vmTemplateID)</span>@Html.DropDownListFor(model => model.vmTemplateID, ViewBag.TempList as List<SelectListItem>)@*@Html.ValidationMessageFor(m=>m.vmTemplateID)*@</li> <li><span>@Html.LabelFor(m => m.vmRoleSizeID)</span>@Html.DropDownListFor(model => model.vmRoleSizeID, ViewBag.RoleSizeList as List<SelectListItem>)@*@Html.ValidationMessageFor(m=>m.vmRoleSizeID)*@</li>
<!--后面省略-->
显示错误信息:
<div class="creat_error">@Html.ValidationMessageFor(m => m.vmName)@Html.ValidationMessageFor(m => m.vmUserName)@Html.ValidationMessageFor(m => m.vmUserPassword)@Html.ValidationMessageFor(m => m.ConfirmPassword)@Html.ValidationMessageFor(m => m.vmTemplateID)@Html.ValidationMessageFor(m=>m.vmCloudID)@Html.ValidationMessageFor(m => m.vmRoleSizeID)@Html.ValidationMessage("Operation")</div>
模型Model类的构建:
ublic class VirtualMachineCreateModel { [Required(ErrorMessage="请输入资源名称")] [RegularExpression(@"^(?!-)(?!.*?-$)[a-zA-Z][a-zA-Z0-9.-]{2,14}$", ErrorMessage = "资源名称由字母、数字和中划线组成,且以字母开头以字母或数字结尾的长度为3-15位")] [Display(Name = "资源名称")] public string vmName { get; set; } [Exclude(ExcludeStr = "a|administrator|user|root", ErrorMessage = "用户名不能为:a,administrator,user,root")] [Required(ErrorMessage="请输入用户名")] [RegularExpression(@"^[a-zA-Z][a-zA-Z0-9.-]{0,19}$", ErrorMessage = "用户名由字母、数字和中划线组成,且以字母开头的长度为1-20位")] [Display(Name="用户名")] public string vmUserName { get; set; } [Required(ErrorMessage="请输入密码")] [DataType(DataType.Password)] [RegularExpression(@"(?=^.{8,15}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$", ErrorMessage = "密码需同时包含大、小写字符,且至少包含一位数字或特殊字符。密码长度8-15位。")] [Display(Name="密码")] public string vmUserPassword { get; set; } [DataType(DataType.Password)] [Display(Name = "确认密码")] [Compare("vmUserPassword", ErrorMessage = "密码和确认密码不匹配。")] public string ConfirmPassword { get; set; } //此处省略 “很多abc”;;;;;; }
希望对你有帮助;