2017 年 9 月

第 32 卷,第 9 期

本文章是由機器翻譯。

ASP.NET Core - 具有 Razor 頁面之更簡易的 ASP.NET MVC 應用程式

Steve Smith

Razor 頁面是 ASP.NET Core 2.0 中的新功能。它們提供更簡單的方式來組織 ASP.NET Core 應用程式,進而保持實作更接近邏輯和檢視模型檢視的實作程式碼中的程式碼。也提供了更簡單的方式,若要開始開發 ASP.NET Core 應用程式,但這並不表示您應該關閉它們如果您是有經驗的.NET 開發人員。您也可以改善大型且更複雜的 ASP.NET Core 應用程式的組織使用 Razor 頁面。

「 模型檢視控制器 (MVC) 模式是用於自 2009年開發 ASP.NET 應用程式已支援的 Microsoft 成熟 UI 模式。它提供許多優點,可協助達到重要性分離,導致更容易維護的軟體應用程式開發人員。不幸的是,模式在預設專案範本中實作時經常會導致許多檔案和資料夾,可以加入開發人事,特別是做為應用程式成長。在 2016 年 9 月文章,撰寫使用功能配量為其中一個方法,以解決此問題的相關 (msdn.com/magazine/mt763233)。Razor 頁面提供全新和不同的方式,可處理這個相同的問題,特別是針對在概念上以網頁為基礎的案例。您只需要為幾乎靜態檢視或只需要執行後-Redirect-取得簡單表單時,這個方法是特別有用。這些案例會針對 Razor 頁面,可避免大量的 MVC 應用程式所需的慣例成問題。

開始使用 Razor 頁面

若要開始使用 Razor 頁面,您可以在 Visual Studio 中使用 ASP.NET Core 2.0 中,建立新的 ASP.NET Core Web 應用程式,並選取 Razor 頁面範本,如中所示圖 1

Razor 的 ASP.NET Core 2.0 的 Web 應用程式頁面範本

圖 1 ASP.NET Core 2.0 的 Web 應用程式與 Razor 頁面範本

您可以從 dotnet 命令列介面 (CLI) 使用相同的動作來達成:

dotnet new razor

您必須確定您在至少執行 2.0 版的.NET Core SDK。請檢查:

dotnet --version

在任一情況下,如果您檢查專案所產生,您會看到它包含新的資料夾,頁面中所示圖 2

Razor 頁面專案範本的組織

圖 2 Razor 頁面專案範本的組織

值得注意的是缺少從這個範本是通常與 MVC 專案相關聯的兩個資料夾:控制器和檢視。Razor 頁面使用 [Pages] 資料夾,來保留的所有應用程式頁面。您可以隨意使用頁面根資料夾內的資料夾來組織中任何方式最適合您的應用程式頁面。Razor 頁面可讓開發人員與群組在一起的項目一起變更的生產力優勢結合 MVC 模式的程式碼品質功能。

請注意,頁面中第 2 版的 ASP.NET Core MVC 部分。您可以加入頁面的 [支援任何 ASP.NET Core MVC 應用程式就是直接加入 Pages] 資料夾,並將 Razor 頁面檔案加入至這個資料夾。

Razor 頁面搭配使用的資料夾結構依照慣例路由需求。雖然在一般 MVC 應用程式中的預設頁面可以處找到"/,"以及"/ 首頁"和"/ 首頁/索引,「 在應用程式中使用 Razor 頁面的預設索引頁會比對"/"和"/ 編製索引 」。 使用子資料夾,就是據以相符的路由以建立您的應用程式的各區段變得非常直覺。每個資料夾可以有 Index.cshtml 檔案做為其根頁面。

查看個別頁面,您會發現新的頁面指示詞,@page Razor 頁面上所需。這個指示詞必須出現在分頁檔,應該使用.cshtml 延伸模組的第一行。Razor 頁面外觀和行為非常類似 Razor 為基礎的檢視檔案,而且非常簡單的頁面可以包括只 HTML:

@page
<h1>Hello World</h1>

地位 Razor 頁面的位置是在封裝及群組] UI 的詳細資料。Razor 頁面支援內嵌或另一個類別為基礎的頁面模型,這可能表示資料元素的頁面會顯示或操作。它們也支援不需要個別的控制器和動作方法的處理常式。這些功能可大幅減少個別的資料夾和檔案所需的 Web 應用程式上使用指定的頁面數目。圖 3比較一般 MVC 為基礎的方法使用 Razor 頁面方法所需的檔案和資料夾。

MVC 資料夾和檔案與Razor 頁面

圖 3 MVC 資料夾和檔案與Razor 頁面

若要示範 Razor 頁面 ASP.NET Core MVC 應用程式的內容中,我要使用簡單的範例專案。

範例專案

若要模擬具有一些複雜性和某些不同功能區域的專案,我要返回我用我功能配量的發行項的範例。這個範例牽涉到檢視及管理的實體,包括忍和忍者劍以及暗示、 植物和 zombie 的不同種類的數字。假設應用程式隨附的休閒遊戲,並可協助您管理遊戲中建構。使用一般 MVC 組織方式,您很可能就必須持有控制站、 檢視、 viewmodels,以及針對每個這類建構的多個的許多不同的資料夾。使用 Razor 頁面中,您可以建立簡單的資料夾階層架構對應至您的應用程式的 URL 結構。

在此情況下,應用程式有簡單的首頁和四個不同的區段,每個都有各自頁面下的子資料夾。資料夾結構是非常清楚,只要首頁 (Index.cshtml) 和 [Pages] 資料夾的根目錄中的部分支援檔案和其所屬的資料夾中的其他區段為圖 4顯示。

Razor 頁面使用資料夾組織

圖 4 資料夾組織中與 Razor 頁面

簡單的網頁通常不需要個別的頁面模型。例如,忍者劍 /Ninjas/Swords/Index.cshtml 中顯示的清單會直接使用內嵌變數做為圖 5顯示。

圖 5 使用內嵌變數

@page
@{ 
  var swords = new List<string>()
  {
    "Katana",
    "Ninjago"
  };
}
<h2>Ninja Swords</h2>
<ul>
  @foreach (var item in swords)
  {
    <li>@item</li>
  }
</ul>
<a asp-page="/Ninjas/Index">Ninja List</a>

在 Razor 區塊中宣告的變數都在範圍一頁。您會看到如何宣告函式和甚至是類別,透過 @functions 區塊下一節。請注意位於頁面底部的連結中的新 asp 網頁標記協助程式使用。這些標記協助程式參考其路由中的頁面,並支援絕對和相對路徑。在此範例中,"/ 忍/索引 」 無法也可以撰寫為 「../ 編製索引 」 或甚至只"..",並且它會路由至相同的 Index.cshtml Razor 頁面忍資料夾中。您也可以使用 < 表單 > 項目上的 asp 網頁標記協助程式,若要指定表單的目的地。Asp 網頁標記協助程式建置功能強大的 ASP.NET Core 路由支援之上,因為它們可以支援許多 URL 產生案例簡單的相對 Url。

頁面模型

Razor 頁面可支援強型別的頁面模型。您可以指定的模型 Razor 頁面 @model 指示詞 (如同強型別的 MVC 檢視)。中所示,您可以定義在 Razor 頁面檔中,模型圖 6

定義模型的圖 6

@page
@using WithRazorPages.Core.Interfaces;
@using WithRazorPages.Core.Model;
@model IndexModel
@functions
{
  public class IndexModel : PageModel
  {
    private readonly IRepository<Zombie> _zombieRepository;
       
    public IndexModel(IRepository<Zombie> zombieRepository)
    {
      _zombieRepository = zombieRepository;
    }
    // additional code omitted
  }
}

您也可以定義名為 Pagename.cshtml.cs 個別程式碼後置檔案中的頁面模型。在 Visual Studio 中,遵循這個慣例的檔案會連結到其對應的分頁檔,以便輕鬆地巡覽其間。相同 @functions 區塊圖 6 中所示的程式碼無法放入個別檔案中。

有優缺點這兩種方法來儲存的頁面模型。放置 Razor 頁面內的頁面模型邏輯本身會導致較少的檔案,並允許執行階段編譯的彈性,因此讓網頁的邏輯,而不需要完整的應用程式部署的更新。相反地,可能無法探索網頁 Razor 頁面中定義的模型中的編譯錯誤,直到執行階段。Visual Studio 會在開啟的 Razor 檔案中顯示錯誤 (而不需要實際編譯它們)。執行 dotnet 建置命令不會編譯 Razor 頁面,或提供這些檔案中的潛在錯誤的相關資訊。

不同的頁面模型類別會提供稍微好一點重要性分離,因為 Razor 頁面可以專注於範本以顯示資料,純粹離開的個別頁面模型以處理在頁面的資料和對應的處理常式的結構。個別的程式碼後置頁面模型也會受益於編譯時期錯誤檢查,且較容易內嵌頁面模型比單元測試。最後,您可以選擇是否要使用 Razor 頁面中的任何模型、 內嵌模型或個別的頁面模型。

路由的模型繫結和處理常式

兩個重要功能的 MVC 控制器類別中通常出現在要路由傳送,並模型繫結。大部分的 ASP.NET Core MVC 應用程式會使用屬性來定義路由之 HTTP 動詞命令和路由的參數,使用語法如下:

[HttpGet("{id}")]
public Task<IActionResult> GetById(int id)

如先前所述,Razor 頁面的路由路徑是以慣例為基礎,並符合 /Pages 資料夾階層內的頁面的位置。不過,您可以將它們新增至 @page 指示詞支援路由參數。而不是指定使用屬性支援的 HTTP 動詞命令,Razor 頁面會使用遵循命名慣例為 OnVerb,動詞命令所在之 HTTP 動詞命令,例如 Get、 Post 等等的處理常式。Razor 網頁處理常式的運作方式非常類似 MVC 控制器動作,並使用模型繫結來填入會定義任何參數。圖 7顯示範例 Razor 頁面使用路由參數、 相依性插入和處理常式,以顯示記錄的詳細資訊。

圖 7 Details.cshtml—Displaying 詳細資料指定的記錄識別碼

public async Task OnGetAsync()
{
  Ninjas = _ninjaRepository.List()
    .Select(n => new NinjaViewModel { Id = n.Id, Name = n.Name }).ToList();
}

public async Task<IActionResult> OnPostAddAsync()
{
  var entity = new Ninja()
  {
    Name = "Random Ninja"
  };
_  ninjaRepository.Add(entity);

  return RedirectToPage();
}

public async Task<IActionResult> OnPostDeleteAsync(int id)
{
  var entityToDelete = _ninjaRepository.GetById(id);
_ ninjaRepository.Delete(entityToDelete);

  return RedirectToPage();
}
@page "{id:int}"
@using WithRazorPages.Core.Interfaces;
@using WithRazorPages.Core.Model;
@inject IRepository<Ninja> _repository

@functions {
  public Ninja Ninja { get; set; }

  public IActionResult OnGet(int id)
  {
    Ninja = _repository.GetById(id);

    // A void handler (no return) is equivalent to return Page()
    return Page();
  }
}
<h2>Ninja: @Ninja.Name</h2>
<div>
    Id: @Ninja.Id
</div>
<div>
    <a asp-page="..">Ninja List</a>
</div>

網頁可以支援多個處理常式,讓您定義 OnGet、 OnPost 等等。Razor 頁面也會導入新模型繫結的屬性 [BindProperty],也就是在表單上特別有用。您可以套用這個屬性 Razor 頁面 (或不明確的 PageModel) 上的屬性,以選擇加入至頁面的非 GET 要求的資料繫結。這可讓 asp 像標記協助程式-和 asp 驗證的運作與屬性您所指定,並可讓處理常式,以使用繫結屬性,而不需要指定做為方法參數。[BindProperty] 屬性也適用於控制站。

圖 8顯示 Razor 頁面,可讓使用者將新記錄新增至應用程式。

圖 8 New.cshtml—Adds 新的工廠

@page
@using WithRazorPages.Core.Interfaces;
@using WithRazorPages.Core.Model;
@inject IRepository<Plant> _repository

@functions {
  [BindProperty]
  public Plant Plant { get; set; }

  public IActionResult OnPost()
  {
    if(!ModelState.IsValid) return Page();

    _repository.Add(Plant);

    return RedirectToPage("./Index");
  }
}
<h1>New Plant</h1>
<form method="post" class="form-horizontal">
  <div asp-validation-summary="All" class="text-danger"></div>
  <div class="form-group">
    <label asp-for="Plant.Name" class="col-md-2 control-label"></label>
    <div class="col-md-10">
      <input asp-for="Plant.Name" class="form-control" />
      <span asp-validation-for="Plant.Name" class="text-danger"></span>
    </div>
  </div>
  <div class="form-group">
    <div class="col-md-offset-2 col-md-10">
      <button type="submit" class="btn btn-primary">Save</button>
    </div>
  </div>
</form>
<div>
  <a asp-page="./Index">Plant List</a>
</div>

通常會很有一個支援多個作業使用相同的 HTTP 指令動詞的頁面。比方說,在此範例中的主頁面支援清單 (做為預設 GET 行為),實體,以及刪除的項目,或加入新的項目 (POST 要求為兩者) 的能力。Razor 頁面支援此案例中使用具名的處理常式,示圖 9,其中包括名稱,該動詞後 (但之前"Async"後置詞,如果有的話)。與控制器類型,提供的動作結果傳回時,您可以使用的 helper 方法的數字的基底相似 PageModel 基底型別。執行時的更新,例如加入新的記錄,您通常想要將使用者重新導向立即作業之後,如果成功的話。這可免除觸發伺服器的重複呼叫,產生重複的記錄 (或更糟) 中的重新整理瀏覽器的問題。您可以使用 RedirectToPage 不使用引數,將重新導向至目前 Razor 頁面的預設 GET 處理常式。

圖 9 名為處理常式

public async Task OnGetAsync()
{
  Ninjas = _ninjaRepository.List()
    .Select(n => new NinjaViewModel { Id = n.Id, Name = n.Name }).ToList();
}

public async Task<IActionResult> OnPostAddAsync()
{
  var entity = new Ninja()
  {
    Name = "Random Ninja"
  };
_  ninjaRepository.Add(entity);

  return RedirectToPage();
}

public async Task<IActionResult> OnPostDeleteAsync(int id)
{
  var entityToDelete = _ninjaRepository.GetById(id);
_ ninjaRepository.Delete(entityToDelete);

  return RedirectToPage();
}

您可以指定具名的處理常式使用標記 asp 網頁處理常式 helper、 表單、 連結或按鈕套用:

<a asp-page-handler="Handler">Link Text</a>
<button type="submit" asp-page-handler="delete" asp-route-id="@id">Delete</button>

Asp 網頁處理常式標記使用路由來建置 URL。根據預設,會將處理常式名稱以及任何 asp 路由參數的屬性套用為查詢字串值。在先前的程式碼中的 [刪除] 按鈕會產生與下列類似的 URL:

Ninjas?handler=delete&id=1

如果您想讓此處理常式做為 URL 的一部分,您可以指定此行為與 @page 指示詞:

@page "{handler?}/{id?}"

與此路由所指定,會產生的連結的 [刪除] 按鈕:

Ninjas/Delete/1

篩選

篩選是 ASP.NET Core MVC 的另一個功能強大功能 (和其中一個我涵蓋在 2016 年 8 月問題: msdn.microsoft.com/mt767699)。如果您使用網頁模型在不同的檔案,您可以使用屬性型篩選 Razor 網頁,包括篩選條件屬性置於頁面模型類別。否則,您仍然可以指定全域篩選,當您設定 MVC 應用程式。其中一個篩選器的最常見用法是指定應用程式中的授權原則。您可以全域設定資料夾和頁面為基礎的授權原則:

services.AddMvc()
  .AddRazorPagesOptions(options =>
  {
    options.Conventions.AuthorizeFolder("/Account/Manage");
    options.Conventions.AuthorizePage("/Account/Logout");
    options.Conventions.AllowAnonymousToPage("/Account/Login");
  });

您可以使用現有的一種篩選條件的所有 Razor 頁面除了動作篩選條件,只會套用到控制器內的動作方法。Razor 頁面也會導入新的頁面篩選,以 IPageFilter (或 IAsyncPageFilter) 來表示。此篩選器可讓您新增已選取特定的頁面上的處理常式,或之前或之後的處理常式方法執行之後執行的程式碼。第一種方法可以用來變更哪一個處理常式用來處理要求,例如:

public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
  context.HandlerMethod = 
    context.ActionDescriptor.HandlerMethods.First(m => m.Name == "Add");
}

在選取的處理常式之後,就會發生模型繫結。模型繫結之後, 會呼叫任何頁面篩選 OnPageHandlerExecuting 方法。這個方法可以存取和操作處理常式,可用的任何模型繫結的資料,以及最少運算的處理常式的呼叫。之後,執行的處理常式,但結果執行動作之前,然後呼叫 OnPageHandlerExecuted 方法。

就概念而言,頁篩選器是非常類似於執行之前和之後執行動作的動作篩選。

請注意,其中一個篩選,請 ValidateAntiforgeryToken,完全不需要 Razor 頁面。這個篩選器可用來防止跨站台要求偽造 (CSRF 或 XSRF) 攻擊,但此內建保護功能 Razor 頁面自動。

架構模式

Razor 頁面隨附 ASP.NET Core MVC 中,並利用許多的內建 ASP.NET Core MVC 功能,例如路由、 模型繫結和篩選。它們與 Microsoft 隨附在 2010 Web Matrix 「 網頁 」 功能共用某些命名的相似度。不過,雖然網頁主要目標新手 Web 開發人員 (並且小最資深的開發人員感興趣的),Razor 頁面將與易學結合強式架構設計。

在架構上,Razor 頁面未遵循 「 模型檢視控制器 (MVC) 模式,因為它們缺少控制站。相反地,Razor 頁面遵循數個應該熟悉許多原生應用程式開發人員的模型-檢視-ViewModel (MVVM) 模式。您也可以考慮使用 Razor 頁面頁面控制器模式中,為 「 處理特定頁面或在網站上動作要求物件描述 Martin Fowler 範例。該 [object] 可能是網頁本身,或它可能是對應到該頁面上的個別物件 」。 當然,頁面控制器模式也應該熟悉人與 ASP.NET Web Form,因為這是如何原始的 ASP.NET 網頁有效,以及。

不同於 ASP.NET Web Form,Razor 頁面會建立 ASP.NET Core,並支援鬆散耦合、 考量和實心原則區隔。Razor 頁面容易被單元測試 (如果使用不同的 PageModel 類別),而且可以提供乾淨、 可維護企業應用程式的基礎。不要關閉 Razor 頁面寫為只適用於愛好者程式設計人員 」 training wheels 」 功能。讓 Razor 頁面發生嚴重的外觀,並考慮是否 Razor 頁面 (單獨或搭配傳統的控制器和檢視頁面) 可以改善您的 ASP.NET Core 應用程式的設計需要時所要跳之間使用的資料夾數目,從而減少特定功能。

移轉

雖然 Razor 頁面不遵循 MVC 模式,但是它們與現有的 ASP.NET Core MVC 控制器和檢視表的其中一個與其他之間切換通常是非常簡單因此密切相容。若要移轉現有控制器/檢視架構的頁面,以使用 Razor 頁面,請遵循下列步驟:

  1. Razor 檢視將檔案複製到 /Pages 資料夾中的適當位置。
  2. @page 指示詞加入至檢視。如果這是僅限 GET 的檢視,您已完成。
  3. 將名為 viewname.cshtml.cs PageModel 檔案,並將它放在使用 Razor 頁面的資料夾。
  4. 如果檢視有 ViewModel,請將它複製到 PageModel 檔案。
  5. 將複製的檢視從其控制器 PageModel 類別相關聯的任何動作。
  6. 重新命名的動作,使用 Razor 網頁處理常式語法 (例如,"OnGet")。
  7. 檢視的 helper 方法的參考取代為頁面方法。
  8. 將複製的任何建構函式相依性插入程式碼從控制器到 PageModel。
  9. 取代 PageModel [BindProperty] 屬性中的程式碼傳遞至檢視的模型。
  10. 取代接受 [BindProperty] 屬性,以及檢視模型物件的動作方法參數。

構造良好的 MVC 應用程式通常會有不同的檔案,檢視、 控制器、 viewmodels,和繫結模型,通常每個專案中的個別資料夾中。Razor 頁面可讓您合併成幾個連結的檔案,在單一資料夾中,這些概念,同時,仍允許您的程式碼,請依照下列邏輯的重要性分離。

您應該能夠反向上述步驟,將 Razor 頁面實作控制器/檢視架構的方式,在大部分情況下。遵循下列步驟應該適用於大部分的 MVC 為基礎的簡單動作和檢視表。更複雜的應用程式可能需要額外的步驟和疑難排解。

後續步驟

此範例包含四種版本的 NinjaPiratePlantZombie 組合管理] 應用程式,以支援加入和檢視每個資料類型。此範例顯示如何組織應用程式具有數個不同的功能區域,使用傳統的 MVC、 MVC 區域、 MVC 功能配量和 Razor 頁面。瀏覽這些不同的方法,並查看哪些適合您的 ASP.NET Core 應用程式。此範例的更新的來源程式碼將會位於bit.ly/2eJ01cS


Steve Smith 是獨立的訓練、 指導和顧問。他是 14 次 Microsoft MVP 深獲肯定收件者,並與數個 Microsoft 產品小組密切合作。請連絡他在 ardalis.com 或在 Twitter 上: @ardalis如果您的小組會考慮到 ASP.NET Core 移動,或如果您想要採用更好的編碼方式。

非常感謝下列 Microsoft 技術專家檢閱這篇文章:Ryan Nowak


MSDN Magazine 論壇中的這篇文章的討論