一. 模型绑定

  ASP.NET Core MVC 中的模型绑定,是将 HTTP 请求中的数据映射到action方法参数。 这些参数可能是简单类型的参数,如字符串、整数或浮点数,也可能是复杂类型的参数。 当 MVC 收到 HTTP 请求时,它会将此请求路由定位到控制器的指定action方法。默认路由模板为 {controller=Home}/{action=Index}/{id?}

    //例如:请求URL
http://contoso.com/movies/edit/2 //映射到movies/edit/2
public IActionResult Edit(int? id)

  上面Url请求对应movies控制器下的Edit方法,该方法接受名为 id 的可选参数。MVC会将Edit中的id参数绑定到路由值中具有相同名称的值。 URL 路由中的字符串不区分大小写。

  上面示例绑定的参数是简单类型,如果参数是一个类,例如Movie类型,该类包含简单和复杂类型作为属性,MVC的模型绑定仍然可以很好地处理它。它使用反射和递归来遍历寻找匹配的复杂类型的属性(如:Collection、Dictionary)。

  如果模型绑定失败,MVC 不会引发错误,参数值会是null。 如果HTTP 请求中的数据是用户输入的值,在action中应使用 ModelState.IsValid 属性检查,不需要手动去检查。

  注意:若要实现模型绑定,该类必须具有要绑定的公共默认构造函数和公共可写属性。 发生模型绑定时,在使用公共默认构造函数对类进行实例化后才可设置属性。

  模型绑定完成后,将发生模型验证。 对于绝大多数开发方案,默认模型绑定效果极佳。还可以扩展,如果有特殊需求,则可自定义内置行为包括:模型绑定特性、全局自定义模型绑定和验证、绑定请求正文中的带格式数据(JSON、XML 和许多其他格式)、还有高级篇中自定义模型绑定。这里不在说明,请查看文档。

二.模型验证

  在将数据存储到数据库之前,应用程序必须先验证数据。在 MVC 中,验证发生在客户端和服务器上。 模型验证使用自带的 System.ComponentModel.DataAnnotations类库来实现。 (不在本章范围:对于在类库中进行验证的实体可以通过开源插件FluentValidation 来实现 https://github.com/JeremySkinner/FluentValidation/wiki)。

  

  2.1 验证属性

    验证属性是模型验证的一种方法, 概念上类似于对数据库表中字段的验证, 验证属性在属性级别指定,下面是一个示例:

public class Movie
{
public int Id { get; set; } [Required]
[StringLength()]
public string Title { get; set; } [ClassicMovie()]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; } [Required]
[StringLength()]
public string Description { get; set; } [Range(, 999.99)]
public decimal Price { get; set; } [Required]
public Genre Genre { get; set; } public bool Preorder { get; set; }
}

  常用的内置验证属性包括:[CreditCard] 信用卡格式、 [Compare]匹配两个属性、[EmailAddress] 邮件格式、[Phone] 电话格式、[Range] 给定范围内、[RegularExpression] 正则表达式、[Required]必须属性值、[StringLength] 最大长度、[Url] URL格式,还可以包括自定义验证属性(例如ClassicMovie)。所有的内置验证属性参考官网

  

  2.2 自定义验证

    上面的验证属性适用于大多数验证需求。 但是,某些验证规则特定于你的业务。在 MVC 中创建自定义验证属性很简单。只需从 ValidationAttribute 继承并重写 IsValid方法。 IsValid 方法采用两个参数,第一个是名为 value 的对象,第二个是名为 validationContext 的 ValidationContext 对象。 Value 引用自定义验证程序要验证的字段中的实际值。

    /// <summary>
/// 自定义验证
/// </summary>
public class ClassicMovieAttribute : ValidationAttribute
{
private int _year; /// <summary>
/// 验证规则值
/// </summary>
/// <param name="year"></param>
public ClassicMovieAttribute(int year)
{
_year = year;
} protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Movie movie = (Movie)validationContext.ObjectInstance; //用户不能将 1960 年以后发行的电影的流派设置为 Classic
if (movie.Genre == "Classic" && movie.ReleaseDate.Year > _year)
{
return new ValidationResult(GetErrorMessage());
} return ValidationResult.Success;
} private string GetErrorMessage()
{
return $"Classic movies must have a release year earlier than {_year}.";
}
}

    运行程序,ReleaseDate是1989年,Genre是Classic,点击Save,验证是在服务端进行,显示错误消息,没有经过前端js验证,如下所示:

  

   2.3 客户端js验证介绍

    jQuery 非介入式验证脚本是一个自定义微软前端库,建立在流行的 jQuery Validate 插件。客户端验证原理是: MVC 的标记帮助程序和 HTML 帮助程序则能够使用模型属性中的验证特性和类型元数据,呈现需要验证的表单元素中的 HTML 5 data- 属性。MVC 为内置模型属性和自定义模型属性生成 data- 属性。然后,jQuery 非介入式验证分析 data- 属性并将逻辑传递给 jQuery Validate,从而将服务器端验证逻辑有效地“复制”到客户端。 可以使用相关标记帮助程序在客户端上显示验证错误。

    下面示例表单中,asp- 标记帮助程序代码如下:

       <div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>

    标记帮助程序将生成以下source html。请注意,HTML 输出中的 data-属性与 ReleaseDate 属性的验证特性相对应。下面的 data-val-required 属性包含在用户未填写发行日期字段时将显示的错误消息。jQuery 非介入式验证将此值传递给 jQuery Validate required() 方法,该方法随后在随附的 <span> 元素中显示该错误消息。

<form action="/Movies/Create" method="post">
<div class="form-horizontal">
<h4>Movie</h4>
<div class="text-danger"></div>
<div class="form-group">
<label class="col-md-2 control-label" for="ReleaseDate">ReleaseDate</label>
<div class="col-md-10">
<input class="form-control" type="datetime"
data-val="true" data-val-required="The ReleaseDate field is required."
id="ReleaseDate" name="ReleaseDate" value="" />
<span class="text-danger field-validation-valid"
data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
</div>
</div>
</div>
</form>

  

  2.4 动态表单添加验证

    在创建动态表单后,需要立即对其进行分析。 例如,下面的代码展示如何对通过 AJAX 添加的表单设置客户端验证。

$.get({
url: "https://url/that/returns/a/form",
dataType: "html",
error: function(jqXHR, textStatus, errorThrown) {
alert(textStatus + ": Couldn't add form. " + errorThrown);
},
success: function(newFormHTML) {
//添加表单newFormHTML
var container = document.getElementById("form-container");
container.insertAdjacentHTML("beforeend", newFormHTML);
//验证第一个表单
var forms = container.getElementsByTagName("form");
var newForm = forms[forms.length - 1];
//分析表单的 data- 属性
$.validator.unobtrusive.parse(newForm);
}
})

    $.validator.unobtrusive.parse() 方法分析该选择器内表单的 data- 属性。当用户填写表单中的属性值提交时, 这些属性的值传递到 jQuery Validate 插件中,以便表单展示所需的客户端验证规则。

    下面用一个简单示例来说明:

    (1)  创建dynamic-form-validate.js文件,模拟动态生成表单,以及点击(#idbtn)按钮时验证:

var newFormHTML = "<form action=\"create\" method=\"post\">";
newFormHTML += "<div class=\"form-group\">";
newFormHTML += "<label asp-for=\"Title\" class=\"control- label\"></label>";
newFormHTML += "<input type=\"text\" data-val=\"true\" data-val-required=\"The Title field is required.\" id = \"Title\" name= \"Title\">";
newFormHTML += "<span class=\"text- danger field- validation - valid\" data-valmsg-for=\"Title\" data-valmsg-replace=\"true\"></span>";
newFormHTML += "</div>";
newFormHTML += "<div class=\"form-group\" >";
newFormHTML += "<input type=\"submit\" value=\"Save\" class=\"btn btn-primary\" />";
newFormHTML += "</div >";
newFormHTML += "</form>"; $("#idbtn").click(function () {
var container = document.getElementById("form-container");
container.insertAdjacentHTML("beforeend", newFormHTML); var forms = container.getElementsByTagName("form");
var newForm = forms[forms.length - 1];
//分析表单的 data- 属性
$.validator.unobtrusive.parse(newForm);
});

    (2) 新建create页

@model StudyMVCDemo.Models.Movie
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1> <div class="row">
<input value="动态加载表单" type="button" id="idbtn" />
<div id="form-container" class="col-md-4"> </div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script src="~/js/dynamic-form-validate.js"></script>
}

    运行程序,点击"动态加载表单" 调用js将html表单添加到form-container元素容器中,点击save提示该字段不能为空,效果如下所示:

  

  2.5 动态控件添加验证

    当向表单动态添加控件(比如: <input/> 和 <select/>)时,需要更新表单上的验证规则。做法是应当先删除现有的验证数据,然后重新分析整个表单,如下js代码所示:

$.get({
url: "https://url/that/returns/a/control",
dataType: "html",
error: function(jqXHR, textStatus, errorThrown) {
alert(textStatus + ": Couldn't add control. " + errorThrown);
},
success: function(newInputHTML) {
//向表单动态添加Input控件
var form = document.getElementById("my-form");
form.insertAdjacentHTML("beforeend", newInputHTML);
//移除现有的验证
$(form).removeData("validator") // Added by jQuery Validate
.removeData("unobtrusiveValidation"); // Added by jQuery Unobtrusive Validation
//重新分析整个表单
$.validator.unobtrusive.parse(form);
}
})

  2.6  IClientModelValidator

    在上面2.2自定义验证中,继承了ValidationAttribute进行服务端验证,还可以结合实现IClientModelValidator接口实现客户端验证,该接口用来控制要添加哪些 data- 属性。实现接口如下所示:

    /// <summary>
/// 自定义验证
/// </summary>
public class ClassicMovieAttribute : ValidationAttribute,IClientModelValidator
{
private int _year; /// <summary>
/// 年份参考值
/// </summary>
/// <param name="year"></param>
public ClassicMovieAttribute(int year)
{
_year = year;
} protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Movie movie = (Movie)validationContext.ObjectInstance; //用户不能将 1960 年以后发行的电影的流派设置为 Classic
if (movie.Genre == "Classic" && movie.ReleaseDate.Year > _year)
{
return new ValidationResult(GetErrorMessage());
} return ValidationResult.Success;
} private string GetErrorMessage()
{
return $"Classic movies must have a release year earlier than {_year}.";
} public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
} MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage()); var year = _year.ToString(CultureInfo.InvariantCulture);
MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
} private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
} attributes.Add(key, value);
return true;
}
}

    生成的源html代码如下所示:

<input class="form-control" type="date"
data-val="true"
data-val-classicmovie="Classic movies must have a release year earlier than 1960."
data-val-classicmovie-year="1960"
data-val-required="The ReleaseDate field is required."
id="ReleaseDate" name="ReleaseDate" value="1989-02-12">

    在上面虽然实现了IClientModelValidator接口,但jQuery不了解规则或消息,还需要自定义classicmovie客户端验证方法,添加到jQuery validator对象。脚本如下所示:

//添加验证方法
$.validator.addMethod('classicmovie',function (value, element, params) {
//value ,是当前验证的元素的值。
//element 元素本身。
//params 是传入的参数(options.rules)
var genre = $("#form1").find("#Genre").val(),
year = params[0],
date = new Date(value);
if (genre.length > 0 && genre === 'Classic') {
// Since this is a classic movie, invalid if release date is after given year.
return date.getFullYear() <= year;
}
return true;
}); //注册一个适配器,参数1是适配器名称,参数2是验证规则的名称
$.validator.unobtrusive.adapters.add('classicmovie',['year'],function (options) {
//适配器规则绑定到jquery validation上面
options.rules['classicmovie'] = [parseInt(options.params['year'])];
options.messages['classicmovie'] = options.message;
});

    运行程序,ReleaseDate是1989年,Genre是Classic,点击Save,客户端验证返回false,提示错误信息,如下所示:

    

  参考文献

    模型绑定

    模型验证

  

asp.net core系列 45 Web应用 模型绑定和验证的更多相关文章

  1. Asp.Net Core 入门(十)—— 模型绑定和验证

    模型绑定时将Http请求中的数据映射到控制器操作方法上对应的参数,操作方法中的参数可以是简单类型,如整形,字符串等,也可以是复杂类型,如Product,Order等. Asp.Net Core MVC ...

  2. asp.net core系列 40 Web 应用MVC 介绍与详细示例

    一. MVC介绍 MVC架构模式有助于实现关注点分离.视图和控制器均依赖于模型. 但是,模型既不依赖于视图,也不依赖于控制器. 这是分离的一个关键优势. 这种分离允许模型独立于可视化展示进行构建和测试 ...

  3. asp.net core系列 39 Web 应用Razor 介绍与详细示例

    一. Razor介绍 在使用ASP.NET Core Web开发时, ASP.NET Core MVC 提供了一个新特性Razor. 这样开发Web包括了MVC框架和Razor框架.对于Razor来说 ...

  4. asp.net core 系列 18 web服务器实现

    一. ASP.NET Core Module 在介绍ASP.NET Core Web实现之前,先来了解下ASP.NET Core Module.该模块是插入 IIS 管道的本机 IIS 模块(本机是指 ...

  5. asp.net core 系列 16 Web主机 IWebHostBuilder

    一.概述 在asp.net core中,Host主机负责应用程序启动和生存期管理.host主机包括Web 主机(IWebHostBuilder)和通用主机(IHostBuilder).Web 主机是适 ...

  6. asp.net core系列 41 Web 应用 MVC视图

    一.MVC视图 在Web开发的MVC和Razor中,都有使用视图,在Razor中称为"页"..cshtml视图是嵌入了Razor标记的HTML模板. Razor 标记使用C#代码, ...

  7. asp.net core系列 48 Identity 身份模型自定义

    一.概述 ASP.NET Core Identity提供了一个框架,用于管理和存储在 ASP.NET Core 应用中的用户帐户. Identity添加到项目时单个用户帐户选择作为身份验证机制. 默认 ...

  8. asp.net core系列 44 Web应用 布局

    一.概述 MVC的视图与Razor页面经常共享视觉和程序元素,通过使用布局来完成,布局还可减少重复代码.本章演示了以下内容的操作方法:(1)使用通用布局,(2)自定义布局,(3) 共享指令,(4)在呈 ...

  9. asp.net core系列 43 Web应用 Session分布式存储(in memory与Redis)

    一.概述 HTTP 是无状态的协议. 默认情况下,HTTP 请求是不保留用户值或应用状态的独立消息. 本文介绍了几种保留请求间用户数据和应用状态的方法.下面以表格形式列出这些存储方式,本篇专讲Sess ...

随机推荐

  1. 算法库中heap应用

    STL中make_heap 的接口为: default (1) template <class RandomAccessIterator> void make_heap (RandomAc ...

  2. XGBoost算法--学习笔记

    学习背景 最近想要学习和实现一下XGBoost算法,原因是最近对项目有些想法,准备做个回归预测.作为当下比较火的回归预测算法,准备直接套用试试效果. 一.基础知识 (1)泰勒公式 泰勒公式是一个用函数 ...

  3. Beta 冲刺day3

    1.昨天的困难,今天解决的进度,以及明天要做的事情 昨天的困难:昨天主要是对第三方与企业复杂的逻辑关系进行分析和优化,以及进行部分模块的功能测试和代码测试. 今天解决的进度:根据前天得到的需求问题进行 ...

  4. CSS弹性盒子的基本用法

    .container { width: 302px; height: 302px; border: 1px solid; display: flex } .item { width: 100px; h ...

  5. 登录测试用例sql语句注入

    利用SQL注入漏洞登录后台的实现方法 作者: 字体:[增加 减小] 类型:转载 时间:2012-01-12我要评论 工作需要,得好好补习下关于WEB安全方面的相关知识,故撰此文,权当总结,别无它意.读 ...

  6. 高通spi 屏幕 -lk代码分析

    lk SPI驱动 1. 初始化时钟 在lk中,我们是从kmain开始执行下来的,而执行顺序则是先初始化时钟,也就是在platform_early_init函数中开始执行的: 在这里我们需要修改这个函数 ...

  7. 管理和安装 chart - 每天5分钟玩转 Docker 容器技术(168)

    安装 chart 当我们觉得准备就绪,就可以安装 chart,Helm 支持四种安装方法: 安装仓库中的 chart,例如:helm install stable/nginx 通过 tar 包安装,例 ...

  8. 原生aspx页面如何引用公共js和css

    项目过程中遇到一个问题,每个页面需要引用很多的js和css文件,其中很多都是控件,而且大部分都是一样的,造成很多重复引用. 针对这种情况,参考了mvc的BundleConfig,思路是建立一个公用的用 ...

  9. java并发之ReentrantLock学习理解

    简介 java多线程中可以使用synchronized关键字来实现线程间同步互斥,但在jdk1.5中新增加了ReentrantLock类也能实现同样的效果,并且在扩展功能上也更加强大,比如具有嗅探锁定 ...

  10. eclipse中javadoc给项目生成api文档

    步骤 1.打开java代码,编写JavaDoc 注释,只有按照java的规范编写注释,才能很好的生成API文档,javadoc注释与普通注释的区别为多一个*(星号).普通代码注释为/*XXX*/,而j ...