一. 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就是一个模型类。

  1.   public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)

 

  (3) 模型验证

    ASP.NET Core MVC 通过使用数据注释验证属性。 验证属性在值发送到服务端前,在客户端上进行检查。并在调用控制器action前在服务端上进行检查。

  1. using System.ComponentModel.DataAnnotations;
  2. public class LoginViewModel
  3. {
  4. [Required]
  5. [EmailAddress]
  6. public string Email { get; set; }
  7.  
  8. [Required]
  9. [DataType(DataType.Password)]
  10. public string Password { get; set; }
  11.  
  12. [Display(Name = "Remember me?")]
  13. public bool RememberMe { get; set; }
  14. }
  15.  
  16. //服务端控制器action验证
  17. public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
  18. {
  19. //验证模型
  20. if (ModelState.IsValid)
  21. {
  22. // work with the model
  23. }
  24. return View(model);
  25. }

  (4) 依赖注入

    依赖关系注入除了在控制器上通过构造函数请求所需服务,还可以使用@inject 指令,应用在视图文件上。下面是视图页面上通过依赖注入获取服务对象。

  1. @inject SomeService ServiceName
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <title>@ServiceName.GetTitle</title>
  6. </head>
  7. <body>
  8. <h1>@ServiceName.GetTitle</h1>
  9. </body>
  10. </html>

  (5) 筛选器

    筛选器帮助开发者封装,横切关注点,例如异常处理或授权。筛选器允许action方法运行自定义预处理和后处理逻辑,并且可以配置为在给定请求的执行管道内的特定点上运行。筛选器可以作为属性应用于控制器或Action(也可以全局运行)。例如MVC 授权筛选器。

  1. [Authorize]
  2. 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标记

  1. <ul>
  2. @for (int i = 0; i <; i++) {
  3. <li>List item @i</li>
  4. }
  5. </ul>

  (10) 强类型视图

    可以基于模型强类型化 MVC 中的 Razor 视图。 控制器可以将强类型化的模型传递给视图,使视图具备类型检查和 IntelliSense 支持。例如,以下视图呈现类型为 IEnumerable<Product> 的模型:

  1. @model IEnumerable<Product>
  2. <ul>
  3. @foreach (Product p in Model)
  4. {
  5. <li>@p.Name</li>
  6. }
  7. </ul>

  (11) 标记帮助程序

    标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。 例如,内置 LinkTagHelper 可以用来创建指向 AccountsController控制器中  Login的方法链接

  1. <p>
  2. Thank you for confirming your email.
  3. Please <a asp-controller="Account" asp-action="Login">Click here to Log in</a>.
  4. </p>

  (12) 视图组件

    通过视图组件可以包装呈现逻辑并在整个应用程序中重用它。 这些组件类似于分部视图,但具有关联逻辑。

     

二. 完整示例介绍(项目StudyMVCDemo)

  2.1 安装EF数据提供程序

    这里使用内存数据库Microsoft.EntityFrameworkCore.InMemory,Entity Framework Core 和内存数据库一起使用, 这对测试非常有用。

  1.     PM> Install-Package Microsoft.EntityFrameworkCore.InMemory

  2.2 新建数据模型类(POCO )和EF上下文类

  1. public class MvcMovieContext : DbContext
  2. {
  3. public MvcMovieContext(DbContextOptions options)
  4. : base(options)
  5. {
  6. }
  7. public DbSet<Movie> Movie { get; set; }
  8. }
  1. public class Movie
  2. {
  3. public int Id { get; set; }
  4. public string Title { get; set; }
  5.  
  6. [DataType(DataType.Date)]
  7. public DateTime ReleaseDate { get; set; }
  8. public string Genre { get; set; }
  9. public decimal Price { get; set; }
  10. }

  2.3 初始化数据

  1. public static void Main(string[] args)
  2. {
  3. var host = CreateWebHostBuilder(args).Build();
  4.  
  5. using (var scope = host.Services.CreateScope())
  6. {
  7. var services = scope.ServiceProvider;
  8.  
  9. try
  10. {
  11. //var context = services.GetRequiredService<MvcMovieContext>();
  12. //程序运行时,使用EF迁移生成数据,用在关系型数据库
  13. //context.Database.Migrate();
  14. SeedData.Initialize(services);
  15. }
  16. catch (Exception ex)
  17. {
  18. var logger = services.GetRequiredService<ILogger<Program>>();
  19. logger.LogError(ex, "An error occurred seeding the DB.");
  20. }
  21. }
  22. host.Run();
  23. }
  1. public static class SeedData
  2. {
  3. /// <summary>
  4. /// 初始化数据
  5. /// </summary>
  6. /// <param name="serviceProvider"></param>
  7. public static void Initialize(IServiceProvider serviceProvider)
  8. {
  9. using (var context = new MvcMovieContext(
  10. serviceProvider.GetRequiredService<DbContextOptions<MvcMovieContext>>()))
  11. {
  12. // 如果有数据返回
  13. if (context.Movie.Any())
  14. {
  15. return; // DB has been seeded
  16. }
  17.  
  18. context.Movie.AddRange(
  19. new Movie
  20. {
  21. Title = "When Harry Met Sally",
  22. ReleaseDate = DateTime.Parse("1989-2-12"),
  23. Genre = "Romantic Comedy",
  24. Price = 7.99M
  25. },
  26.  
  27. new Movie
  28. {
  29. Title = "Ghostbusters ",
  30. ReleaseDate = DateTime.Parse("1984-3-13"),
  31. Genre = "Comedy",
  32. Price = 8.99M
  33. },
  34.  
  35. new Movie
  36. {
  37. Title = "Ghostbusters 2",
  38. ReleaseDate = DateTime.Parse("1986-2-23"),
  39. Genre = "Comedy",
  40. Price = 9.99M
  41. },
  42.  
  43. new Movie
  44. {
  45. Title = "Rio Bravo",
  46. ReleaseDate = DateTime.Parse("1959-4-15"),
  47. Genre = "Western",
  48. Price = 3.99M
  49. }
  50. );
  51. context.SaveChanges();
  52. }
  53. }
  54. }

  

  2.4 添加控制器类(MoviesController)

  1.  public class MoviesController : Controller
  2. {
  3.  
  4. private readonly MvcMovieContext _MvcMovieContext;
  5.  
  6. public MoviesController(MvcMovieContext MvcMovieContext)
  7. {
  8. this._MvcMovieContext = MvcMovieContext;
  9. }
  10. }

  2.5 列表页Movies/index.cshtml

  1. // GET: /<controller>/
  2. public IActionResult Index()
  3. {
  4. var movies = _MvcMovieContext.Movie.ToList();
  5. return View(movies);
  6. }
  1. @model IEnumerable<StudyMVCDemo.Models.Movie>
  2.  
  3. @{
  4. ViewData["Title"] = "Index";
  5. }
  6.  
  7. <h1>Index</h1>
  8.  
  9. <p>
  10. <a asp-action="Create">Create New</a>
  11. </p>
  12. <table class="table">
  13. <thead>
  14. <tr>
  15. <th>
  16. @Html.DisplayNameFor(model => model.Title)
  17. </th>
  18. <th>
  19. @Html.DisplayNameFor(model => model.ReleaseDate)
  20. </th>
  21. <th>
  22. @Html.DisplayNameFor(model => model.Genre)
  23. </th>
  24. <th>
  25. @Html.DisplayNameFor(model => model.Price)
  26. </th>
  27. <th></th>
  28. </tr>
  29. </thead>
  30. <tbody>
  31. @foreach (var item in Model)
  32. {
  33. <tr>
  34. <td>
  35. @Html.DisplayFor(modelItem => item.Title)
  36. </td>
  37. <td>
  38. @Html.DisplayFor(modelItem => item.ReleaseDate)
  39. </td>
  40. <td>
  41. @Html.DisplayFor(modelItem => item.Genre)
  42. </td>
  43. <td>
  44. @Html.DisplayFor(modelItem => item.Price)
  45. </td>
  46. <td>
  47. <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
  48. <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
  49. <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
  50. </td>
  51. </tr>
  52. }
  53. </tbody>
  54. </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

  1. /// <summary>
  2. /// 详细页
  3. /// </summary>
  4. /// <param name="id"></param>
  5. /// <returns></returns>
  6. public async Task<IActionResult> Details(int? id)
  7. {
  8. if (id == null)
  9. {
  10. return NotFound();
  11. }
  12.  
  13. var movie = await _MvcMovieContext.Movie
  14. .FirstOrDefaultAsync(m => m.Id == id);
  15. if (movie == null)
  16. {
  17. return NotFound();
  18. }
  19. return View(movie);
  20. }
  1. @model StudyMVCDemo.Models.Movie
  2.  
  3. @{
  4. ViewData["Title"] = "Details";
  5. }
  6.  
  7. <h1>Details</h1>
  8.  
  9. <div>
  10. <h4>Movie</h4>
  11. <hr />
  12. <dl class="row">
  13. <dt class="col-sm-2">
  14. @Html.DisplayNameFor(model => model.Title)
  15. </dt>
  16. <dd class="col-sm-10">
  17. @Html.DisplayFor(model => model.Title)
  18. </dd>
  19. <dt class="col-sm-2">
  20. @Html.DisplayNameFor(model => model.ReleaseDate)
  21. </dt>
  22. <dd class="col-sm-10">
  23. @Html.DisplayFor(model => model.ReleaseDate)
  24. </dd>
  25. <dt class="col-sm-2">
  26. @Html.DisplayNameFor(model => model.Genre)
  27. </dt>
  28. <dd class="col-sm-10">
  29. @Html.DisplayFor(model => model.Genre)
  30. </dd>
  31. <dt class="col-sm-2">
  32. @Html.DisplayNameFor(model => model.Price)
  33. </dt>
  34. <dd class="col-sm-10">
  35. @Html.DisplayFor(model => model.Price)
  36. </dd>
  37. </dl>
  38. </div>
  39. <div>
  40. <a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
  41. <a asp-action="Index">Back to List</a>
  42. </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 特性。

  1. public async Task<IActionResult> Edit(int? id)
  2. {
  3. if (id == null)
  4. {
  5. return NotFound();
  6. }
  7.  
  8. var movie = await _MvcMovieContext.Movie.FindAsync(id);
  9. if (movie == null)
  10. {
  11. return NotFound();
  12. }
  13. return View(movie);
  14. }
  15.  
  16. [HttpPost]
  17. [ValidateAntiForgeryToken]
  18. public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price")] Movie movie)
  19. {
  20. if (id != movie.Id)
  21. {
  22. return NotFound();
  23. }
  24.  
  25. if (ModelState.IsValid)
  26. {
  27. try
  28. {
  29. _MvcMovieContext.Update(movie);
  30. await _MvcMovieContext.SaveChangesAsync();
  31. }
  32. catch (DbUpdateConcurrencyException)
  33. {
  34. throw;
  35. }
  36. return RedirectToAction("Index");
  37. }
  38. return View(movie);
  39. }
  1. @model StudyMVCDemo.Models.Movie
  2.  
  3. @{
  4. ViewData["Title"] = "Edit";
  5. }
  6.  
  7. <h1>Edit</h1>
  8.  
  9. <h4>Movie</h4>
  10. <hr />
  11. <div class="row">
  12. <div class="col-md-4">
  13. <form asp-action="Edit">
  14. <div asp-validation-summary="ModelOnly" class="text-danger"></div>
  15. <input type="hidden" asp-for="Id" />
  16. <div class="form-group">
  17. <label asp-for="Title" class="control-label"></label>
  18. <input asp-for="Title" class="form-control" />
  19. <span asp-validation-for="Title" class="text-danger"></span>
  20. </div>
  21. <div class="form-group">
  22. <label asp-for="ReleaseDate" class="control-label"></label>
  23. <input asp-for="ReleaseDate" class="form-control" />
  24. <span asp-validation-for="ReleaseDate" class="text-danger"></span>
  25. </div>
  26. <div class="form-group">
  27. <label asp-for="Genre" class="control-label"></label>
  28. <input asp-for="Genre" class="form-control" />
  29. <span asp-validation-for="Genre" class="text-danger"></span>
  30. </div>
  31. <div class="form-group">
  32. <label asp-for="Price" class="control-label"></label>
  33. <input asp-for="Price" class="form-control" />
  34. <span asp-validation-for="Price" class="text-danger"></span>
  35. </div>
  36. <div class="form-group">
  37. <input type="submit" value="Save" class="btn btn-primary" />
  38. </div>
  39. </form>
  40. </div>
  41. </div>
  42.  
  43. <div>
  44. <a asp-action="Index">Back to List</a>
  45. </div>
  46.  
  47. @section Scripts {
  48. @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
  49. }

    启动程序,从列表页的Edit点击进入,如下图所示:

  2.8 删除

  1. // 删除没有对应的页面,从列表页的Delete点击进入,下面是删除的关键代码
  2. public async Task<IActionResult> DeleteConfirmed(int id)
  3. {
  4. var movie = await _context.Movie.FindAsync(id);
  5. _context.Movie.Remove(movie);
  6. await _context.SaveChangesAsync();
  7. return RedirectToAction(nameof(Index));
  8. }

  参考文献

    MVC教程

    

asp.net core系列 40 Web 应用MVC 介绍与详细示例的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

  7. asp.net core系列 67 Web压力测试工具WCAT

    一.介绍 最近搭建了一套CQRS框架,需要在投入开发前,进行必要的压力测试.Web Capacity Analysis Tool  (Wcat)是一种轻量级HTTP负载生成工具,主要用于衡量受控环境中 ...

  8. asp.net core系列 45 Web应用 模型绑定和验证

    一. 模型绑定 ASP.NET Core MVC 中的模型绑定,是将 HTTP 请求中的数据映射到action方法参数. 这些参数可能是简单类型的参数,如字符串.整数或浮点数,也可能是复杂类型的参数. ...

  9. asp.net core系列 42 Web 应用 分部视图

    一.分部视图 对于MVC 视图和 Razor Pages 页面,都有分部视图功能.通常将 MVC 视图和 Razor Pages 页面统称为“标记文件”,下面会常提到该名词.使用分部视图的优势包括:( ...

随机推荐

  1. hashMap源码学习记录

    hashMap作为java开发面试最常考的一个题目之一,有必要花时间去阅读源码,了解底层实现原理. 首先,让我们看看hashMap这个类有哪些属性 // hashMap初始数组容量 static fi ...

  2. python 获取mac地址zz

    通过python获取当前mac地址的方法如下:(1)通用方法,借助uuid模块def get_mac_address(): import uuid      node = uuid.getnode() ...

  3. Android的自定义View及View的绘制流程

    目标:实现Android中的自定义View,为理清楚Android中的View绘制流程“铺路”. 想法很简单:从一个简单例子着手开始编写自定义View,对ViewGroup.View类中与绘制View ...

  4. 对象关系映射 ORM

    1.1 作用 MTV框架中包括一个重要的部分,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因 ...

  5. nw.js中用sqlite3

    前一段时间,nw.js 项目中想用一个本地数据,最后选择了sqlite3.选好之后就开始干吧,结果mmp,被坑了好久. nw.js官方文档也有调用node原生模块的API,但是照着文档那样配置,以为会 ...

  6. php基础-mysqli

    基本八个步骤 //连接数据库 $link = mysqli_connect('localhost', 'root', ''); //判断是否连接成功 if (!$link) { exit('数据库连接 ...

  7. C++或C#调用外部exe的分析

    假如有个外部程序名为A.exe,放在目录E:\temp\下,然后我们用C++或者C#写一个程序调用这个A.exe的话(假设这个调用者所在的路径在D:\invoke),通常会采用下面的代码: // C# ...

  8. Dubbo+zookeeper构建高可用分布式集群(一)-单机部署

    不久前,我们讨论过Nginx+tomcat组成的集群,这已经是非常灵活的集群技术,但是当我们的系统遇到更大的瓶颈,全部应用的单点服务器已经不能满足我们的需求,这时,我们要考虑另外一种,我们熟悉的内容, ...

  9. Trie树详解及其应用

    一.知识简介        最近在看字符串算法了,其中字典树.AC自动机和后缀树的应用是最广泛的了,下面将会重点介绍下这几个算法的应用.      字典树(Trie)可以保存一些字符串->值的对 ...

  10. [Swift]LeetCode145. 二叉树的后序遍历 | Binary Tree Postorder Traversal

    Given a binary tree, return the postorder traversal of its nodes' values. Example: Input: [1,null,2, ...