[創意料理] 用 ASP.NET MVC 的 Display Mode 讓 View 依使用者角色來拆分以減少邏輯分支

先前有寫過一篇文章 - 將 ASP.NET MVC 的 View 依使用者角色來拆分可以減少邏輯分支,在留言中 demo 哥有提到用 Display Mode 也可以漂亮地解決,於是我就試著把這樣的需求用 Display Mode 來實作,實作之後我必須說,程式碼真的可以少寫一些。

Display Mode 大部分是被拿來依據使用者的裝置,來輸出合適的頁面,但是它不僅僅能判斷使用者裝置而已,它進行條件判斷的依據是 HttpContextBase,所以我們能從 HttpContextBase 得到的資訊,都可以成為 Display Mode 的判斷條件,User 就是其中一個。

View 的命名改用 xxx.[Role].cshtml 的方式

Display Mode 是用後綴命名的方式來尋找 View 的,照先前文章的例子,View 的命名改成這樣:

跟之前的差別除了命名方式之外,針對 NoLogging 的匿名使用者,建議 View 的命名保留預設與 Action 名稱一樣,因為 Display Mode 是全域的機制,它不專屬於某個 Action 或 Controller,如果我們在這邊為匿名使用者的 View 命名成 Promotion.NoLogging.cshtml,那麼全站其他不需要區分角色的 View 也都要命名成 xxx.NoLogging.cshtml,除非我們在判斷「匿名使用者」的邏輯跟「不需要區分角色」的邏輯不一樣。

實作 IPrincipal 及 IIdentity

我們的這個作法是藉由將已登入的 User 資訊塞給 HttpContextBase.User 屬性,好讓 Display Mode 做條件判斷時可以取得 User 資訊,因此我們的 User 類別必須實作 IPrincipal,還要多建立一個 UserIdentity 類別,實作 IIdentity

public class User : IPrincipal
{
    public User(IIdentity identity, int consumptionAmount)
    {
        this.Identity = identity;
        this.ConsumptionAmount = consumptionAmount;
    }

    public int ConsumptionAmount { get; }

    public string Role
    {
        get
        {
            if (this.ConsumptionAmount < 100000) return "Ordinary";
            if (this.ConsumptionAmount < 200000) return "VIP";
            if (this.ConsumptionAmount < 300000) return "VVIP";

            return "VVVIP";
        }
    }

    public IIdentity Identity { get; }

    public bool IsInRole(string role)
    {
        return this.Role.Equals(role);
    }
}

public class UserIdentity : IIdentity
{
    public UserIdentity(string name, string authenticationType, bool isAuthenticated)
    {
        this.Name = name;
        this.AuthenticationType = authenticationType;
        this.IsAuthenticated = isAuthenticated;
    }

    public string Name { get; }

    public string AuthenticationType { get; }

    public bool IsAuthenticated { get; }
}

這邊有一個屬性要注意的就是 IIdentity.Name,它具有識別的效果,因此它跟我們認知的 User Name 是有差別的,它的意思比較接近 User Id。

註冊 Display Mode

把我們為了角色所建立的 Display Mode 新增進 DisplayModeProvider.Instance.Modes

新增 DisplayRoleView ActionFilter

剛剛有提到 Dispaly Mode 是全域的,所以我必須知道要回傳的頁面需不需要區分角色,而我就以 HttpContextBase.User 是不是 null? 這個條件來區分。

public class DisplayRoleViewAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.HttpContext.User = filterContext.Controller.ViewBag.User;
    }
}

Action 可以少寫一些關於使用者角色的程式碼

原本的 Action 還有寫一些判斷角色、組合 View 名稱的程式碼,現在都不用了,全部交給 Display Mode 處理。

以上用 Display Mode 來實作依據使用者角色回傳不同頁面的方法,提供給各位朋友參考。

參考資料

C# 指南 ASP.NET 教學 ASP.NET MVC 指引
Azure SQL Database 教學 SQL Server 教學 Xamarin.Forms 教學