譯文,個(gè)人原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處(C# 6 與 .NET Core 1.0 高級(jí)編程 - 41 ASP.NET MVC(下)),不對(duì)的地方歡迎指出與交流。   

章節(jié)出自《Professional C# 6 and .NET Core 1.0》。水平有限,各位閱讀時(shí)仔細(xì)分辨,唯望莫誤人子弟。

附英文版原文:Professional C# 6 and .NET Core 1.0 - Chapter 41 ASP.NET MVC

C# 6 與 .NET Core 1.0 高級(jí)編程 - 41 ASP.NET MVC(上)

C# 6 與 .NET Core 1.0 高級(jí)編程 - 41 ASP.NET MVC(中)

-------------------------

最近兩篇譯文來(lái)得比較遲,前一陣子忙起來(lái)之后忘記了。

由于有點(diǎn)事情,《Professional C# 6 and .NET Core 1.0》第42、43章譯文,是4月中旬之后的事情了。

Enjoy your reading, enjoy your code!

-------------------------

實(shí)現(xiàn)操作過(guò)濾器

ASP.NET MVC在許多領(lǐng)域是可擴(kuò)展的。例如,可以實(shí)現(xiàn)控制器工廠來(lái)搜索和實(shí)例化控制器(接口IControllerFactory)??刂破鲗?shí)現(xiàn) IController 接口。在控制器中查找操作方法可以通過(guò)使用IActionInvoker接口來(lái)解決??梢允褂脧腁ctionMethodSelectorAttribute派生的屬性類(lèi)來(lái)定義允許的HTTP方法。將HTTP請(qǐng)求映射到參數(shù)的模型綁定器可以通過(guò)實(shí)現(xiàn)IModelBinder接口自定義。 “模型綁定器”部分使用FormCollectionModelBinder類(lèi)型??梢允褂脤?shí)現(xiàn)接口 IViewEngine 的不同視圖引擎。本章使用Razor視圖引擎。還可以通過(guò)HTML輔助程序、標(biāo)記助手和操作過(guò)濾器進(jìn)行自定義。大多數(shù)擴(kuò)展點(diǎn)都超出了本書(shū)的范圍,但是操作過(guò)濾器是最經(jīng)常實(shí)現(xiàn)或使用的,因此這里將介紹這些過(guò)濾器。

在執(zhí)行操作之前和之后調(diào)用操作過(guò)濾器。它們被分配給使用屬性的控制器或控制器的動(dòng)作方法。操作過(guò)濾器通過(guò)創(chuàng)建從基類(lèi)ActionFilterAttribute派生的類(lèi)來(lái)實(shí)現(xiàn)。這個(gè)類(lèi)可以覆蓋基類(lèi)成員OnActionExecuting,OnActionExecuted,OnResultExecuting和OnResultExecuted。 OnActionExecuting在調(diào)用action方法之前被調(diào)用,并且當(dāng)action方法被完成時(shí)調(diào)用OnActionExecuted。之后,在返回結(jié)果之前,調(diào)用OnResultExecuting方法,最后調(diào)用OnResultExecuted。

在這些方法中,可以訪(fǎng)問(wèn)Request對(duì)象以檢索調(diào)用者的信息。通過(guò)Request對(duì)象可以根據(jù)瀏覽器決定一些操作,可以訪(fǎng)問(wèn)路由信息,可以動(dòng)態(tài)更改視圖結(jié)果等等。以下代碼片段從路由信息訪(fǎng)問(wèn)變量語(yǔ)言。要將此變量添加到路由,可以如本章前面的“定義路由”部分所述更改路由。通過(guò)在路由信息中添加語(yǔ)言變量,如下代碼片段所示可以使用 RouteData.Values 訪(fǎng)問(wèn)URL提供的值??梢允褂脵z索到的值更改用戶(hù)語(yǔ)言:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public class LanguageAttribute : ActionFilterAttribute
{
  private string _language = null;
  public override void OnActionExecuting(ActionExecutingContext 
filterContext)
  {
    _language = filterContext.RouteData.Values["language"] == null ?
      null : filterContext.RouteData.Values["language"].ToString();
    //…
  }
  public override void OnResultExecuting(ResultExecutingContext 
filterContext) 
  {
  }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

注意 第28章“本地化”解釋了全球化和本地化,設(shè)置文化和其他區(qū)域細(xì)節(jié)。

如以下代碼段所示,創(chuàng)建的操作過(guò)濾器屬性類(lèi)可以將該屬性應(yīng)用于控制器。使用該類(lèi)的屬性,每個(gè)action方法都調(diào)用屬性類(lèi)的成員。另外,也可以將屬性應(yīng)用于操作方法,因此僅當(dāng)調(diào)用操作方法時(shí)才調(diào)用成員:

[Language]
public class HomeController : Controller
{

ActionFilterAttribute實(shí)現(xiàn)幾個(gè)接口:IActionFilter,IAsyncActionFilter,IResultFilter,IAsyncResultFilter,IFilter和 IOrderedFilter。
ASP.NET MVC包括一些預(yù)定義的操作過(guò)濾器,如 請(qǐng)求 HTTPS 的過(guò)濾器,授權(quán)調(diào)用,處理錯(cuò)誤或緩存數(shù)據(jù)。

將在本章后面的“驗(yàn)證和授權(quán)”部分中介紹使用特性Authorize。

創(chuàng)建數(shù)據(jù)驅(qū)動(dòng)的應(yīng)用程序

現(xiàn)在你已經(jīng)閱讀了ASP.NET MVC的所有基礎(chǔ),是時(shí)候來(lái)看一個(gè)使用ADO.NET實(shí)體框架的數(shù)據(jù)驅(qū)動(dòng)的應(yīng)用程序??梢钥吹紸SP.NET MVC結(jié)合數(shù)據(jù)訪(fǎng)問(wèn)提供的功能。

注意 ADO.NET實(shí)體框架在第38章“實(shí)體框架核心”中有詳細(xì)介紹。

示例應(yīng)用程序 MenuPlanner 用于維護(hù)在數(shù)據(jù)庫(kù)中的餐館菜單條目。只有經(jīng)過(guò)身份驗(yàn)證的帳戶(hù)才可以執(zhí)行數(shù)據(jù)庫(kù)條目的維護(hù)。未經(jīng)身份驗(yàn)證的用戶(hù)則可以瀏覽菜單。

該項(xiàng)目是通過(guò)使用 ASP.NET Core 1.0 Web 應(yīng)用程序模板創(chuàng)建的。身份驗(yàn)證使用默認(rèn)選擇的個(gè)人用戶(hù)帳戶(hù)。這個(gè)項(xiàng)目模板為ASP.NET MVC和控制器添加了幾個(gè)文件夾,包括HomeController和AccountController。它還添加了一些腳本庫(kù)。

定義模型

首先在 Models 目錄中定義一個(gè)模型。使用ADO.NET實(shí)體框架創(chuàng)建模型。 MenuCard類(lèi)型定義了一些屬性和與菜單列表的關(guān)系(代碼文件MenuPlanner/Models/MenuCard.cs):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public class MenuCard
{
  public int Id { get; set; }
  [MaxLength(50)]
  public string Name { get; set; }
  public bool Active { get; set; }
  public int Order { get; set; }
  public virtual List<Menu> Menus { get; set; }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

從 MenuCard 引用的菜單類(lèi)型由Menu類(lèi)定義(代碼文件MenuPlanner/Models/Menu.cs):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public class Menu
{
  public int Id { get; set; }
  public string Text { get; set; }
  public decimal Price { get; set; }
  public bool Active { get; set; }
  public int Order { get; set; }
  public string Type { get; set; }
  public DateTime Day { get; set; }
  public int MenuCardId { get; set; }
  public virtual MenuCard MenuCard { get; set; }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

與數(shù)據(jù)庫(kù)的連接,以及 Menu 和 MenuCard 類(lèi)型的集合都由 MenuCardsContext 管理。使用ModelBuilder,上下文指定Menu類(lèi)型的Text屬性不能為null,并且它的最大長(zhǎng)度為50(代碼文件MenuPlanner/Models/MenuCardsContext.cs):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public class MenuCardsContext : DbContext
{
  public DbSet<Menu> Menus { get; set; }
  public DbSet<MenuCard> MenuCards { get; set; }
  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    modelBuilder.Entity<Menu>().Property(p => p.Text)
      .HasMaxLength(50).IsRequired();
    base.OnModelCreating(modelBuilder);
  }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

Web應(yīng)用程序的啟動(dòng)代碼定義了用作數(shù)據(jù)上下文的MenuCardsContext,并從配置文件讀取連接字符串(代碼文件MenuPlanner/Startup.cs):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public IConfiguration Configuration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
  // Add Entity Framework services to the services container.
  services.AddEntityFramework()
          .AddSqlServer()
          .AddDbContext<ApplicationDbContext>(options =>
             options.UseSqlServer(
               Configuration["Data:DefaultConnection:ConnectionString"]))
          .AddDbContext<MenuCardsContext>(options =>
             options.UseSqlServer(
               Configuration["Data:MenuCardConnection:ConnectionString"]));
  // etc.
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

配置文件添加 MenuCardConnection 連接字符串。 該連接字符串引用 Visual Studio 2015 附帶的SQL實(shí)例 。當(dāng)然可以改變這個(gè),也可以添加一個(gè)到SQL Azure 的連接字符串(代碼文件MenuPlanner/appsettings.json):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

{
  "Data": {
    "DefaultConnection": {
      "ConnectionString":"Server=(localdb)\\mssqllocaldb;
        Database=aspnet5-MenuPlanner-4d3d9092-b53f-4162-8627-f360ef6b2aa8;
        Trusted_Connection=True;MultipleActiveResultSets=true"
    },
    "MenuCardConnection": {
      "ConnectionString":"Server=
(localdb)\\mssqllocaldb;Database=MenuCards; 
        Trusted_Connection=True;MultipleActiveResultSets=true"
    }
  },
  // etc.
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

創(chuàng)建數(shù)據(jù)庫(kù)

可以使用Entity Framework命令來(lái)創(chuàng)建用于創(chuàng)建數(shù)據(jù)庫(kù)的代碼。命令行提示符中可以使用.NET核心命令行(CLI)和ef命令創(chuàng)建代碼以自動(dòng)創(chuàng)建數(shù)據(jù)庫(kù)。要使用命令提示符,必須將當(dāng)前文件夾設(shè)置為project.json文件所在的目錄:

>dotnet ef migrations add InitMenuCards --context MenuCardsContext

注意 dotnet工具在第1章“.NET應(yīng)用程序體系結(jié)構(gòu)”和第17章“Visual Studio 2015”中討論。

因?yàn)槎鄠€(gè)數(shù)據(jù)上下文( MenuCardsContext 和 ApplicationDbContext )是通過(guò)項(xiàng)目定義的,所以需要使用--context選項(xiàng)指定數(shù)據(jù)上下文。 ef命令在項(xiàng)目結(jié)構(gòu)創(chuàng)建一個(gè)Migrations文件夾, InitMenuCards類(lèi)中使用Up方法創(chuàng)建數(shù)據(jù)庫(kù)表,使用Down方法再次刪除更改(代碼文件MenuPlanner/Migrations/[date] InitMenuCards.cs):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public partial class InitMenuCards : Migration
{
  public override void Up(MigrationBuilder migrationBuilder)
  {
    migrationBuilder.CreateTable(
      name:"MenuCard",
      columns: table => new
      {
        Id = table.Column<int>(nullable: false)
          .Annotation("SqlServer:ValueGenerationStrategy",
            SqlServerValueGenerationStrategy.IdentityColumn),
        Active = table.Column<bool>(nullable: false),
        Name = table.Column<string>(nullable: true),
        Order = table.Column<int>(nullable: false)
      },
      constraints: table =>
      {
        table.PrimaryKey("PK_MenuCard", x => x.Id);
      });
    migrationBuilder.CreateTable(
      name:"Menu",
      columns: table => new
      {
        Id = table.Column<int>(nullable: false)
          .Annotation("SqlServer:ValueGenerationStrategy", 
            SqlServerValueGenerationStrategy.IdentityColumn),
        Active = table.Column<bool>(nullable: false),
        Day = table.Column<DateTime>(nullable: false),
        MenuCardId = table.Column<int>(nullable: false),
        Order = table.Column<int>(nullable: false),
        Price = table.Column<decimal>(nullable: false),
        Text = table.Column<string>(nullable: false),
        Type = table.Column<string>(nullable: true)
      },
      constraints: table =>
      {
        table.PrimaryKey("PK_Menu", x => x.Id);
        table.ForeignKey(
          name:"FK_Menu_MenuCard_MenuCardId",
          column: x => x.MenuCardId,
          principalTable:"MenuCard",
          principalColumn:"Id",
          onDelete: RefeerentialAction.Cascade);
      });
  }
  public override void Down(MigrationBuilder migration)
  {
    migration.DropTable("Menu");
    migration.DropTable("MenuCard");
  }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

現(xiàn)在只需要一些代碼來(lái)啟動(dòng)遷移進(jìn)程,用初始樣本數(shù)據(jù)填充數(shù)據(jù)庫(kù)。 MenuCardDatabaseInitializer 通過(guò)在從 Database 屬性返回的DatabaseFacade對(duì)象上調(diào)用擴(kuò)展方法 MigrateAsync 來(lái)應(yīng)用遷移過(guò)程。這反過(guò)來(lái)檢查與連接字符串相關(guān)聯(lián)的數(shù)據(jù)庫(kù)是否已具有與通過(guò)遷移指定的數(shù)據(jù)庫(kù)相同的版本。如果它不具有相同的版本,則調(diào)用所需的Up方法以獲得相同的版本。除此之外,創(chuàng)建幾個(gè)MenuCard對(duì)象將它們存儲(chǔ)在數(shù)據(jù)庫(kù)中(代碼文件MenuPlanner/Models/MenuCardDatabaseInitializer.cs):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
namespace MenuPlanner.Models
{
  public class MenuCardDatabaseInitializer
  {
    private static bool _databaseChecked = false;
    public MenuCardDatabaseInitializer(MenuCardsContext context)
    {
      _context = context;
    }
    private MenuCardsContext _context;
    public async Task CreateAndSeedDatabaseAsync() 
    {
      if (!_databaseChecked)
      {
        _databaseChecked = true;
        await _context.Database.MigrateAsync();
        if (_context.MenuCards.Count() == 0)
        {
          _context.MenuCards.Add(
            new MenuCard { Name ="Breakfast", Active = true, Order = 1 });
          _context.MenuCards.Add(
            new MenuCard { Name ="Vegetarian", Active = true, Order = 2 });
          _context.MenuCards.Add(
            new MenuCard { Name ="Steaks", Active = true, Order = 3 });
        }
        await _context.SaveChangesAsync();
      }
    }
  }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

隨著數(shù)據(jù)庫(kù)和模型到位,可以創(chuàng)建一個(gè)服務(wù)。

創(chuàng)建服務(wù)

在創(chuàng)建服務(wù)之前,創(chuàng)建接口IMenuCardsService,該接口定義服務(wù)所需的所有方法(代碼文件MenuPlanner/Services/IMenuCardsService.cs):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

using MenuPlanner.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MenuPlanner.Services
{
  public interface IMenuCardsService
  {
    Task AddMenuAsync(Menu menu);
    Task DeleteMenuAsync(int id);
    Task<Menu> GetMenuByIdAsync(int id);
    Task<IEnumerable<Menu>> GetMenusAsync();
    Task<IEnumerable<MenuCard>> GetMenuCardsAsync();
    Task UpdateMenuAsync(Menu menu);
  }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

服務(wù)類(lèi)MenuCardsService實(shí)現(xiàn)了返回菜單和菜單卡的方法,創(chuàng)建、更新和刪除菜單(代碼文件 MenuPlanner/Services/MenuCardsService.cs):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

using MenuPlanner.Models;
using Microsoft.EntityFrameworkCore
using System.Collections.Generic; 
using System.Linq;
using System.Threading.Tasks;
namespace MenuPlanner.Services
{
  public class MenuCardsService : IMenuCardsService
  {
    private MenuCardsContext _menuCardsContext;
    public MenuCardsService(MenuCardsContext menuCardsContext)
    {
      _menuCardsContext = menuCardsContext;
    }
    public async Task<IEnumerable<Menu>> GetMenusAsync()
    {
      await EnsureDatabaseCreated();
      var menus = _menuCardsContext.Menus.Include(m => m.MenuCard);
      return await menus.ToArrayAsync();
    }
    public async Task<IEnumerable<MenuCard>> GetMenuCardsAsync()
    {
      await EnsureDatabaseCreated();
      var menuCards = _menuCardsContext.MenuCards;
      return await menuCards.ToArrayAsync();
    }
    public async Task<Menu> GetMenuByIdAsync(int id)
    {
      return await _menuCardsContext.Menus.SingleOrDefaultAsync(
        m => m.Id == id);
    }
    public async Task AddMenuAsync(Menu menu)
    {
      _menuCardsContext.Menus.Add(menu);
      await _menuCardsContext.SaveChangesAsync();
    }
    public async Task UpdateMenuAsync(Menu menu)
    {
      _menuCardsContext.Entry(menu).State = EntityState.Modified;
      await _menuCardsContext.SaveChangesAsync();
    }
    public async Task DeleteMenuAsync(int id)
    {
      Menu menu = _menuCardsContext.Menus.Single(m => m.Id == id);
      _menuCardsContext.Menus.Remove(menu);
      await _menuCardsContext.SaveChangesAsync();
    }
    private async Task EnsureDatabaseCreated() 
    {
      var init = new MenuCardDatabaseInitializer(_menuCardsContext);
      await init.CreateAndSeedDatabaseAsync();
    }
  }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

要通過(guò)依賴(lài)注入使服務(wù)可用,使用AddScoped方法將服務(wù)注冊(cè)到服務(wù)集合中(代碼文件MenuPlanner/Startup.cs):

public void ConfigureServices(IServiceCollection services)
{
  // etc.
  services.AddScoped<IMenuCardsService, MenuCardsService>();
  // etc.
}

創(chuàng)建控制器

ASP.NET MVC提供了構(gòu)架來(lái)創(chuàng)建直接訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)的控制器??梢酝ㄟ^(guò)在解決方案資源管理器中選擇Controllers文件夾來(lái)執(zhí)行此操作,并從上下文菜單中選擇添加->控制器。將打開(kāi)“添加構(gòu)架”對(duì)話(huà)框。從“添加構(gòu)架”對(duì)話(huà)框中,可以使用Entity Framework選擇“MVC 6控制器”視圖。單擊添加按鈕將打開(kāi)添加控制器對(duì)話(huà)框,如圖41.13所示。該對(duì)話(huà)框可以選擇 Menu 模型類(lèi)和實(shí)體框架數(shù)據(jù)上下文MenuCardsContext,配置生成視圖,并給控制器命名。創(chuàng)建具有視圖的控制器以查看生成的代碼,以及視圖。

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

圖41.13

本書(shū)示例不直接使用來(lái)自控制器的數(shù)據(jù)上下文,而是在其間插入服務(wù)。這樣做提供了更多的靈活性??梢允褂脕?lái)自不同控制器的服務(wù),同時(shí)可以使用來(lái)自諸如ASP.NET Web API之類(lèi)的服務(wù)的服務(wù)。

注意 ASP.NET Web API在第42章討論。

通過(guò)以下示例代碼,ASP.NET MVC控制器通過(guò)構(gòu)造函數(shù)注入注入菜單卡服務(wù)(代碼文件MenuPlanner/Controllers/MenuAdminController.cs):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public class MenuAdminController : Controller
{
  private readonly IMenuCardsService _service;
  public MenuAdminController(IMenuCardsService service)
  {
    _service = service;
  }
  // etc.
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

Index方法是當(dāng)僅使用URL引用控制器而不傳遞操作方法時(shí)調(diào)用的默認(rèn)方法。此處,將創(chuàng)建數(shù)據(jù)庫(kù)中的所有 Menu 項(xiàng),并將其傳遞到 Index 視圖。 Details 方法返回通過(guò)從服務(wù)找到的菜單的Details視圖。注意錯(cuò)誤處理。當(dāng)沒(méi)有ID傳遞給Details方法時(shí),使用來(lái)自基類(lèi)的HttpBadRequest方法返回HTTP Bad Request(400錯(cuò)誤響應(yīng))。當(dāng)在數(shù)據(jù)庫(kù)中找不到菜單ID時(shí),通過(guò)HttpNotFound方法返回HTTP Not Found(404錯(cuò)誤響應(yīng)):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public async Task<IActionResult> Index()
{
  return View(await _service.GetMenusAsync());
}
public async Task<IActionResult> Details(int? id = 0)
{
  if (id == null)
  {
    return HttpBadRequest();
  }
  Menu menu = await _service.GetMenuByIdAsync(id.Value);
  if (menu == null)
  {
    return HttpNotFound();
  }
  return View(menu);
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

當(dāng)用戶(hù)創(chuàng)建新菜單時(shí),在來(lái)自客戶(hù)端的HTTP GET請(qǐng)求之后調(diào)用第一個(gè)Create方法。使用該方法,ViewBag信息將傳遞到視圖。ViewBag包含有關(guān)SelectList中的菜單卡的信息。 SelectList允許用戶(hù)選擇項(xiàng)目。因?yàn)镸enuCard集合被傳遞給SelectList,所以用戶(hù)可以用新創(chuàng)建的菜單選擇菜單卡。

public async Task<IActionResult> Create()
{
  IEnumerable<MenuCard> cards = await _service.GetMenuCardsAsync();
  ViewBag.MenuCardId = new SelectList(cards,"Id","Name");
  return View();
}

注意 要使用SelectList類(lèi)型,必須將NuGet包Microsoft.AspNet.Mvc.ViewFeatures添加到項(xiàng)目。

在用戶(hù)填寫(xiě)表單并將具有新菜單的表單提交給服務(wù)器后,第二個(gè)Create方法從HTTP POST請(qǐng)求中調(diào)用。該方法使用模型綁定將表單數(shù)據(jù)傳遞到Menu對(duì)象,并將Menu對(duì)象添加到數(shù)據(jù)上下文以將新創(chuàng)建的菜單寫(xiě)入數(shù)據(jù)庫(kù):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(
  [Bind("Id","MenuCardId","Text","Price","Active","Order","Type","Day")] 
  Menu menu)
{
  if (ModelState.IsValid)
  {
    await _service.AddMenuAsync(menu);
    return RedirectToAction("Index");
  }
  IEnumerable<MenuCard> cards = await _service.GetMenuCardsAsync();
  ViewBag.MenuCards = new SelectList(cards,"Id","Name");
  return View(menu);
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

要編輯菜單卡,需要定義兩個(gè)名為Edit的操作方法 - 一個(gè)用于GET請(qǐng)求,一個(gè)用于POST請(qǐng)求。第一個(gè)Edit方法返回單個(gè)菜單項(xiàng),第二個(gè)在成功完成模型綁定后調(diào)用服務(wù)的UpdateMenuAsync方法:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public async Task<IActionResult> Edit(int? id)
{
  if (id == null)
  {
    return HttpBadRequest();
  }
  Menu menu = await _service.GetMenuByIdAsync(id.Value);
  if (menu == null)
  {
    return HttpNotFound();
  }
  IEnumerable<MenuCard> cards = await _service.GetMenuCardsAsync();
  ViewBag.MenuCards = new SelectList(cards,"Id","Name", menu.MenuCardId);
  return View(menu);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(
    [Bind("Id","MenuCardId","Text","Price","Order","Type","Day")]
    Menu menu)
{
  if (ModelState.IsValid)
  {
    await _service.UpdateMenuAsync(menu);
    return RedirectToAction("Index");
  }
  IEnumerable<MenuCard> cards = await _service.GetMenuCardsAsync();
  ViewBag.MenuCards = new SelectList(cards,"Id","Name", menu.MenuCardId);
  return View(menu);
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

控制器的最后一部分包括 Delete 方法。因?yàn)閮蓚€(gè)方法都有相同的參數(shù) - C#中這是不允許的,第二個(gè)方法的名稱(chēng)改為DeleteConfirmed。但是,第二個(gè)方法可以從與第一個(gè)Delete方法相同的URL鏈接訪(fǎng)問(wèn),但第二個(gè)方法使用HTTP POST訪(fǎng)問(wèn)而不是使用ActionName特性的GET訪(fǎng)問(wèn)。該方法調(diào)用服務(wù)的DeleteMenuAsync方法:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public async Task<IActionResult> Delete(int? id)
{
  if (id == null)
  {
    return HttpBadRequest();
  }
  Menu menu = await _service.GetMenuByIdAsync(id.Value);
  if (menu == null)
  {
    return HttpNotFound();
  }
  return View(menu);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
  Menu menu = await _service.GetMenuByIdAsync(id);
  await _service.DeleteMenuAsync(menu.Id);
  return RedirectToAction("Index");
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

創(chuàng)建視圖

現(xiàn)在是時(shí)候創(chuàng)建視圖了。視圖在 Views/MenuAdmin 文件夾中創(chuàng)建??梢酝ㄟ^(guò)在解決方案資源管理器中選擇MenuAdmin文件夾來(lái)創(chuàng)建視圖,然后從上下文菜單中選擇添加->視圖。打開(kāi)“添加視圖”對(duì)話(huà)框,如圖41.14所示。對(duì)話(huà)框中可以選擇列表、詳細(xì)信息、創(chuàng)建、編輯、刪除模板,然后相應(yīng)地安排HTML元素。使用此對(duì)話(huà)框選擇的Model類(lèi)指定了視圖基于的模型。
大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

圖41.14

定義HTML表的 Index 視圖具有作為其模型的菜單集合。對(duì)于表的頭元素,帶有標(biāo)記助手asp-for的HTML元素標(biāo)簽用于訪(fǎng)問(wèn)要顯示的屬性名稱(chēng)。為了顯示條目,使用@foreach迭代菜單集合,并且使用輸入元素的Tag Helper訪(fǎng)問(wèn)每個(gè)屬性值。錨元素的標(biāo)記助手會(huì)為“編輯”、“詳細(xì)信息”和“刪除”頁(yè)面創(chuàng)建鏈接(代碼文件MenuPlanner/Views/MenuAdmin/Index.cshtml):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

@model IList<MenuPlanner.Models.Menu>
@{
    ViewBag.Title ="Index";
}
<h2>@ViewBag.Title</h2>
<p>
    <a asp-action="Create">Create New</a>
</p>
@if (Model.Count() > 0)
{
  <table>
    <tr>
      <th>
        <label asp-for="@Model[0].MenuCard.Item"></label>
      </th>
      <th>
        <label asp-for="@Model[0].Text"></label>
      </th>
      <th> 
        <label asp-for="Model[0].Day"></label>
      </th>
    </tr>
</label>