ASP.NET MVC 技巧 - 利用 Layout Page 與 Controller 繼承簡化程式碼
10 |
分享我 ASP.NET MVC 設計常用的小技巧一則。
假設有個網站版面要求如下,所有 View 上方統一放上黑底標題列,標題列左方為 View 標題,右上角則為使用者帳號及姓名,下方白色區域則為 View 的實際內容:
這類情境很適合用 Layout Page 處理。我們設計 ~/Views/Shared/_Layout.cshtml 如下:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
<style>
html,body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
padding: 0; margin: 0;
}
header {
background-color: #444; color: white; padding: 6px; position: relative;
}
header h3 {
font-size: 20pt; margin: 3px 12px; text-shadow: 1px 1px 1px black;
}
header .user-info {
font-size: 9pt; position: absolute; right: 6px; top: 6px;
}
</style>
</head>
<body>
<header>
<h3>@ViewBag.Title</h3>
<div class="user-info">
@ViewBag.UserName ( @ViewBag.UserId )
</div>
</header>
<div>
@RenderBody()
</div>
</body>
</html>
/Views/Home/Index.cshtml 只需設定 ViewBag.Title 及用 <img> 嵌入圖案:(註:透過 _ViewStart.csthml 指定 Layout = "/Views/Shared/_Layout.cshtml")
@{
ViewBag.Title = "Index";
}
<p style="padding: 12px;">
<img src="~/img/icon.png" />
</p>
HomeController.cs 需提供 ViewBag.UserId 及 ViewBag.UserName 給 _Layout.cshtml。UserId 可透過 Request.LogonUserIdentity.Name 取得,UserName 則是傳入 UserId 向後端查詢對應姓名,邏輯不難,但每個 Controller 都要加入相同邏輯。寫個傳入 Request 解析姓名並注入 ViewBag 共用函式給每個 Controller 呼叫是個方法,然而,使用繼承簡化設計會是更好的主意。
宣告一個自訂的 Controller 基底類別 MyControllerBase.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcWeb.Models
{
public class MyControllerBase : Controller
{
public string UserId => Request.LogonUserIdentity.Name.Split('\\').Last();
public string UserName => BaseDataHelper.GetUserName(UserId);
//... 其他共用邏輯 ...
}
}
接著讓 HomeController 改繼承 MyControllerBase,即可直接由 UserId 及 UserName 屬性取得所需資訊:
using MvcWeb.Models;
using System.Web.Mvc;
namespace MvcWeb.Controllers
{
public class HomeController : MyControllerBase
{
// GET: Home
public ActionResult Index()
{
ViewBag.UserId = base.UserId;
ViewBag.UserName = base.UserName;
return View();
}
}
}
但是,在每個 Controller 裡寫 ViewBag.UserId = base.UserId、ViewBag.UserName = base.UserName 還是有點遜,我們可以善用 Controller 都繼承自 MyControllerBase 這點,直接在 _Layout.cshtml 存取 UserId/UserName,完全省掉在 Controller 設定 ViewBag 的工夫。做法是透過 ViewContext.Controller 存取 Controller 物件,若 Controller 繼承自 MyControllerBase,便將其轉型成 MyControllerBase 取回 UserId / UserName 屬性。程式範例如下:
@using MvcWeb.Models
@{
MyControllerBase controller = ViewContext.Controller as MyControllerBase;
if (controller == null)
{
throw new ApplicationException("Controller 必須繼承自 MyControllerBase.");
}
var userId = controller.UserId;
var userName = controller.UserName;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
<style>
/* 略 */
</style>
</head>
<body>
<header>
<h3>@ViewBag.Title</h3>
<div class="user-info">
@userName ( @userId )
</div>
</header>
<div>
@RenderBody()
</div>
</body>
</html>
如此,Controller 只需繼承 MyControllerBase,_Layout.cshtml 便能直接取回必要的資料,Controller 完全不需沾手 UserId/UserName 這些細節,不但能少寫些程式,還貫徹了觀注點分離(SoC)概念,是我偏好的簡潔優雅寫法之一,推薦給大家。
補充:這個範例的精神在於展示利用繼承共用程式碼,我的實際應用 UserId/UserName 不只用於 View,在各 Controller 另有記錄身分、決定流程、控制權限等用途,故用繼承實作還有其他效益。若只限於 View 應用,亦可考慮使用 HtmlHelper 等技巧;另外,範例只有單純文字顯示,若 UI 結構或邏輯更複雜時,切割成 Partial View 或 ChildAction 較方便管理。
Tips of how to use ASP.NET MVC layout page and inherited controller to build concise view page.
Comments
# by demo
類似的需求我通常都會抽成 ChildAction ,而且抽成 ChildAction 後甚至可以加上快取提高速度 。 在 RazorPage 的話黑大用的這種方法更是直覺(我現在越來越愛 RazorPage)
# by 凱大
我是覺得上方的 bar 與 下方的 page 不應該是緊密連動的 兩者應屬於各自獨立的 application 並且交由背景程式來進行配置 或是 IoC 提供介面來操作 繼承太過於緊密了
# by Jeffrey
to demo, 同意。UI 的複雜度更高時,改用 Partial View 或 ChildAction 較有效率,已補充到本文,感謝提醒。
# by CClemon
想請問黑大,假設如果使用partial view弄在一個view裡面但是兩個使用的model是不相同的,我可以使用何種方法來透過Ajax更新partial veiw的model目前一直想不到方法都是改用vue來處理這個部分。
# by Jeffrey
to CClemon, 「更新 Partial View 的 Model」是指在點選某個按鈕或元素後切換 Partial View 顯示另一個 Model 的內容嗎?
# by CClemon
to 黑大 是的目前想不到如何用model來做處理,都是把資料轉Json再用js產生需要的html,想說使用MVC就準備用razor來處理完
# by Jeffrey
to CClemon,由於 Razor 是伺服器端功能,若要從前端傳入 Model,等於點選網頁元素後要把已轉成 JavaScript 形式的 Model 再傳回後端以 Razor 語法產生 HTML,此時應用獨立 View 而非 Partial View。
# by Lawrence
黑大好,我在 asp.net core mvc 中嘗試使用ViewContext.Controller,不過似乎.net core的ViewContext沒有Controller可以使用,這部分有優雅的解決方案嗎
# by Jeffrey
to Lawrence,查了一下,ASP.NET Core 調整架構後,已無法透過 ViewContext 存取來源 Controller,我想到一個土方法是在後端 ViewBag.Controller = this,CSHTML 端再 MyController controller = [MyController]ViewBag.Controller,不知可行嗎?
# by Lawrence
to 黑大 這個方法後來我使用@User在_Layout樣板中取得帳號的資訊來處理了 試了一陣子發現沒辦法所以改方法,後來才發現黑大有回,找時間來試試黑大提供的方法,感謝黑大