模型验证 ◼ ASP.NET Core的验证技术是在模型类中以声明方式(注解)指定验证规则,并且在应用中的所 有位置强制执行:
◼ 减少将无效数据保存到数据库的几率
◼ 提升应用的可靠性
常用验证说明
◼ [Required]
:验证字段是否不为 null
◼ [StringLength]
:验证字符串属性值是否不超过指定的长度限制。
◼ [Range]
:验证属性值是否位于指定范围内。
◼ [Compare]
:验证模型中的两个属性是否匹配。
◼ [RegularExpression]
:验证 属性值是否与指定的正则表达式匹配。
◼ [EmailAddress]
:验证属性是否具有电子邮件格式。
注:[DataType]
:只是帮助字段进行格式设置,不提供任何验证
示例:模型添加验证 – 后列注释部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Movie { public int Id { get ; set ; } [Required(ErrorMessage = "电影名称必填" ) ] [StringLength(20, MinimumLength = 3,ErrorMessage ="3-20个字符" ) ] [Display(Name = "电影名称" ) ] public string ? Title { get ; set ; } [Required(ErrorMessage = "上映日期必填" ) ] [Display(Name = "上映日期" ) ] [DataType(DataType.Date) ] public DateTime ReleaseDate { get ; set ; } [Required(ErrorMessage = "电影类型必填" ) ] [StringLength(10, MinimumLength = 2, ErrorMessage = "2-10个字符以内" ) ] [Display(Name = "电影类型" ) ] public string ? Genre { get ; set ; } [Required(ErrorMessage = "电影票价必填" ) ] [Range(1, 100,ErrorMessage ="1-100之间" ) ] [Display(Name = "票价" ) ] [DataType(DataType.Currency) ] public decimal Price { get ; set ; } }
验证测试运行:新建数据
强类型传值 ◼ 回顾:ViewData 字典传值是一个弱类型传值方式 (使用时需要手工强转类型)
◼ 强类型传值则不需要手工强转类型
◼ 如何实现强类型传值:
① 控制器在返回视图时,添加模型对象作为参数,即: return View(模型对象);
② 在视图中,先使用 @model 指令声明模型对象类型
③ 然后在视图中使用Model对象来接收传来的模型对象,之后使用Model对象无需强转
强类型传值方式1:单值情况
控制器
1 2 3 4 5 6 public IActionResult Test ( ){ Movie movie = _context.Movie.Find(1 ); return View(movie); }
新建Test视图
Test视图
1 2 3 4 @model MvcMovie.Models.Movie @* ② 先用"@model"指令声明模型类型 *@ <p>@Model.Title</p> <!-- ③使用Model对象来接收传来的模型对象Model无需强转就可使用,如读取对象属性值 --> ...
运行情况:
强类型传值方式2:多值 (集合) 情况
控制器
1 2 3 4 5 public IActionResult Test ( ){ List<Movie> movies = _context.Movie.ToList(); return View(movies); }
修改Test视图
Test视图
1 2 3 4 5 6 @model IEnumerable<MvcMovie.Models.Movie> ... @foreach ( var item in Model ) // ③使用Model对象来接收传来的集合对象,Model无需强转就可使用,如遍历集合 { ...处理 item }
◼ 注:IEnumerable<> 是一个集合类泛型接口,是 List<> 的父类
示例:使用表格显示集合数据
Test视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @model IEnumerable <MvcMovie.Models.Movie> <table class ="table table-striped" > <tr> <th>电影名称</th> <th>电影类型</th> <th>上映日期</th> <th>票价</th> <th colspan="2" >操作</th> </tr> @foreach (var m in Model) { <tr> <td>@m.Title</td> <td>@m.Genre</td> <td>@m.ReleaseDate</td> <td>@m.Price</td> <td><a href="Edit/@m.Id" >编辑</a></td> <td><a href="Delete/@m.Id" >删除</a></td> </tr> } </table>
运行情况:
LINQ查询 ◼ LINQ:是Language Integrated Query的简称(语言集成查询),是一系列直接将查询功能集成到 C# 语言的技术统称。
◼ LINQ:能以与查询数据库相似的方式来查询内存数据。
◼ LINQ深入学习:https://docs.microsoft.com/zh-cn/dotnet/csharp/linq/
LINQ查询示例1:Linq To Objects
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public string Test1 (){ int [] scores = new int [] { 97 , 85 , 92 , 81 , 60 }; IEnumerable<int > query = from s in scores where s > 80 orderby s descending select s; string tmp = "" ; foreach ( int i in query ) { tmp += i + " " ; } return tmp; }
上面查询中LINQ查询的功能:查询值>80的元素,并按降序排序
LINQ查询示例2:Linq To Objects
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public string Test1 (){ string [] instructors = { "Aaron" , "Jane" , "Fritz" , "Keith" , "Scott" , "Tom" }; IEnumerable<string > query = from s in instructors where s.Length == 5 orderby s descending select s; string tmp = "" ; foreach (string i in query ) { tmp += i + " " ; } return tmp; }
功能:查询字符串长度==5的元素,并按降序排序
LINQ查询示例3:Linq To SQL
1 2 3 4 5 6 7 8 9 10 public IActionResult Test3 (){ string searchString = "剧情" ; var query = from s in _context.Movie where s.Genre.Contains(searchString) orderby s.Id select s; return View( query ); }
功能:查询类型为”剧情”的电影,并按 id 升序排序
Test3视图代码:
1 2 3 4 5 6 7 8 9 10 11 12 @model IEnumerable<MvcMovie.Models.Movie> <h4>查询结果</h4> @foreach ( var m in Model ) { <p>ID序号:@m.Id</p> <p>电影名称:@m.Title</p> <p>电影类型:@m.Genre</p> <p>上映日期:@m.ReleaseDate</p> <p>票价:@m.Price</p> <hr /> }
LINQ查询示例4:配合form表单
(1)修改Index代码:注释原来的代码
MoviesController.cs
1 2 3 4 5 6 7 8 9 10 11 public async Task<IActionResult> Index ( string searchString ) { if ( String.IsNullOrEmpty(searchString) ) { searchString = "" ; } var movies = from m in _context.Movie where m.Title.Contains(searchString) select m; return View( await movies.ToListAsync() ); }
说明:async Task/await 是 C# 异步编程模式
了解异步编程模式:async Task/await
◼ async Task/await 是一种异步/等待任务编程模式
◼ 优势:更有效地利用服务器资源,并且服务器可以无延迟地处理更多流量
◼ 只有导致查询或发送数据库命令的语句才能以异步方式执行,包括:
◼ ToList Async、SingleOrDefault Async、FirstOrDefault Async 和 SaveChanges Async。
(2)修改Index视图:添加查询表单
注:代码放在Index视图标签前面
Index.cshtml
1 2 3 4 5 6 <form asp-controller ="Movies" asp-action ="Index" method ="get" > <p > 电影名称: <input type ="text" name ="searchString" > <input type ="submit" value ="查询" /> </p > </form >
CRUD编程 控制器代码 MoviesController.cs
1 2 3 4 5 6 7 8 9 10 11 public class MoviesController : Controller { private readonly MvcMovieContext _context; public MoviesController (MvcMovieContext context ) { _context = context; } }
回顾:数据库上下文类作用:封装与数据库和模型相关的功能,依据数据实体状态创建SQL命令,将数据更新保存到数据库中
查询所有 – Index
Index代码:原来的
1 2 3 4 5 6 public async Task<IActionResult> Index () { return _context.Movie != null ? View(await _context.Movie.ToListAsync()) : Problem("Entity set 'MvcMovieContext.Movie' is null." ); }
◼ _context.Movie:读取Movie集合所有数据,返回类型是 DbSet 集合
◼ ToListAsync():将 DbSet 集合转为 List 集合
Index视图代码
Index.cshtml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @model IEnumerable<MvcMovie.Models.Movie> @* @model命令:声明视图Model对象的类型(强类型,多值) *@ @{ ViewData["Title" ] = "Index" ; } <h1>Index</h1> <p> <a asp-action="Create" >Create New</a> </p> <!-- 生成为<a href="/Movies/Create" >Create</a> --> <table class ="table" > <thead> <tr> <th> @Html.DisplayNameFor(model => model.Title) </th> <!-- model => model.Title 为 lambda表达式 --> <th> @Html.DisplayNameFor(model => model.ReleaseDate) </th> <th> @Html.DisplayNameFor(model => model.Genre) </th> <th> @Html.DisplayNameFor(model => model.Price) </th> <!-- @Html.DisplayNameFor用于显示属性的Display值(中文值) --> <th></th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Title) </td> <!-- @Html.DisplayFor用于显示属性的值 --> <td> @Html.DisplayFor(modelItem => item.ReleaseDate) </td> <td> @Html.DisplayFor(modelItem => item.Genre) </td> <td> @Html.DisplayFor(modelItem => item.Price) </td> <td> <a asp-action="Edit" asp-route-id="@item.Id" >Edit</a> | <a asp-action="Details" asp-route-id="@item.Id" >Details</a> | <a asp-action="Delete" asp-route-id="@item.Id" >Delete</a> <!-- 上面三个<a>将生成为: <a href="/Movies/Edit/id值" >Edit</a> <a href="/Movies/Details/id值" >Edit</a> <a href="/Movies/Delete/id值" >Edit</a> id值由具体对象确定 --> </td> </tr> } </tbody> </table>
用法说明
① <a asp-action="Edit" asp-route-id="@item.Id">Edit</a>
◼ *asp-:使用 TagHelpers 技术来动态生成 HTML 属性
◼ TagHelpers 技术基本用法见【附录1】
② @Html.DisplayNameFor(model => model.Title)
@Html.DisplayFor(modelItem => item.Title)
◼ @Html.方法():使用 HtmlHelper 类动态创建 HTML 标签或字符串
◼ HtmlHelper类 基本用法见【附录2】
添加操作 – Create 有两个Create操作
第一个Create代码(功能:打开新建界面,用户进行数据录入)
1 2 3 4 public IActionResult Create (){ return View(); }
Create视图代码
Create.cshtml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @model MvcMovie.Models.Movie //@model命令:强类型,传单值(思考一下:第一个Create并传值为何视图要加@model?) @{ ViewData["Title"] = "Create"; } <h1 > Create</h1 > <h4 > Movie</h4 > <hr /> <div class ="row" > <div class ="col-md-4" > <form asp-action ="Create" > </div > <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 > <div class ="form-group" > <label asp-for ="Genre" class ="control-label" > </label > <input asp-for ="Genre" class ="form-control" /> <span asp-validation-for ="Genre" class ="text-danger" > </span > </div > <div class ="form-group" > <label asp-for ="Price" class ="control-label" > </label > <input asp-for ="Price" class ="form-control" /> <span asp-validation-for ="Price" class ="text-danger" > </span > </div > <div class ="form-group" > <input type ="submit" value ="Create" class ="btn btn-primary" /> </div > </form > </div > </div > <div > <a asp-action ="Index" > Back to List</a > </div > @section Scripts { // JS代码放在Scripts节中 @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} // 加载"_ValidationScriptsPartial"局部视图,该视图加载了支持验证功能的JS库 }
Tag Helpers技术参考见【附录1】
第二个Create代码(功能:将新建数据提交保存到数据库)
1 2 3 4 5 6 7 8 9 10 11 12 13 [HttpPost ] [ValidateAntiForgeryToken ] public async Task<IActionResult> Create ( [Bind("Id,Title,ReleaseDate,Genre,Price" )] Movie movie ) { if ( ModelState.IsValid ) { _context.Add(movie); await _context.SaveChangesAsync(); return RedirectToAction( nameof (Index) ); } return View( movie ); }
◼ _context.Add()
:调用数据库上下文的Add方法保存数据
◼ _context.SaveChangesAsync()
:将更新的数据保存到数据库(持久化
代码说明:
(1)3个注解:
◼ [HttpPost] 注解 :表明只能由 POST 请求才能调用此 Action 方法,不写默认为[HttpGet]
(第一个 Create 就是 GET )
◼ [ValidateAntiForgeryToken] 注解 :用于防止请求伪造 (更安全)
◼ [Bind(“字段”)] 注解 :指定能接收到的数据字段,防止过度发布 (更安全)
(2)return RedirectToAction( nameof(Index) );
◼ **nemeof()**:获取变量或类型的名称,nameof(Index)
:得到Index这个Action的名称,即字符串”Index”
◼ 因此代码相当于:return RedirectToAction("Index");
功能就是:重定向(跳转)到Index并执行
编辑操作 – Edit 有两个Edit操作:与Create操作类似
第一个Edit代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 public async Task<IActionResult> Edit ( int ? id ) { if (id == null || _context.Movie == null ) { return NotFound(); } var movie = await _context.Movie.FindAsync( id ); if (movie == null ) { return NotFound(); } return View(movie); }
Edit视图代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @model MvcMovie.Models.Movie @{ ViewData["Title"] = "Edit"; } <h1 > Edit</h1 > <h4 > Movie</h4 > <hr /> <div class ="row" > <div class ="col-md-4" > <form asp-action ="Edit" > <div asp-validation-summary ="ModelOnly" class ="text-danger" > </div > <input type ="hidden" asp-for ="Id" /> <div class ="form-group" > <label asp-for ="Title" class ="control-label" > </label > <input asp-for ="Title" class ="form-control" /> <span asp-validation-for ="Title" class ="text-danger" > </span > </div > <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 > <div class ="form-group" > <label asp-for ="Genre" class ="control-label" > </label > <input asp-for ="Genre" class ="form-control" /> <span asp-validation-for ="Genre" class ="text-danger" > </span > </div > <div class ="form-group" > <label asp-for ="Price" class ="control-label" > </label > <input asp-for ="Price" class ="form-control" /> <span asp-validation-for ="Price" class ="text-danger" > </span > </div > <div class ="form-group" > <input type ="submit" value ="Save" class ="btn btn-primary" /> </div > </form > </div > </div > @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} }
对比一下Create视图,可以看到二者代码几乎相同,但要注意:
(1) Edit 视图模板的 Model 对象一开始就接收了强类型传值
(2) Edit 视图必须要携带对象 Id 值(可隐藏不显示)才能实现最终的修改,否则提交无效
第二个Edit代码:(功能:将修改的数据提交保存到数据库)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 [HttpPost ] [ValidateAntiForgeryToken ] public async Task<IActionResult> Edit ( int id, // id要单独接收 [Bind("Id,Title,ReleaseDate,Genre,Price" )] Movie movie ){ if ( id != movie.Id ) { return NotFound(); } if ( ModelState.IsValid ) { try { _context.Update(movie); await _context.SaveChangesAsync(); } catch ( DbUpdateConcurrencyException ) { if ( !MovieExists(movie.Id) ) { return NotFound(); } else { throw ; } } return RedirectToAction( nameof (Index) ); } return View( movie ); } private bool MovieExists (int id ){ return (_context.Movie?.Any(e => e.Id == id)).GetValueOrDefault(); }
查询明细 – Details
Details界面:
Details代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public async Task<IActionResult> Details ( int ? id ){ if (id == null || _context.Movie == null ) { return NotFound(); } var movie = await _context.Movie.FirstOrDefaultAsync( m => m.Id == id ); if ( movie == null ) { return NotFound(); } return View(movie); }
Details视图代码:和Index视图类似,只不过是单值
HtmlHelper类参考【附录2】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 @model MvcMovie.Models.Movie @{ ViewData["Title"] = "Details"; } <h1 > Details</h1 > <div > <h4 > Movie</h4 > <hr /> <dl class ="row" > <dt class = "col-sm-2" > @Html.DisplayNameFor(model => model.Title) </dt > <dd class = "col-sm-10" > @Html.DisplayFor(model => model.Title) </dd > <dt class = "col-sm-2" > @Html.DisplayNameFor(model => model.ReleaseDate) </dt > <dd class = "col-sm-10" > @Html.DisplayFor(model => model.ReleaseDate) </dd > <dt class = "col-sm-2" > @Html.DisplayNameFor(model => model.Genre) </dt > <dd class = "col-sm-10" > @Html.DisplayFor(model => model.Genre) </dd > <dt class = "col-sm-2" > @Html.DisplayNameFor(model => model.Price) </dt > <dd class = "col-sm-10" > @Html.DisplayFor(model => model.Price) </dd > </dl > </div > <div > <a asp-action ="Edit" asp-route-id ="@Model.Id" > Edit</a > |<a asp-action ="Index" > Back to List</a > </div >
删除操作 – Delete
有两个Delete操作:
第一个Delete代码:(功能:根据id值查找对象,并送到Delete.cshtm视图显示)
1 2 3 4 5 6 7 8 9 10 11 12 13 public async Task<IActionResult> Delete ( int ? id ) { if (id == null || _context.Movie == null ) { return NotFound(); } var movie = await _context.Movie.FirstOrDefaultAsync(m => m.Id == id); if (movie == null ) { return NotFound(); } return View(movie); }
Delete视图代码:(前面与Details一样)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 @model MvcMovie.Models.Movie @{ ViewData["Title"] = "Delete"; } <h1 > Details</h1 > <h3 > Are you sure you want to delete this?</h3 > <div > <h4 > Movie</h4 > <hr /> <dl class ="row" > <dt class = "col-sm-2" > @Html.DisplayNameFor(model => model.Title) </dt > <dd class = "col-sm-10" > @Html.DisplayFor(model => model.Title) </dd > <dt class = "col-sm-2" > @Html.DisplayNameFor(model => model.ReleaseDate) </dt > <dd class = "col-sm-10" > @Html.DisplayFor(model => model.ReleaseDate) </dd > <dt class = "col-sm-2" > @Html.DisplayNameFor(model => model.Genre) </dt > <dd class = "col-sm-10" > @Html.DisplayFor(model => model.Genre) </dd > <dt class = "col-sm-2" > @Html.DisplayNameFor(model => model.Price) </dt > <dd class = "col-sm-10" > @Html.DisplayFor(model => model.Price) </dd > </dl > <form asp-action ="Delete" > <input type ="hidden" asp-for ="Id" /> <input type ="submit" value ="Delete" class ="btn btn-danger" /> | <a asp-action ="Index" > Back to List</a > </form > </div >
第二个Delete代码:(功能:根据id值查找Movie对象并删除)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [HttpPost, ActionName("Delete" ) ] [ValidateAntiForgeryToken ] public async Task<IActionResult> DeleteConfirmed ( int id ){ if (_context.Movie == null ) { return Problem("Entity set 'MvcMovieContext.Movie' is null." ); } var movie = await _context.Movie.FindAsync( id ); if (movie != null ) { _context.Movie.Remove(movie); } await _context.SaveChangesAsync(); return RedirectToAction(nameof (Index)); }
附录 【附录1】Tag Helpers技术 ◼ Tag Helpers 允许服务器端在视图中动态创建和生成HTML元素属性
◼ 示例:
Tag Helpers写法
1 <a asp-controller="Movies" asp-action="Edit" asp-route-id="@item.ID">Edit</a>
生成的HTML(假设id=1)↓
1 <a href="/Movies/Edit/1">Edit</a>
一些常用的 asp-*
属性
属性
说明
asp-controller
controller的名称
asp-action
action方法的名称
asp-route-id
id值
asp-for
为元素绑定模型属性
asp-validation-for
为元素添加验证消息
TagHelpers技术参考:
https://docs.microsoft.com/zh-cn/aspnet/core/mvc/views/tag-helpers/built-in/anchor-tag-helper?view=aspnetcore-5.0
https://docs.microsoft.com/zh-cn/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-5.0
asp-for 示例:
Tag Helpers写法
1 <input asp-for="Title" class="form-control" /> <!-- 这个"Title"是模型的一个字段属性 -->
◼ 自动生成 id 和 name(默认都为模型属性名)
◼ 自动生成 type 类型(如string字段对应type=”text”)
◼ 自动绑定 value 值(模型字段如果有值)
↓
原生写法
1 <input class="form-control" type="text" id="Title" name="Title" value="Title属性值">
asp-validation-for 示例
Tag Helpers写法
1 <span asp-validation-for ="Email" > </span >
[Required]
[EmailAddress]
public string Email { get; set; }
◼ 添加 HTML5 的 data-valmsg-for="property"
属性
◼ 该元素会附加指定模型属性的输入字段中的验证错误消息
↓
原生写法
1 2 3 <span class ="field-validation-valid" data-valmsg-for ="Email" data-valmsg-replace ="true" > The Email Address field is required. </span >
【附录2】Html Helper类 ◼ HtmlHelper类:用于动态创建HTML标签或字符串,提供了很多返回HTML标签的方法
◼ 示例:
Html Helper写法
1 @Html.TextBoxFor(m => m.Name)
◼ 自动生成 input 标签,并根据模型属性确定type类型
◼ 自动生成 id 和 name(默认都为模型属性名)
◼ 自动绑定 value 值(模型字段如果有值)
↓
生成的HTML
1 <input id ="Name" name ="Name" type ="text" value ="" >
HtmlHelper类常用方法
方法
说明
DisplayNameFor
返回字段的Display值(如中文值,没有设置则默认为字段名)
DisplayFor
返回字段的值(字符串)
LabelFor
生成Label标签
TextBoxFor
生成input标签
ValidationMessageFor
生成span标签,用于验证消息
HtmlHelper类参考:
https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.mvc.viewfeatures.htmlhelper?view=aspnetcore-5.0
HtmlHelper类示例:(以Movie对象为例)
1 2 3 4 @Html.DisplayNameFor(m => m.Name) @Html.DisplayFor(m => m.Name) @Html.LabelFor(m => m.Name) @Html.ValidationMessageFor(m => m.Name)
↓
生成的HTML
1 2 3 4 电影名称 (属性的Display值) 肖申克的救赎 (属性值) <label for ="Name" > Name</label > <span class ="field-validation-valid" data-valmsg-for ="Name" data-valmsgreplace ="true" > </span >