概述

用户界面由业务逻辑确定,业务逻辑又由数据确定。完成 Model(数据)的设计后,便可以开始设计控制器(业务逻辑)和视图(用户界面)。

Layout 视图

除非是那种只有一两个页面的小 Web 程序,否则我们总是从要共享使用的 Layout 视图开始设计。

从 Layout 视图开始的一大好处是,它可以概述 Web 程序的大纲,并作为控制器的目录。例如,如果在 Layout 导航栏中添加了 Products 和 Types 链接,就意味着将来一定会添加 ProductController 和 TypeController。

创建 Layout 视图

_Layout.cshtml 必须创建在 Views\Shared\ 文件夹下:

  1. <html>
  2. <!--use table as the navigation bar-->
  3. <table>
  4. <tr>
  5. <td>|@Html.ActionLink("Home", "Index", "Home")</td>
  6. <td>|@Html.ActionLink("Products", "Index", "Product")</td>
  7. <td>|@Html.ActionLink("Types", "Index", "ProductType")</td>
  8. </tr>
  9. </table>
  10. <hr/>
  11. @RenderBody()
  12. <hr/>
  13. Welcome to My Website! (@DateTime.Now)
  14. </html>

视图使用此 Layout 作为模板时,其内容将呈现在 @RenderBody() 处。

应用 Layout 视图

为了将 _Layout.cshtml 设置为默认视图,需在 Views 文件夹下创建 _ViewStart.cshtml 并添加如下代码:

  1. @{
  2. Layout = "_Layout";
  3. }

如果有的视图不想使用该默认 Layout,可以在视图顶部添加代码:

  1. @{
  2. Layout = null;
  3. }

Home 的控制器和视图

首先添加控制器是用于渲染 Web 程序主页的 HomeController。

添加 HomeController

希望你还记得默认的路由模板:{controller=Home}/{action=Index}/{id?}

在 Controllers 文件夹下创建 HomeController:

  1. using Microsoft.AspNetCore.Mvc;
  2. public class HomeController : Controller
  3. {
  4. public IActionResult Index()
  5. {
  6. return View();
  7. }
  8. }

Layout 视图中 @Html.ActionLink("Home", "Index", "Home") 生成的超链接就链接到该控制器的 Index 方法。

添加 Index 视图

在 Views\Hone 文件夹下创建 Index.cshtml:

  1. <h1>Welcome to Use Product Manager</h1>
  2. <h2>Introduction</h2>
  3. <p>With this simple web application, you can manage the products and product types.<br/>
  4. To manage products, please click the Products link. To manage product types, please click the Types link.</p>

添加 Index 视图后,运行程序访问主页,效果如下:
5.2 实现控制器和视图 - 图1

注:此时由于另外两个视图尚未实现,所以点击它们的链接会报 404 Not Found。

ProductType 的控制器和视图

主页创建后,接着就是创建功能性的控制器。因为 Product 是依赖于 ProductType 的,所以要先创建 ProductType 的控制器和视图。

ProductType 的控制器和 Index 操作

创建功能性控制器时,我们通常从创建一个空 Controller 并添加 Index 操作开始。众所周知功能性控制器的主要工作就是 CRUD,与 Retrieve 对应的操作就是 Index。

ProductTypeController.cs:

  1. using Microsoft.AspNetCore.Mvc;
  2. using ProductManager.Models;
  3. namespace ProductManager.Controllers
  4. {
  5. public class ProductTypeController : Controller
  6. {
  7. public IActionResult Index()
  8. {
  9. var types = DataSource.GetProductTypes();
  10. return View(types);
  11. }
  12. // other actions will be here
  13. }
  14. }

添加 Index 视图

在 Views\ProductType 文件夹下创建 Index.cshtml:

  1. @model List<ProductManager.Models.ProductType>
  2. <h1>All Product Types</h1>
  3. <table border="1">
  4. <tr>
  5. <th>ID</th>
  6. <th>Name</th>
  7. <th>Product Count</th>
  8. <th>Delete</th>
  9. <th>Edit</th>
  10. <th>Products</th>
  11. </tr>
  12. @foreach (var pt in Model) {
  13. <tr>
  14. <td>@pt.ID</td>
  15. <td>@pt.Name</td>
  16. <td>@pt.Products.Count</td>
  17. <td>
  18. @if (pt.CanBeRemoved) {
  19. @Html.ActionLink("Delete", "Delete", new { id = pt.ID })
  20. } else {
  21. <span>--</span>
  22. }
  23. </td>
  24. <td>@Html.ActionLink("Edit", "Edit", new { id = pt.ID })</td>
  25. <td>@Html.ActionLink("Products", "IndexByTypeID", "Product", new { id = pt.ID })</td>
  26. </tr>
  27. }
  28. </table>
  29. @Html.ActionLink("Add a New Product Type", "Create")

顶部的 @model List <ProductManager.Models.ProductType> 表示该视图的 View Model(视图模型)是 List

运行程序,效果如下:
5.2 实现控制器和视图 - 图2

添加 Create 操作和视图

通常在添加 Index 后,紧跟着添加的就是 Create。这样也方便创建产品类型后在 Index 页面进行查看。

CRUD 里 Create 和 Update 通常都对应一对方法,一个用来页面跳转,一个实际执行操作。

Create Action:

  1. [HttpGet]
  2. public IActionResult Create()
  3. {
  4. return View();
  5. }
  6. [HttpPost]
  7. public IActionResult Create(ProductType productType)
  8. {
  9. if (ModelState.IsValid) {DataSource.AddProductType(productType);}
  10. return RedirectToAction("Index");
  11. }

Create.cshtml:

  1. @model ProductManager.Models.ProductType
  2. <h1>Add a New Product Type</h1>
  3. @using (Html.BeginForm()) {
  4. @Html.LabelFor(pt => pt.Name)
  5. @Html.TextBoxFor(pt=>pt.Name)
  6. <input type="submit" value="Add" />
  7. }
  • Model 属性绑定的类型是 ProductType

  • 使用 Html.BeginForm() 生成了创建产品类型的表单

添加 Delete 操作

Delete 删除 ProductType 后就直接跳转回 Index 页面,所以省了视图。

Delete Action:

  1. public IActionResult Delete(int id)
  2. {
  3. DataSource.RemoveProductTypeByID(id);
  4. return RedirectToAction("Index");
  5. }

添加 Edit 操作和视图

Edit 和 Create 功能非常相似,唯一区别就是编辑时要跟踪记录 ProductType 的 ID。一般通过 <input type="hidden/> 实现。

Edit Action:

  1. [HttpGet]
  2. public IActionResult Edit(int id)
  3. {
  4. var pt = DataSource.GetProductTypeByID(id);
  5. return View(pt);
  6. }
  7. [HttpPost]
  8. public IActionResult Edit(ProductType productType)
  9. {
  10. if (ModelState.IsValid)
  11. {
  12. DataSource.UpdateProductTypeByID(productType.ID, productType);
  13. }
  14. return RedirectToAction("Index");
  15. }

Edit.cshtml:

  1. @model ProductManager.Models.ProductType
  2. <h1>Edit Product Type</h1>
  3. @using (Html.BeginForm()) {
  4. @Html.HiddenFor(pt=>pt.ID)
  5. @Html.LabelFor(pt=>pt.Name)
  6. @Html.TextBoxFor(pt=>pt.Name)
  7. <input type="submit" value="Update" />
  8. }

不难看出 Edit 视图和 Create 相差无几,而实际项目中也经常使用一个视图。

Product 的控制器和视图

上一节中,我们学习了如何创建控制器及其视图 —— 从 Index 的操作和视图开始,然后是 Create 和 Delete,最后是 Edit。现在,让我们继续按照这个顺序创建 Product 的控制器和视图。

Product 的控制器和 Index

Controllers\ProductController:

  1. using Microsoft.AspNetCore.Mvc;
  2. using ProductManager.Models;
  3. namespace ProductManager.Controllers {
  4. public class ProductController : Controller {
  5. public IActionResult Index() {
  6. ViewData["Title"] = "All Products";
  7. var products = DataSource.GetProducts();
  8. return View(products);
  9. }
  10. public IActionResult IndexByTypeID(int id) {
  11. var pt = DataSource.GetProductTypeByID(id);
  12. ViewData["Title"] = $"Products of {pt.Name}";
  13. var products = DataSource.GetProductsByTypeID(id);
  14. return View("Index", products); // reuse Index view
  15. }
  16. // other actions
  17. }
  18. }

与 ProductTypeController 不同,ProductController 有两个 Index 操作。第一个返回所有产品的列表,第二个按产品类别返回产品列表。回顾 ProductType 的 Index 页面里面的 @Html.ActionLink("Products", "IndexByTypeID", "Product", new { id = pt.ID }),很明显它生成的超链接对应此处的第二个操作。

因为 Index 和 IndexByTypeID 都将显式产品列表,所以它俩可以共享视图文件。

Views\Product\Index.cshtml:

  1. @model IList<ProductManager.Models.Product>
  2. <h1>@ViewData["Title"]</h1>
  3. <table border="1">
  4. <tr>
  5. <th>ID</th>
  6. <th>Name</th>
  7. <th>Type</th>
  8. <th>Price</th>
  9. <th>Delete</th>
  10. <th>Edit</th>
  11. <th>Detail</th>
  12. </tr>
  13. @foreach (var p in Model) {
  14. <tr>
  15. <td>@p.ID</td>
  16. <td>@p.Name</td>
  17. <td>@p.Type.Name</td>
  18. <td>@p.Price</td>
  19. <td>@Html.ActionLink("Delete", "Delete", new { id = p.ID })</td>
  20. <td>@Html.ActionLink("Edit", "Edit", new { id = p.ID })</td>
  21. <td>@Html.ActionLink("Detail", "ShowDetail", new { id = p.ID })</td>
  22. </tr>
  23. }
  24. </table>
  25. @Html.ActionLink("Add a New Product", "Create")

Master-Detail 的操作和视图

Web 程序可以通过主从页(master-detail page)来渲染有主从关系的数据。主从页的想法是在 master 页呈现主题的简要信息,并在简要信息中提供 detail 页的入口。

上面的 Index 视图就是 master 页,页面中的 @Html.ActionLink("Detail", "ShowDetail", new { id = p.ID }) 就是 detail 页的入口。

下面是 ShowDetail 操作的代码:

  1. public IActionResult ShowDetail(int id) {
  2. var product = DataSource.GetProductByID(id);
  3. if (product != null)
  4. return View("Detail", product);
  5. else
  6. return RedirectToAction("Index");
  7. }

Views\Product\Detail.cshtml:

  1. @model ProductManager.Models.Product
  2. <h1>Product Detail</h1>
  3. <table border="1">
  4. <tr>
  5. <td>@Html.DisplayNameFor(p => p.ID)</td>
  6. <td>@Html.DisplayFor(p => p.ID)</td>
  7. </tr>
  8. <tr>
  9. <td>@Html.DisplayNameFor(p => p.Name)</td>
  10. <td>@Html.DisplayFor(p => p.Name)</td>
  11. </tr>
  12. <tr>
  13. <td>@Html.DisplayNameFor(p => p.Type)</td>
  14. <td>@Html.ActionLink(Model.Type.Name, "IndexByTypeID", new { id = Model.TypeID })</td>
  15. </tr>
  16. <tr>
  17. <td>@Html.DisplayNameFor(p => p.Price)</td>
  18. <td>@Html.DisplayFor(p => p.Price)</td>
  19. </tr>
  20. </table>

现在来测试 Index 和 Detail 视图。运行程序。导航到 Product 页面然后单击任意一个 Detail 链接,你应该会看到以下页面:
image.png

添加 Create 和 Delete

和 ProductTypeController 代码类似:

  1. [HttpGet]
  2. public IActionResult Create() {
  3. return View();
  4. }
  5. [HttpPost]
  6. public IActionResult Create(Product product) {
  7. if (ModelState.IsValid)
  8. DataSource.AddProduct(product);
  9. return RedirectToAction("Index");
  10. }
  11. public IActionResult Delete(int id) {
  12. DataSource.RemoveProductByID(id);
  13. return RedirectToAction("Index");
  14. }

Views\Product\Create.cshtml:

  1. @using ProductManager.Models
  2. @model Product
  3. <h1>Add a New Product</h1>
  4. @using (Html.BeginForm()) {
  5. <table>
  6. <tr>
  7. <td>@Html.LabelFor(p => p.Name)</td>
  8. <td>@Html.TextBoxFor(p => p.Name)</td>
  9. </tr>
  10. <tr>
  11. <td>@Html.LabelFor(p => p.Price)</td>
  12. <td>@Html.TextBoxFor(p => p.Price)</td>
  13. </tr>
  14. <tr>
  15. <td>@Html.LabelFor(p => p.Type)</td>
  16. <td>
  17. @Html.DropDownListFor(p => p.TypeID,
  18. new SelectList(DataSource.GetProductTypes(), "ID", "Name"))
  19. </td>
  20. </tr>
  21. <tr>
  22. <td></td>
  23. <td><input type="submit" value="Add" /></td>
  24. </tr>
  25. </table>
  26. }

大部分代码都很容易理解。需要注意的地方是:

  1. @Html.DropDownListFor(p => p.TypeID,
  2. new SelectList(DataSource.GetProductTypes(), "ID", "Name"))

它将被渲染成:

  1. <select id="TypeID" name="TypeID">
  2. <option value="1">Book</option>
  3. <option value="2">Fruit</option>
  4. </select>

现在来测试 Create 和 Delete。运行程序。创建并删除产品,你应该会看到以下页面:
image.png

添加 Edit 操作和视图

基于 Create 操作和视图,我们可以很快的编写 Edit 的代码:

  1. [HttpGet]
  2. public IActionResult Edit(int id) {
  3. var product = DataSource.GetProductByID(id);
  4. return View(product);
  5. }
  6. [HttpPost]
  7. public IActionResult Edit(Product product) {
  8. if (ModelState.IsValid)
  9. DataSource.UpdateProductByID(product.ID, product);
  10. return RedirectToAction("Index");
  11. }

Views\Product\Edit.cshtml:

  1. @using ProductManager.Models
  2. @model Product
  3. <h1>Edit Product</h1>
  4. @using (Html.BeginForm()) {
  5. @Html.HiddenFor(p => p.ID)
  6. <table>
  7. <tr>
  8. <td>@Html.LabelFor(p => p.Name)</td>
  9. <td>@Html.TextBoxFor(p => p.Name)</td>
  10. </tr>
  11. <tr>
  12. <td>@Html.LabelFor(p => p.Price)</td>
  13. <td>@Html.TextBoxFor(p => p.Price)</td>
  14. </tr>
  15. <tr>
  16. <td>@Html.LabelFor(p => p.Type)</td>
  17. <td>
  18. @Html.DropDownListFor(p => p.TypeID,
  19. new SelectList(DataSource.GetProductTypes(), "ID", "Name"))
  20. </td>
  21. </tr>
  22. <tr>
  23. <td></td>
  24. <td><input type="submit" value="Update" /></td>
  25. </tr>
  26. </table>
  27. }

最后,让我们来测试一下 Edit 功能:
image.png

恭喜你!你实现了一个包含整个课程内容的 ASP.NET Core Web 程序。