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 <; 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));
- }
参考文献
asp.net core系列 40 Web 应用MVC 介绍与详细示例的更多相关文章
- asp.net core系列 39 Web 应用Razor 介绍与详细示例
一. Razor介绍 在使用ASP.NET Core Web开发时, ASP.NET Core MVC 提供了一个新特性Razor. 这样开发Web包括了MVC框架和Razor框架.对于Razor来说 ...
- asp.net core系列 41 Web 应用 MVC视图
一.MVC视图 在Web开发的MVC和Razor中,都有使用视图,在Razor中称为"页"..cshtml视图是嵌入了Razor标记的HTML模板. Razor 标记使用C#代码, ...
- asp.net core 系列 16 Web主机 IWebHostBuilder
一.概述 在asp.net core中,Host主机负责应用程序启动和生存期管理.host主机包括Web 主机(IWebHostBuilder)和通用主机(IHostBuilder).Web 主机是适 ...
- asp.net core 系列 18 web服务器实现
一. ASP.NET Core Module 在介绍ASP.NET Core Web实现之前,先来了解下ASP.NET Core Module.该模块是插入 IIS 管道的本机 IIS 模块(本机是指 ...
- asp.net core系列 44 Web应用 布局
一.概述 MVC的视图与Razor页面经常共享视觉和程序元素,通过使用布局来完成,布局还可减少重复代码.本章演示了以下内容的操作方法:(1)使用通用布局,(2)自定义布局,(3) 共享指令,(4)在呈 ...
- asp.net core系列 43 Web应用 Session分布式存储(in memory与Redis)
一.概述 HTTP 是无状态的协议. 默认情况下,HTTP 请求是不保留用户值或应用状态的独立消息. 本文介绍了几种保留请求间用户数据和应用状态的方法.下面以表格形式列出这些存储方式,本篇专讲Sess ...
- asp.net core系列 67 Web压力测试工具WCAT
一.介绍 最近搭建了一套CQRS框架,需要在投入开发前,进行必要的压力测试.Web Capacity Analysis Tool (Wcat)是一种轻量级HTTP负载生成工具,主要用于衡量受控环境中 ...
- asp.net core系列 45 Web应用 模型绑定和验证
一. 模型绑定 ASP.NET Core MVC 中的模型绑定,是将 HTTP 请求中的数据映射到action方法参数. 这些参数可能是简单类型的参数,如字符串.整数或浮点数,也可能是复杂类型的参数. ...
- asp.net core系列 42 Web 应用 分部视图
一.分部视图 对于MVC 视图和 Razor Pages 页面,都有分部视图功能.通常将 MVC 视图和 Razor Pages 页面统称为“标记文件”,下面会常提到该名词.使用分部视图的优势包括:( ...
随机推荐
- hashMap源码学习记录
hashMap作为java开发面试最常考的一个题目之一,有必要花时间去阅读源码,了解底层实现原理. 首先,让我们看看hashMap这个类有哪些属性 // hashMap初始数组容量 static fi ...
- python 获取mac地址zz
通过python获取当前mac地址的方法如下:(1)通用方法,借助uuid模块def get_mac_address(): import uuid node = uuid.getnode() ...
- Android的自定义View及View的绘制流程
目标:实现Android中的自定义View,为理清楚Android中的View绘制流程“铺路”. 想法很简单:从一个简单例子着手开始编写自定义View,对ViewGroup.View类中与绘制View ...
- 对象关系映射 ORM
1.1 作用 MTV框架中包括一个重要的部分,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因 ...
- nw.js中用sqlite3
前一段时间,nw.js 项目中想用一个本地数据,最后选择了sqlite3.选好之后就开始干吧,结果mmp,被坑了好久. nw.js官方文档也有调用node原生模块的API,但是照着文档那样配置,以为会 ...
- php基础-mysqli
基本八个步骤 //连接数据库 $link = mysqli_connect('localhost', 'root', ''); //判断是否连接成功 if (!$link) { exit('数据库连接 ...
- C++或C#调用外部exe的分析
假如有个外部程序名为A.exe,放在目录E:\temp\下,然后我们用C++或者C#写一个程序调用这个A.exe的话(假设这个调用者所在的路径在D:\invoke),通常会采用下面的代码: // C# ...
- Dubbo+zookeeper构建高可用分布式集群(一)-单机部署
不久前,我们讨论过Nginx+tomcat组成的集群,这已经是非常灵活的集群技术,但是当我们的系统遇到更大的瓶颈,全部应用的单点服务器已经不能满足我们的需求,这时,我们要考虑另外一种,我们熟悉的内容, ...
- Trie树详解及其应用
一.知识简介 最近在看字符串算法了,其中字典树.AC自动机和后缀树的应用是最广泛的了,下面将会重点介绍下这几个算法的应用. 字典树(Trie)可以保存一些字符串->值的对 ...
- [Swift]LeetCode145. 二叉树的后序遍历 | Binary Tree Postorder Traversal
Given a binary tree, return the postorder traversal of its nodes' values. Example: Input: [1,null,2, ...