asp.net core系列 40 Web 应用MVC 介绍与详细示例
一. mvc介绍
mvc架构模式有助于实现关注点分离。视图和控制器均依赖于模型。 但是,模型既不依赖于视图,也不依赖于控制器。 这是分离的一个关键优势。 这种分离允许模型独立于可视化展示进行构建和测试。asp.net core mvc 包括以下功能:
路由、模型绑定、模型验证、依赖关系注入、筛选器、区域、web api、可测试性、razor 视图引擎、强类型视图、标记帮助程序、 视图组件。
(1) 路由
asp.net core mvc 建立在 asp.net core 的路由之上,是一个功能强大的 url 映射组件,可用于生成具有易于理解和可搜索 url 的应用程序。关于路由知识,请查看asp.net core 系列第5,6章。
(2) 模型绑定(model)
asp.net core mvc 模型绑定将客户端请求数据(窗体值(form)、路由数据、查询字符串参数、http 头)转换到控制器(controller)可以处理的对象中。 因此,控制器逻辑不必找出传入的请求数据;它只需具备作为其action方法的参数的数据。下面的loginviewmodel就是一个模型类。
public async task<iactionresult> login(loginviewmodel model, string returnurl = null)
(3) 模型验证
asp.net core mvc 通过使用数据注释验证属性。 验证属性在值发送到服务端前,在客户端上进行检查。并在调用控制器action前在服务端上进行检查。
using system.componentmodel.dataannotations; public class loginviewmodel { [required] [emailaddress] public string email { get; set; } [required] [datatype(datatype.password)] public string password { get; set; } [display(name = "remember me?")] public bool rememberme { get; set; } } //服务端控制器action验证 public async task<iactionresult> login(loginviewmodel model, string returnurl = null) { //验证模型 if (modelstate.isvalid) { // work with the model } return view(model); }
(4) 依赖注入
依赖关系注入除了在控制器上通过构造函数请求所需服务,还可以使用@inject 指令,应用在视图文件上。下面是视图页面上通过依赖注入获取服务对象。
@inject someservice servicename <!doctype html> <html lang="en"> <head> <title>@servicename.gettitle</title> </head> <body> <h1>@servicename.gettitle</h1> </body> </html>
(5) 筛选器
筛选器帮助开发者封装,横切关注点,例如异常处理或授权。筛选器允许action方法运行自定义预处理和后处理逻辑,并且可以配置为在给定请求的执行管道内的特定点上运行。筛选器可以作为属性应用于控制器或action(也可以全局运行)。例如mvc 授权筛选器。
[authorize] public class accountcontroller : controller
(6) 区域
区域用在大型web开发上, 是功能分组的方法。区域是应用程序内的一个 mvc 结构。 例如,具有多个业务单位(如结账、计费、搜索等)的电子商务应用。每个单位都有自己的逻辑组件视图、控制器和模型。
(7) web api
除了作为生成网站的强大平台,asp.net core mvc 还对生成 web api 提供强大的支持。 可以生成可连接大量客户端(包括浏览器和移动设备)的服务,前面章节有讲过。
(8) 可测试性
框架对界面和依赖项注入的使用非常适用于单元测试,并且该框架还包括使得集成测试快速轻松的功能(例如 testhost 和实体框架的 inmemory 提供程序)
(9) razor 视图引擎
asp.net core mvc 视图使用 razor 视图引擎呈现视图。 razor 是一种紧凑、富有表现力且流畅的模板标记语言,用于使用嵌入式 c# 代码定义视图。 razor 用于在服务器上动态生成 web 内容。 可以完全混合服务器代码与客户端内容和代码。例如下面嵌入 c#代码,循环输出5组li标记
<ul> @for (int i = 0; i < 5; i++) { <li>list item @i</li> } </ul>
(10) 强类型视图
可以基于模型强类型化 mvc 中的 razor 视图。 控制器可以将强类型化的模型传递给视图,使视图具备类型检查和 intellisense 支持。例如,以下视图呈现类型为 ienumerable<product>
的模型:
@model ienumerable<product> <ul> @foreach (product p in model) { <li>@p.name</li> } </ul>
(11) 标记帮助程序
标记帮助程序使服务器端代码可以在 razor 文件中参与创建和呈现 html 元素。 例如,内置 linktaghelper 可以用来创建指向 accountscontroller
控制器中
login
的方法链接
<p> thank you for confirming your email. please <a asp-controller="account" asp-action="login">click here to log in</a>. </p>
(12) 视图组件
通过视图组件可以包装呈现逻辑并在整个应用程序中重用它。 这些组件类似于分部视图,但具有关联逻辑。
二. 完整示例介绍(项目studymvcdemo)
2.1 安装ef数据提供程序
这里使用内存数据库microsoft.entityframeworkcore.inmemory,entity framework core 和内存数据库一起使用, 这对测试非常有用。
pm> install-package microsoft.entityframeworkcore.inmemory
2.2 新建数据模型类(poco )和ef上下文类
public class mvcmoviecontext : dbcontext { public mvcmoviecontext(dbcontextoptions options) : base(options) { } public dbset<movie> movie { get; set; } }
public class movie { public int id { get; set; } public string title { get; set; } [datatype(datatype.date)] public datetime releasedate { get; set; } public string genre { get; set; } public decimal price { get; set; } }
2.3 初始化数据
public static void main(string[] args) { var host = createwebhostbuilder(args).build(); using (var scope = host.services.createscope()) { var services = scope.serviceprovider; try { //var context = services.getrequiredservice<mvcmoviecontext>(); //程序运行时,使用ef迁移生成数据,用在关系型数据库 //context.database.migrate();
seeddata.initialize(services); } catch (exception ex) { var logger = services.getrequiredservice<ilogger<program>>(); logger.logerror(ex, "an error occurred seeding the db."); } } host.run(); }
public static class seeddata { /// <summary> /// 初始化数据 /// </summary> /// <param name="serviceprovider"></param> public static void initialize(iserviceprovider serviceprovider) { using (var context = new mvcmoviecontext( serviceprovider.getrequiredservice<dbcontextoptions<mvcmoviecontext>>())) { // 如果有数据返回 if (context.movie.any()) { return; // db has been seeded } context.movie.addrange( new movie { title = "when harry met sally", releasedate = datetime.parse("1989-2-12"), genre = "romantic comedy", price = 7.99m }, new movie { title = "ghostbusters ", releasedate = datetime.parse("1984-3-13"), genre = "comedy", price = 8.99m }, new movie { title = "ghostbusters 2", releasedate = datetime.parse("1986-2-23"), genre = "comedy", price = 9.99m }, new movie { title = "rio bravo", releasedate = datetime.parse("1959-4-15"), genre = "western", price = 3.99m } ); context.savechanges(); } } }
2.4 添加控制器类(moviescontroller)
public class moviescontroller : controller { private readonly mvcmoviecontext _mvcmoviecontext; public moviescontroller(mvcmoviecontext mvcmoviecontext) { this._mvcmoviecontext = mvcmoviecontext; } }
2.5 列表页movies/index.cshtml
// get: /<controller>/ public iactionresult index() { var movies = _mvcmoviecontext.movie.tolist(); return view(movies); }
@model ienumerable<studymvcdemo.models.movie> @{ viewdata["title"] = "index"; } <h1>index</h1> <p> <a asp-action="create">create new</a> </p> <table class="table"> <thead> <tr> <th> @html.displaynamefor(model => model.title) </th> <th> @html.displaynamefor(model => model.releasedate) </th> <th> @html.displaynamefor(model => model.genre) </th> <th> @html.displaynamefor(model => model.price) </th> <th></th> </tr> </thead> <tbody> @foreach (var item in model) { <tr> <td> @html.displayfor(modelitem => item.title) </td> <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> </td> </tr> } </tbody> </table>
启动程序,在浏览器中输入http://localhost:18084/movies,如下图所示:
上图中菜单布局是在 views/shared/_layout.cshtml 文件中实现的,该_layout.cshtml页中@renderbody()是视图页面的占位符。
views/_viewstart.cshtml 文件将 views/shared/_layout.cshtml 文件引入到每个视图中。 可以使用 layout
属性设置不同的布局视图,或将它设置为 null
,这样将不会使用任何布局文件。后面详细了解布局。
2.6 详细页movies/ details.cshtml
/// <summary> /// 详细页 /// </summary> /// <param name="id"></param> /// <returns></returns> public async task<iactionresult> details(int? id) { if (id == null) { return notfound(); } var movie = await _mvcmoviecontext.movie .firstordefaultasync(m => m.id == id); if (movie == null) { return notfound(); } return view(movie); }
@model studymvcdemo.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>
启动程序,从列表页的超连接details点击进入,如下图所示:
2.7 编辑页movies/ edit.cshtml
对于编辑页有二个action, 一个是get用来提取数据填充到表单,一个是post用来提交修改的表单数据。
(1) post中的bind特性是对需要的属性进行更新。
(2) validateantiforgerytoken特性用于防止请求伪造, 生成的隐藏的 xsrf 标记 input name="__requestverificationtoken"。用在post提交的比如修改和删除功能等。
(3) 模型验证asp-validation-for是指表单post到服务器之前,客户端验证会检查字段上的任何验证规则。 如果有任何验证错误,则将显示错误消息,并且不会post表单,内部是输入标记帮助程序使用 dataannotations 特性,并在客户端上生成 jquery 验证所需的 html 特性。
public async task<iactionresult> edit(int? id) { if (id == null) { return notfound(); } var movie = await _mvcmoviecontext.movie.findasync(id); if (movie == null) { return notfound(); } return view(movie); } [httppost] [validateantiforgerytoken] public async task<iactionresult> edit(int id, [bind("id,title,releasedate,genre,price")] movie movie) { if (id != movie.id) { return notfound(); } if (modelstate.isvalid) { try { _mvcmoviecontext.update(movie); await _mvcmoviecontext.savechangesasync(); } catch (dbupdateconcurrencyexception) { throw; } return redirecttoaction("index"); } return view(movie); }
@model studymvcdemo.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> <div> <a asp-action="index">back to list</a> </div> @section scripts { @{await html.renderpartialasync("_validationscriptspartial");} }
启动程序,从列表页的edit点击进入,如下图所示:
2.8 删除
// 删除没有对应的页面,从列表页的delete点击进入,下面是删除的关键代码 public async task<iactionresult> deleteconfirmed(int id) { var movie = await _context.movie.findasync(id); _context.movie.remove(movie); await _context.savechangesasync(); return redirecttoaction(nameof(index)); }
参考文献
上一篇: SQL的作用、语句分类以及通用语法讲解