[ASP Net MVC] 調整 MvcSiteMapProvider 快取機制

調整 MvcSiteMapProvider 快取機制

前言

 

近日在使用 MvcSiteMapProvider 建立 Menu 時,驚覺該快取機制跟筆者想像有很大的落差,測試後發現預設的快取只會有一份,注意是所有使用這個網站的人共同使用同一份快取資料;而筆者所進行開發的網站是會因用戶角色權限來產出不同 Menu 選單,因此這樣的快取當然無法滿足筆者需求,所以要來處理一下。

 

 

環境

 

* ASP.NET MVC5

* MvcSiteMapProvider MVC5  V4.6.19

 

image

 

 

實作

 

MvcSiteMapProvider 作者有提供使用者自行擴充快取機制,但是只能使用Dependency Injection(DI)方式來進行,幸好許多知名的DI Framework像是Autofac, Ninject, SimpleInjector, StructureMap, Unity 等都支援,因此擴充性相當高,相對的如果沒有使用過 DI Framework的朋友可能就比較辛苦了。

 

首先來思考一下,筆者對於Menu的需求會因為登入使用者權限而異動,因此快取對象應該是針對登入的每位使用者來做為區別;因此如果將快取的Key值引入登入者Session資訊,我們就即可以此區分不同用戶Menu快取資料,讓每位用戶都享有各自Menu快取功能。所以我們將先實作ISiteMapCacheKeyGenerator介面,可於 GenerateKey方法中自行定義各自用戶的快取Key值,最後只要將此 SessionBasedSiteMapCacheKeyGenerator 類別注入即可。實作代碼如下。

 

public class SessionBasedSiteMapCacheKeyGenerator : ISiteMapCacheKeyGenerator
{
    // fields
    protected readonly IMvcContextFactory mvcContextFactory;

    // constructor
    public SessionBasedSiteMapCacheKeyGenerator(IMvcContextFactory mvcContextFactory)
    {
        if (mvcContextFactory == null)
            throw new ArgumentNullException("mvcContextFactory");
        this.mvcContextFactory = mvcContextFactory;
    }

    // methods - ISiteMapCacheKeyGenerator Members
    public virtual string GenerateKey()
    {
        var context = mvcContextFactory.CreateHttpContext();
        var builder = new StringBuilder();
        builder.Append("sitemap://");
        builder.Append(context.Request.Url.DnsSafeHost);
        builder.Append("/?sessionId=");
        builder.Append(context.Session.SessionID);
        return builder.ToString();
    }
}

 

由於需使用DI方式擴充快取機制,因此除安裝 MvcSiteMapProvider MVC5 外,還要依照專案所使用的DI Framework來下載MvcSiteMapProvider Modules套件,由於筆者使用Unity所以就下載該對應版本即可。

 

image

 

安裝完畢後會發現在Web.config中,已經將MvcSiteMapProvider設定為使用外部DI Container了。

 

image

 

並且多了些檔案,其中 MvcSiteMapProviderContainerExtension 就是作為擴充 DI Container 之用。

 

image

 

接著於MvcSiteMapProviderContainerExtension.cs檔案中加入以下代碼,來註冊先前定義用來引入登入者Session資訊於快取Key值的SessionBasedSiteMapCacheKeyGenerator 類別。註冊完畢後就可以在生成相關實體時,透過Unity自動注入SessionBasedSiteMapCacheKeyGenerator實體,以達到使用session資訊作為快取識別依據效果。

 

this.Container.RegisterType<ISiteMapCacheKeyGenerator, SessionBasedSiteMapCacheKeyGenerator>();

 

image

 

最後直接在 UnityConfig 中加入 Container Extension Module 及設定 Sitemap Loader就大功告成了。

 

image

 

/// <summary>
    /// Specifies the Unity configuration for the main container.
    /// </summary>
    public class UnityConfig
    {
        #region Unity Container
        private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
        {
            var container = new UnityContainer();
            RegisterTypes(container);
            return container;
        });

        public static IUnityContainer GetConfiguredContainer()
        {
            return container.Value;
        }
        #endregion

        public static void RegisterTypes(IUnityContainer container)
        {

            // Add the extension module (required)
            container.AddNewExtension<MvcSiteMapProviderContainerExtension>();

            // Setup global sitemap loader (required)
            MvcSiteMapProvider.SiteMaps.Loader = container.Resolve<ISiteMapLoader>();
        }
    }

 

 

可以自行測試一下,若兩個用戶的Menu可以呈現不同樣式就成功了。

 

 

參考資訊

 

http://www.shiningtreasures.com/post/2013/08/11/mvcsitemapprovider-4-cache-configuration

http://stackoverflow.com/questions/17152115/make-mvc-sitemap-unique-per-session-not-user


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !