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

一. MVC介绍

  MVC架构模式有助于实现关注点分离。视图和控制器均依赖于模型。 可是,模型既不依赖于视图,也不依赖于控制器。 这是分离的一个关键优点。 这种分离容许模型独立于可视化展现进行构建和测试。ASP.NET Core MVC 包括如下功能:html

    路由、模型绑定、模型验证、依赖关系注入、筛选器、区域、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就是一个模型类。mvc

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

 

  (3) 模型验证app

    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) 依赖注入asp.net

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

@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(); } } }
View Code

  

  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)); }

 

   参考文献

    MVC教程

相关文章
相关标签/搜索