ASP.NET Core MVC 中的檢視
作者:Steve Smith 和 Dave Brock
本文件說明 ASP.NET Core MVC 應用程式中所使用的檢視。 如需 Razor Pages 的相關資訊,請參閱 ASP.NET Core 中的 Razor Pages 簡介。
在模型檢視控制器 (MVC) 模式中,「檢視」會處理應用程式的資料呈現和使用者互動。 檢視是具有內嵌 Razor 標記的 HTML 範本。 Razor 標記是與 HTML 標記互動的程式碼,可以產生傳送至用戶端的網頁。
在 ASP.NET Core MVC 中,檢視是在 Razor 標記中使用 C# 程式設計語言的 .cshtml
檔案。 通常,檢視檔案會分組成針對每個應用程式之控制器而命名的資料夾。 資料夾會儲存在應用程式根目錄的 Views
資料夾中:
Home
控制器是由 Views
資料夾內的 Home
資料夾表示。 Home
資料夾包含 About
、Contact
和 Index
(首頁) 網頁的檢視。 使用者要求這三個網頁中的其中一個時,Home
控制器中的控制器動作可決定使用這三個檢視的哪一個來建置網頁並將其傳回給使用者。
使用配置,提供一致的網頁區段,並減少程式碼重複。 配置通常包含標頭、導覽和功能表項目,以及頁尾。 標頭和頁尾通常會包含許多中繼資料項目以及指令碼和樣式表連結的未定案標記。 配置可協助您避免在檢視中使用此未定案標記。
部分檢視透過管理檢視的可重複使用部分,來減少程式碼重複。 例如,部分檢視可用於數個檢視中出現之部落格網站上的作者自傳。 作者自傳是一般檢視內容,不需要執行程式碼就能產生網頁內容。 檢視單獨透過模型繫結就可以使用作者自傳內容,因此最適合使用這類型內容的部分檢視。
檢視元件與部分檢視類似,可讓您減少重複的程式碼,但它們適用於需要程式碼在伺服器上執行才能轉譯網頁的檢視內容。 轉譯的內容需要資料庫互動時 (例如網站購物車),檢視元件十分有用。 檢視元件不會限制為模型繫結,以產生網頁輸出。
使用檢視的優點
檢視可協助在 MVC 應用程式內建立關注點分離,方法是區隔使用者介面標記與應用程式的其他部分。 遵循 SoC 設計可讓您的應用程式模組化,以提供數個優點:
- 應用程式較容易維護,因為其組織性較佳。 檢視一般會依應用程式功能分組。 這可讓您在處理功能時更輕鬆地找到相關檢視。
- 應用程式的組件是鬆散耦合的。 您可以分別從商務邏輯和資料存取元件來建置和更新應用程式的檢視。 您可以修改應用程式的檢視,而不一定需要更新應用程式的其他部分。
- 因為檢視是個別單位,所以可以更輕鬆地測試應用程式的使用者介面部分。
- 為達到更佳的編排情況,較不希望使用者介面的區段意外地重複。
建立檢視
Views/[ControllerName]
資料夾中會建立控制器特有的檢視。 在控制器間共用的檢視會放在 Views/Shared
資料夾中。 若要建立檢視,請新增檔案,並讓它與建立關聯的控制器動作同名,且副檔名為 .cshtml
。 若要建立與 Home
控制器中 About
動作對應的檢視,請在 Views/Home
資料夾中建立 About.cshtml
檔案:
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"].</h2>
<h3>@ViewData["Message"]</h3>
<p>Use this area to provide additional information.</p>
Razor 標記的開頭為 @
符號。 在大括弧 ({ ... }
) 所設定的 Razor 程式碼區塊內放入 C# 程式碼,即可執行 C# 陳述式。 例如,請參閱上方將 "About" 指派給 ViewData["Title"]
。 只要使用 @
符號參考值,就可以在 HTML 內顯示值。 請參閱上方 <h2>
和 <h3>
項目的內容。
上述檢視內容只是轉譯給使用者之整個網頁的一部分。 有關 rest 的頁面版面配置,還有檢視其他通用層面,都會列入其他檢視檔案中。 若要深入了解,請參閱配置主題。
控制器指定檢視的方式
檢視通常會從動作傳回為 ViewResult,這是 ActionResult 的類型。 動作方法可以直接建立並傳回 ViewResult
,但這通常不會進行。 因為大部分的控制器都是繼承自Controller,所以您只需要使用 View
協助程式方法傳回 ViewResult
:
HomeController.cs
:
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
傳回此動作時,最後一個區段中顯示的 About.cshtml
檢視會轉譯為下列網頁:
View
協助程式方法具有數個多載。 您可以選擇性地指定:
要傳回的明確檢視:
return View("Orders");
要傳遞至檢視的模型:
return View(Orders);
檢視和模型:
return View("Orders", Orders);
檢視探索
動作傳回檢視時,會進行稱為「檢視探索」的程序。 此程序根據檢視名稱來決定使用的檢視檔案。
View
方法的預設行為 (return View();
) 是傳回的檢視與從中呼叫它的動作方法同名。 例如,控制器的 About
ActionResult
方法名稱會用於搜尋名為 About.cshtml
的檢視檔案。 首先,執行階段會在 Views/[ControllerName]
資料夾中尋找檢視。 如果在這裡找不到相符的檢視,則會搜尋檢視的 Shared
資料夾。
如果您使用 return View();
以隱含方式傳回 ViewResult
,或使用 return View("<ViewName>");
將檢視名稱明確地傳遞至 View
方法,則不重要。 在這兩種情況下,檢視探索會依此順序搜尋相符的檢視檔案:
Views/\[ControllerName]/\[ViewName].cshtml
Views/Shared/\[ViewName].cshtml
可以提供檢視檔案路徑,而不是檢視名稱。 如果使用從應用程式根目錄開始的絕對路徑 (選擇性地開始於 "/" 或 "~/"),則必須指定 .cshtml
副檔名:
return View("Views/Home/About.cshtml");
您也可以使用相對路徑來指定不同目錄中的檢視,但沒有 .cshtml
副檔名。 在 HomeController
內,您可以使用相對路徑傳回 Manage
檢視的 Index
檢視:
return View("../Manage/Index");
同樣地,您可以使用 "./" 前置詞來指出目前的控制器特定目錄:
return View("./About");
您可以使用自訂 IViewLocationExpander,來自訂檢視如何位在應用程式內的預設慣例。
檢視探索依賴如何依檔案名稱來尋找檢視檔案。 如果基礎檔案系統區分大小寫,則檢視名稱可能會區分大小寫。 基於作業系統之間的相容性,控制器與動作名稱和建立關聯的檢視資料夾和檔案名稱之間的大小寫必須相符。 如果您遇到使用區分大小寫的檔案系統時找不到檢視檔案的錯誤,請確認所要求檢視檔案與實際檢視檔案名稱之間的大小寫符合。
請遵循最佳做法來組織檢視的檔案結構,以反映控制器、動作與檢視之間的關聯性來進行維護和避免混淆。
將資料傳遞至檢視
使用數種方法將資料傳遞至檢視:
- 強型別資料:viewmodel
- 弱型別資料
ViewBag
強型別資料 (viewmodel)
最穩健的方法是在檢視中指定 model 類型。 此模型通常稱為 viewmodel。 您可以透過動作將 viewmodel 類型執行個體傳遞至檢視。
使用 viewmodel 將資料傳遞至檢視,讓檢視利用「強式」檢查。 強式型別 (或強型別) 代表每個變數與常數都有明確定義的類型 (例如,string
、int
或 DateTime
)。 在編譯時期檢查檢視中所使用之類型的有效性。
Visual Studio 與 Visual Studio Code 會使用稱為 IntelliSense 的功能,列出強型別類別成員。 當您想要查看 viewmodel 的屬性時,請鍵入 viewmodel 的變數名稱,後面接著句號 (.
)。 這有助於更快速地撰寫程式碼,並且減少錯誤。
使用 @model
指示詞來指定模型。 搭配使用模型與 @Model
:
@model WebApplication1.ViewModels.Address
<h2>Contact</h2>
<address>
@Model.Street<br>
@Model.City, @Model.State @Model.PostalCode<br>
<abbr title="Phone">P:</abbr> 425.555.0100
</address>
若要將模型提供給檢視,控制器會將它當成參數傳遞:
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
var viewModel = new Address()
{
Name = "Microsoft",
Street = "One Microsoft Way",
City = "Redmond",
State = "WA",
PostalCode = "98052-6399"
};
return View(viewModel);
}
您可以提供給檢視的模型類型沒有任何限制。 建議您使用定義最少行為或沒有定義行為 (方法) 的簡單的 CLR 物件 (POCO) viewmodel。 通常,viewmodel 類別會儲存在應用程式根目錄的 Models
資料夾或個別 ViewModels
資料夾。 上述範例中所使用的 Address
viewmodel 是儲存至名為 Address.cs
之檔案中的 POCO viewmodel:
namespace WebApplication1.ViewModels
{
public class Address
{
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
}
}
您可以將相同的類別用於 viewmodel 類型和商務模型類型。 不過,使用不同的模型可讓您的檢視因應用程式的商務邏輯和資料存取部分而不同。 模型將模型繫結和驗證用於依使用者傳送至應用程式的資料時,模型與 viewmodel 的區隔也提供安全性優點。
弱型別資料 (ViewData
、[ViewData]
屬性和 ViewBag
)
ViewBag
預設不適用於 Razor Pages PageModel
類別。
除了強型別檢視之外,檢視還可以存取弱型別 (也稱為鬆散型別) 資料集合。 與強式型別不同,「弱式型別」 (或「鬆散型別」) 表示您未明確宣告所使用資料的類型。 您可以使用弱型別資料的集合,對控制器與檢視傳遞將少量的資料進出。
在 ... 之間傳遞資料 | 範例 |
---|---|
控制器和檢視 | 使用資料填入下拉式清單。 |
檢視和配置檢視 | 透過檢視檔案,在配置檢視中設定 <title> 元素內容。 |
部分檢視和檢視 | 一種小工具,可根據使用者所要求的網頁來顯示資料。 |
此集合可以透過控制器和檢視上的 ViewData
或 ViewBag
屬性進行參考。 ViewData
屬性是弱型別物件的字典。 ViewBag
屬性是 ViewData
中提供基礎 ViewData
集合之動態屬性的包裝函式。 注意:ViewData
和 ViewBag
的索引鍵查閱皆不區分大小寫。
ViewData
和 ViewBag
是在執行階段動態解析。 因為它們未提供編譯時間類型檢查,所以兩者通常會比使用 viewmodel 更容易發生錯誤。 因此,有些開發人員會盡量少使用或不使用 ViewData
和 ViewBag
。
ViewData
ViewData
是透過 string
索引鍵存取的 ViewDataDictionary 物件。 字串資料可以直接儲存和使用,而不需要轉換;但必須在擷取其他 ViewData
物件值時將其轉換為特定類型。 您可以使用 ViewData
將資料從控制器傳遞至檢視以及在檢視內傳遞資料,包括部分檢視和配置。
下列範例使用運作中 ViewData
來設定問候語和地址的值:
public IActionResult SomeAction()
{
ViewData["Greeting"] = "Hello";
ViewData["Address"] = new Address()
{
Name = "Steve",
Street = "123 Main St",
City = "Hudson",
State = "OH",
PostalCode = "44236"
};
return View();
}
使用檢視中的資料:
@{
// Since Address isn't a string, it requires a cast.
var address = ViewData["Address"] as Address;
}
@ViewData["Greeting"] World!
<address>
@address.Name<br>
@address.Street<br>
@address.City, @address.State @address.PostalCode
</address>
[ViewData]
屬性
另一個使用 ViewDataDictionary 的方法是 ViewDataAttribute。 控制器或 Razor Page 模型上以 [ViewData]
屬性 (attribute) 標示的屬性 (Property) 會儲存其值並從字典載入。
在下列範例中,包含 Title
屬性的 Home 控制器是以 [ViewData]
標示。 About
方法會設定 [About] 檢視的標題:
public class HomeController : Controller
{
[ViewData]
public string Title { get; set; }
public IActionResult About()
{
Title = "About Us";
ViewData["Message"] = "Your application description page.";
return View();
}
}
在此配置中,標題會從 ViewData 字典中讀取:
<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"] - WebApplication</title>
...
ViewBag
ViewBag
預設不適用於 Razor Pages PageModel
類別。
ViewBag
是 Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.DynamicViewData
物件,可供動態存取 ViewData
中儲存的物件。 ViewBag
的使用更為方便,因為它不需要進行轉換。 下列範例示範如何使用 ViewBag
,而其結果與上方使用 ViewData
相同:
public IActionResult SomeAction()
{
ViewBag.Greeting = "Hello";
ViewBag.Address = new Address()
{
Name = "Steve",
Street = "123 Main St",
City = "Hudson",
State = "OH",
PostalCode = "44236"
};
return View();
}
@ViewBag.Greeting World!
<address>
@ViewBag.Address.Name<br>
@ViewBag.Address.Street<br>
@ViewBag.Address.City, @ViewBag.Address.State @ViewBag.Address.PostalCode
</address>
同時使用 ViewData
和 ViewBag
ViewBag
預設不適用於 Razor Pages PageModel
類別。
因為 ViewData
和 ViewBag
參照相同的基礎 ViewData
集合,所以您可以同時使用 ViewData
和 ViewBag
,並在讀取和寫入值時於其間混合使用和比對。
使用 ViewBag
設定標題,並使用 About.cshtml
檢視頂端的 ViewData
來設定描述:
@{
Layout = "/Views/Shared/_Layout.cshtml";
ViewBag.Title = "About Contoso";
ViewData["Description"] = "Let us tell you about Contoso's philosophy and mission.";
}
讀取屬性,但反向使用 ViewData
和 ViewBag
。 在 _Layout.cshtml
檔案中,使用 ViewData
取得標題,並使用 ViewBag
取得描述:
<!DOCTYPE html>
<html lang="en">
<head>
<title>@ViewData["Title"]</title>
<meta name="description" content="@ViewBag.Description">
...
請記住,字串不要求針對 ViewData
進行轉換。 您可以使用 @ViewData["Title"]
,而不需要轉換。
可以同時使用 ViewData
和 ViewBag
,與混合使用和比對讀取與寫入屬性一樣。 會轉譯下列標記:
<!DOCTYPE html>
<html lang="en">
<head>
<title>About Contoso</title>
<meta name="description" content="Let us tell you about Contoso's philosophy and mission.">
...
ViewData
與 ViewBag
之間的差異摘要
ViewBag
預設不適用於 Razor Pages PageModel
類別。
ViewData
- 衍生自 ViewDataDictionary,因此其具有很實用的字典屬性,例如
ContainsKey
、Add
、Remove
和Clear
。 - 字典中的索引鍵是字串,因此允許空白字元。 範例:
ViewData["Some Key With Whitespace"]
- 在檢視中必須轉換任何
string
以外的類型,才能使用ViewData
。
- 衍生自 ViewDataDictionary,因此其具有很實用的字典屬性,例如
ViewBag
- 衍生自
Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.DynamicViewData
,因此允許使用點標記法 (@ViewBag.SomeKey = <value or object>
) 來建立動態屬性,而不需要轉換。ViewBag
的語法可以更快速地新增至控制器和檢視。 - 檢查 Null 值更簡單。 範例:
@ViewBag.Person?.Name
- 衍生自
使用 ViewData
或 ViewBag
的時機
ViewData
和 ViewBag
是同樣有效的方式,都可以在控制器與檢視之間傳遞少量資料。 根據喜好設定來選擇使用哪一個。 您可以混合使用與比對 ViewData
和 ViewBag
物件;不過,使用一致的方法,較容易讀取和維護程式碼。 這兩種方法是在執行階段動態解析,因此容易導致執行階段錯誤。 有些開發小組會避免使用它們。
動態檢視
如果檢視未使用 @model
來宣告模型類型,但具有傳遞給它們的模型執行個體 (例如,return View(Address);
),則可以動態參考執行個體的屬性:
<address>
@Model.Street<br>
@Model.City, @Model.State @Model.PostalCode<br>
<abbr title="Phone">P:</abbr> 425.555.0100
</address>
此功能提供彈性,但未提供編譯保護或 IntelliSense。 如果屬性不存在,則網頁產生會在執行階段失敗。
其他檢視功能
標籤協助程式可讓您輕鬆地將伺服器端行為新增至現有 HTML 標籤。 使用標籤協助程式就不需要在檢視內撰寫自訂程式碼或協助程式。 標籤協助程式會以屬性形式套用至 HTML 項目,而且無法處理它們的編輯器會忽略它們。 這可讓您在各種工具中編輯和轉譯檢視標記。
許多內建 HTML 協助程式都可以產生自訂 HTML 標記。 檢視元件可以處理更複雜的使用者介面邏輯。 檢視元件所提供的 SoC 與該控制器和檢視所提供的 SoC 相同。 它們不需要動作和檢視來處理一般使用者介面項目所使用的資料。
與許多其他 ASP.NET Core 層面一樣,檢視支援相依性插入,允許將服務插入檢視。
CSS 隔離
將 CSS 樣式隔離至個別頁面、檢視和元件,以減少或避免:
- 相依於可能難以維護的全域樣式。
- 巢狀內容中的樣式衝突。
若要新增頁面或檢視的限定範圍 CSS 檔案,請將 CSS 樣式放在符合 .cshtml
檔案名稱的隨附 .cshtml.css
檔案中。 在下列範例中,Index.cshtml.css
檔案會提供只套用至 Index.cshtml
頁面或檢視的 CSS 樣式。
Pages/Index.cshtml.css
(Razor Pages) 或 Views/Index.cshtml.css
(MVC):
h1 {
color: red;
}
在建置時會發生 CSS 隔離。 架構會重寫 CSS 選取器,以符合應用程式頁面或檢視所轉譯的標記。 重寫的 CSS 樣式會組合並產生做為靜態資產 ({APP ASSEMBLY}.styles.css
)。 預留位置 {APP ASSEMBLY}
是專案的組件名稱。 組合 CSS 樣式的連結會放在應用程式的版面配置中。
在應用程式 Pages/Shared/_Layout.cshtml
(Razor Pages) 的 <head>
內容或 Views/Shared/_Layout.cshtml
(MVC) 中,新增或確認組合 CSS 樣式的連結是否存在:
<link rel="stylesheet" href="~/{APP ASSEMBLY}.styles.css" />
在下列範例中,應用程式的組件名稱為 WebApp
:
<link rel="stylesheet" href="WebApp.styles.css" />
範圍 CSS 檔案中定義的樣式只會套用至相符檔案的轉譯輸出。 在上述範例中,應用程式其他位置所定義的任何 h1
CSS 宣告都不會與 Index
的標題樣式衝突。 CSS 樣式串聯和繼承規則對於限定範圍的 CSS 檔案仍有效。 例如,直接套用至 Index.cshtml
檔案中 <h1>
元素的樣式會覆寫 Index.cshtml.css
中限定範圍 CSS 檔案的樣式。
在組合 CSS 檔案內,每個頁面、檢視或 Razor 元件都會與格式為 b-{STRING}
的範圍識別碼相關聯,其中 {STRING}
預留位置是架構所產生的十個字元字串。 下列範例提供 Razor Pages 應用程式 Index
頁面中上述 <h1>
元素的樣式:
/* /Pages/Index.cshtml.rz.scp.css */
h1[b-3xxtam6d07] {
color: red;
}
在從組合檔案套用 CSS 樣式的 Index
頁面中,範圍識別碼會附加為 HTML 屬性:
<h1 b-3xxtam6d07>
識別碼對於應用程式而言是唯一的。 在建置階段,會使用慣例 {STATIC WEB ASSETS BASE PATH}/Project.lib.scp.css
來建立專案組合,其中預留位置 {STATIC WEB ASSETS BASE PATH}
是靜態 Web 資產基底路徑。
如果使用其他專案 (例如 NuGet 套件或 Razor 類別庫),則組合的檔案:
- 參考使用 CSS 匯入的樣式。
- 不會發佈為取用樣式應用程式的靜態 Web 資產。
CSS 前置處理器支援
CSS 前置處理器可用來利用變數、巢狀、模組、混合和繼承等功能來改善 CSS 開發。 雖然 CSS 隔離原生不支援 Sass 或 Less 等 CSS 前置處理器,但只要架構在建置程序期間重寫 CSS 選取器之前進行前置處理器編譯,整合 CSS 前置處理器就會很順暢。 例如,使用 Visual Studio,在 Visual Studio 工作執行器總管中將現有的前置處理器編譯設定為 [建置之前] 工作。
許多協力廠商 NuGet 套件 (例如 AspNetCore.SassCompiler
) 可以在 CSS 隔離發生之前,先在建置程序開始時編譯 SASS/SCSS 檔案,而且不需要額外的設定。
CSS 隔離設定
CSS 隔離允許某些進階案例的設定,例如當相依於現有工具或工作流程時。
自訂範圍識別碼格式
在本節中,{Pages|Views}
預留位置為 Pages
(適用於 Razor Pages 應用程式) 或為 Views
(適用於 MVC 應用程式)。
根據預設,範圍識別碼會使用 b-{STRING}
格式,其中 {STRING}
預留位置是架構所產生的十個字元字串。 若要自訂範圍識別碼格式,請將專案檔更新為所需的模式:
<ItemGroup>
<None Update="{Pages|Views}/Index.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>
在上述範例中,針對 Index.cshtml.css
產生的 CSS 會將其範圍識別碼從 b-{STRING}
變更為 custom-scope-identifier
。
使用範圍識別碼來達到限定範圍 CSS 檔案的繼承。 在下列專案檔範例中,BaseView.cshtml.css
檔案包含跨檢視的通用樣式。 DerivedView.cshtml.css
檔案會繼承這些樣式。
<ItemGroup>
<None Update="{Pages|Views}/BaseView.cshtml.css" CssScope="custom-scope-identifier" />
<None Update="{Pages|Views}/DerivedView.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>
使用萬用字元 (*
) 運算子,跨多個檔案共用範圍識別碼:
<ItemGroup>
<None Update="{Pages|Views}/*.cshtml.css" CssScope="custom-scope-identifier" />
</ItemGroup>
變更靜態 Web 資產的基底路徑
會在應用程式的根目錄產生限定範圍 CSS 檔案。 在專案檔中,使用 StaticWebAssetBasePath
屬性來變更預設路徑。 下列範例會將限定範圍 CSS 檔案,還有應用程式的 rest 資產放到 _content
路徑上:
<PropertyGroup>
<StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
</PropertyGroup>
停用自動組合
若要退出架構在執行階段發佈及載入限定範圍檔案的方式,請使用 DisableScopedCssBundling
屬性。 使用此屬性時,其他工具或程序會負責從 obj
目錄取得隔離的 CSS 檔案,並在執行階段將其發佈及載入:
<PropertyGroup>
<DisableScopedCssBundling>true</DisableScopedCssBundling>
</PropertyGroup>
Razor 類別庫 (RCL) 支援
當 Razor 類別庫 (RCL) 提供隔離樣式時,<link>
標籤的 href
屬性會指向 {STATIC WEB ASSET BASE PATH}/{PACKAGE ID}.bundle.scp.css
,其中預留位置為:
{STATIC WEB ASSET BASE PATH}
:靜態 Web 資產基底路徑。{PACKAGE ID}
:程式庫的套件識別碼。 如果未在專案檔中指定封裝識別碼,則封裝識別碼預設為專案的組件名稱。
在以下範例中:
- 靜態 Web 資產基底路徑為
_content/ClassLib
。 - 類別庫的組件名稱為
ClassLib
。
Pages/Shared/_Layout.cshtml
(Razor Pages) 或 Views/Shared/_Layout.cshtml
(MVC):
<link href="_content/ClassLib/ClassLib.bundle.scp.css" rel="stylesheet">
如需有關 RCL 的詳細資訊,請參閱下列文件:
如需 Blazor CSS 隔離的相關資訊,請參閱 ASP.NET Core Blazor CSS 隔離。