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教程

相關文章
相關標籤/搜索