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();
            }
        }
    }
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教程

    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM