調整 MvcSiteMapProvider 快取機制
前言
近日在使用 MvcSiteMapProvider 建立 Menu 時,驚覺該快取機制跟筆者想像有很大的落差,測試後發現預設的快取只會有一份,注意是所有使用這個網站的人共同使用同一份快取資料;而筆者所進行開發的網站是會因用戶角色權限來產出不同 Menu 選單,因此這樣的快取當然無法滿足筆者需求,所以要來處理一下。
環境
* ASP.NET MVC5
* MvcSiteMapProvider MVC5 V4.6.19
實作
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所以就下載該對應版本即可。
安裝完畢後會發現在Web.config中,已經將MvcSiteMapProvider設定為使用外部DI Container了。
並且多了些檔案,其中 MvcSiteMapProviderContainerExtension 就是作為擴充 DI Container 之用。
接著於MvcSiteMapProviderContainerExtension.cs檔案中加入以下代碼,來註冊先前定義用來引入登入者Session資訊於快取Key值的SessionBasedSiteMapCacheKeyGenerator 類別。註冊完畢後就可以在生成相關實體時,透過Unity自動注入SessionBasedSiteMapCacheKeyGenerator實體,以達到使用session資訊作為快取識別依據效果。
this.Container.RegisterType<ISiteMapCacheKeyGenerator, SessionBasedSiteMapCacheKeyGenerator>();
最後直接在 UnityConfig 中加入 Container Extension Module 及設定 Sitemap Loader就大功告成了。
/// <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
希望此篇文章可以幫助到需要的人
若內容有誤或有其他建議請不吝留言給筆者喔 !