APS.NET MVC 小技巧 - 切換 cshtml 是否套用 Layout
7 |
分享最近學到的小技巧,在 ASP.NET MVC Action 透過 return View() 或 return PartialView() 控制是否要顯示 Layout 部分。
直接使用 VS2019 預設的 ASP.NET Core MVC 專案範本當範例,它的 Index.cshtml 長這樣:
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
但實際呈現畫面還有頁首、頁尾(黃底標示處):
原因是在 Views 目錄下有個 _ViewStart.csthml
_ViewStart.cshtml 的內容只有三行,設定 Layout = "_Layout" 指向 Views/Shared/_Layout.cshtml (完整版面設計,包含頁首頁尾):
@{
Layout = "_Layout";
}
_ViewStart.cshtml 會每個完整 View (註:不含 Layout 頁面及 Partial View) 顯示時被執行,方便放入共用邏輯。同時它具有階層性,除了放在 Views 資料夾的根目錄,子資料夾也可以有自己的 _ViewStart.cshtml,只套用隸屬該子資料夾的 View。參考 由於 _ViewStart.cshtml 不會套用在 PartialView 上,利用這個特性可以玩出一些花招。
假設有某個網頁有時需要獨立顯示,有時會被內嵌在其他網頁的區塊中。獨立顯示時需帶出頁首頁尾,被內嵌時不用。要停用 Layout 可以透過 Layout = null 控制,除此之外還有一種簡便做法,以下測試把 Action 的 return View() 改成 return PartialView():
public IActionResult Index()
{
return PartialView();
//return View();
}
如上圖,同樣是 Index.cshtml,return PartialView() 省略執行 _ViewStart.cshtml,故不會套用 Layout = "_Layaout" 只顯示內容。(註:此做法可用於 ASP.NET MVC 3/4/5 及 ASP.NET Core ) 要應用在前述的內嵌時不顯示 Layout 情境,簡單做法是新增 ?embedded=Y 參數,Action 寫成 return Request["embedded"] == "Y" ? PartialView() : View();
。
前篇文章學會用 VS2019 查看 ASP.NET Core 原始碼,不鑽一下原始碼手會癢,練習從原始碼找出 PartialView 會略過 _ViewStart.csthml 的依據:
PartialViewResultExecutor 尋找 View 時,ViewEngine.GetView() isMainPage 參數為 false:
public virtual ViewEngineResult FindView(ActionContext actionContext, PartialViewResult viewResult)
{
//...略
var result = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isMainPage: false);
RazorViewEngine.cs GetView() 將 isMainPage 傳給 LocatePageFromPath() 再傳給 CreateCacheResult(),在 CreateCacheResult 中 isMainPage 決定是否套用 GetViewStartPages():
public ViewEngineResult GetView(string executingFilePath, string viewPath, bool isMainPage)
{
//...略
var cacheResult = LocatePageFromPath(executingFilePath, viewPath, isMainPage);
return CreateViewEngineResult(cacheResult, viewPath);
}
private ViewLocationCacheResult LocatePageFromPath(string executingFilePath, string pagePath, bool isMainPage)
{
//...略
cacheResult = CreateCacheResult(expirationTokens, applicationRelativePath, isMainPage);
//...略
}
internal ViewLocationCacheResult CreateCacheResult(
HashSet<IChangeToken> expirationTokens, string relativePath, bool isMainPage)
{
//...略
var viewStartPages = isMainPage ?
GetViewStartPages(viewDescriptor.RelativePath, expirationTokens) :
Array.Empty<ViewLocationCacheItem>();
//...略
}
完成程式碼追查練習,謎底也揭曉了,我充實而欣慰。唉,程式魔人的快樂,往往就是這麼樸實無華,且枯燥。
Tips of using View() or PartialView() to control whether the .cshtml include the layout page or not.
Comments
# by Eric Tsao
可以再配合著 Request.IsAjaxRequest() 玩出更多花樣唷~~
# by Jeffrey
to Eric Tsao,感謝,再學到一招!
# by Ming-hung Tsao
黑大別客氣,我是看您的部落格長大的XDDD
# by JackyLiang
讚捏,剛好我困惑的地方
# by Alex
謝謝版主 剛好用到
# by linus lin
請問版主 我的問題剛好想法 頁面想吃Layout 已經設定好 @{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; 卻還是吃不到Layout的畫面
# by Jeffrey
to linus lin, 聽描述很難推測問題點,能做成可重現問題的簡單範例專案放上 github 嗎?這樣大家比較能幫忙。