ASP.NET MVC 學習筆記(十四)-利用Attribute設定頁面的Title
前情提要..在MVC要動態設定Title時,很自然地會利用一個ViewData來傳遞
請參考Demo這兩篇文章
這樣的方法也用了一陣子了,但一直覺得下面這樣的寫法不太順眼
把Title設定放在 { } Block區塊中,會讓我覺得這是Action內要執行的一個部分
注意力總是會被分散掉一些些,所以一直希望將他移到Attribute去。
今天來介紹一下如何實作的
最重要的觀念就是MVC在執行Action時,有一個類別是繼承 IActionInvoker
而預設使用的實作類別是這一個
主要的想法就是在執行CreateActionResult的時候,偷偷加一些東西上去
因此我們自訂一個類別CustomCreateActionInvoker,然後繼承ControllerActionInvoker
接著override CreateActionResult這個方法。
繼承ControllerActionInvoker的原因是我們不需要實作太多東西,基本上還是利用Base的Method來執行。
//自訂一個類別繼承ControllerActionInvoker
public class CustomCreateActionInvoker : ControllerActionInvoker
{
protected override ActionResult CreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor, object actionReturnValue)
{
//還是用Base的方法去獲得ActionResult
var result = base.CreateActionResult(controllerContext, actionDescriptor, actionReturnValue);
/*
* 1. 當不是ChildAction時(這邊可以視情況改變)
* 2. 當型態是ViewResult(排除JsonResult、FileResult..等狀況),也可用ViewResultBase
* 才開始抓取Title
*/
if (!controllerContext.IsChildAction && result is ViewResult)
{
var viewResult = result as ViewResult;
if (!HasPageTitleAttribute(viewResult, actionDescriptor))
{
HasPageTitleAttribute(viewResult, controllerContext.Controller.GetType());
}
}
return result;
}
private bool HasPageTitleAttribute(ViewResult viewResult, ICustomAttributeProvider attrubteProvider)
{
//當有抓到自訂的Attribute [PageTitleAttribute] 時,就將它設定到ViewData
var attr = attrubteProvider.GetCustomAttributes(typeof(PageTitleAttribute), true).FirstOrDefault()
as PageTitleAttribute;
if (attr != null)
{
//使用自訂Attribute的GetTitle取得Title
viewResult.ViewData["PageTitle"] = attr.GetTitle(viewResult.ViewData.Model);
return true;
}
return false;
}
}
接著要將原先執行Action使用的ControllerActionInvoker類別換成我們自己
實作的CustomCreateActionInvoker,在BaseController內替換掉它。
public class BaseController : Controller
{
/// <summary>
/// 還是保留PageTitle的設定,以應付無法處理的情況
/// </summary>
public string PageTitle
{
set
{
ViewData["PageTitle"] = value;
}
}
/// <summary>
/// 將CreateActionInvoker替換成自訂的CustomCreateActionInvoker
/// </summary>
/// <returns></returns>
protected override IActionInvoker CreateActionInvoker()
{
return new CustomCreateActionInvoker();
}
}
自訂的Attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class PageTitleAttribute : Attribute
{
/// <summary> 設定Title </summary>
private string Title { get; set; }
/// <summary> 取得頁面Model內的任一屬性 </summary>
private string PropertyName { get; set; }
/// <summary> Format格式 </summary>
private string Format { get; set; }
private static BindingFlags _bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static;
/// <summary>
/// 直接設定Title
/// </summary>
public PageTitleAttribute(string title)
{
this.Title = title;
}
/// <summary>
/// 取得頁面的Model的屬性值,加上Format設定
/// </summary>
/// <param name="propertyName">屬性名稱</param>
/// <param name="format">Format設定。範例:產品-{0} 介紹</param>
public PageTitleAttribute(string propertyName, string format)
{
this.PropertyName = propertyName;
this.Format = format;
}
/// <summary>
/// 取得Title
/// </summary>
/// <param name="model">頁面的Model</param>
public string GetTitle(object model)
{
if (this.Title != null)
return this.Title;
var title = String.Empty;
if (model != null)
{
var prop = model.GetType().GetProperty(PropertyName, _bindingFlags);
if (prop == null) throw new Exception("找不到屬性[" + PropertyName + "]");
title = Convert.ToString(prop.GetValue(model, null));
}
return String.Format(this.Format, title);
}
}
來看看使用的方式
[PageTitle("文章頁面")]
public class HomeController : BaseController
{
[PageTitle("首頁")]
public ActionResult Index()
{
//取得Action上設定的Title
return View();
}
public ActionResult List()
{
//Children Action不執行取得Title
return View();
}
[PageTitle("Name", "{0} | 文章")]
public ActionResult Detail()
{
//取得Page的Model [Name]屬性的值,加上Format。
//測試資料
DemoClass model = new DemoClass()
{
ID = "1",
Name = "ASP.NET MVC PageTitle設定",
Content = "299"
};
return View(model);
}
public ActionResult About()
{
//沒有設定Title的Action,會去抓Controller設定的Title
//如果此Controller內也沒有設定Title,則會抓BaseController的
return View();
}
public ActionResult Json()
{
//除了ViewResult之外的Result型態都不抓Title
return Json(new { test = "JsonResult不抓取Title" }, JsonRequestBehavior.AllowGet);
}
}
MasterPage的設定
<title><%=ViewData["PageTitle"] %><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
大概是這樣子。
其實還有幾種實作方式,例如在BaseController覆寫OnActionExecuted
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
if (!filterContext.IsChildAction && filterContext.Result is ViewResult)
{
var viewResult = filterContext.Result as ViewResult;
if (!HasPageTitleAttribute(viewResult, filterContext.ActionDescriptor))
{
HasPageTitleAttribute(viewResult, filterContext.Controller.GetType());
}
}
}
或者是自訂一個Filter,然後在Global註冊全域的Filter
(MVC 3才有提供,MVC 2的話就加在BaseController上吧)。
等方法...就看實際的狀況來選擇適合的方式吧。
備註:這篇實作的東西很少,所以使用上可能會不方便,如果有別的需求可以在自己擴展囉。
***範例下載***