[廚餘回收] 在 ASP.NET Core 使用 ViewLocationExpander 實作 Display Mode 要當心 ViewLocation 的 Cache 機制

這件事情是這樣的,在 ASP.NET MVC 有一個 Display Mode 功能,我們公司把它應用在 AWD(Adaptive Web Design) 機制上,雖然在 ASP.NET Core 被拿掉了,但是我們可以實作 IViewLocationExpander 把它給弄回來,某天發現某個 Mobile 網頁的內容套到了 Desktop 版的 Layout,百思不得其解,最後爬了 ASP.NET Core 的原始碼才知道怎麼回事。

我用範例專案來說明,假定 /Home/Index 有 Mobile 版本,而 /Home/Privacy 只有 Desktop 版本,問題發生的時候看起來就像這樣。

在 AWD 的機制下,不同 Device 版本的網頁同時上線是最理想的情況,但是實際情況有可能會讓某個 Device 的版本先上線,像這次某個 Mobile 網頁的內容套到了 Desktop 版 Layout 的情形,就是在 Desktop 版本先上線的情況之下發生的,究其原因就是我們沒有掌握 ASP.NET Core 內部對於 ViewLocation 的 Cache 機制。

ASP.NET Core 內部會將有效的 ViewLocation 轉成 ViewLocationCacheResult 並且 Cache 起來,避免下次還要再查找一次,既然有 Cache 就有 CacheKey,關鍵在於 CacheKey 是如何產生的? ViewLocationCacheKey 的組成條件是 ViewName+ControllerName+AreaName+PageName+IsMainPage+ViewLocationExpanderValues

其中 ViewLocationExpanderValues 就是導致問題發生的主因,Layout 在 Render 的時候,ViewLocationExpanderValues 裡面自訂的 Device 值是 Mobile,但是 Mobile 版本的網頁還沒上線,所以產生 ViewLocation 的邏輯上就不需要將 Mobile 版本 Layout 的路徑加入候選。

乍看之下這個邏輯沒啥問題,不過這個就會造成 Mobile 版本但實際上是 Desktop 版本的 Layout,其 ViewLocationCacheResult 被 Cache 起來,那麼同一個 Area 同一個 Controller 底下的其他 Mobile 版本的 View,套用到 Desktop 版本的 Layout。

正確的做法應該是將 ViewLocationExpanderValues 裡面自訂的 Device 拿掉或是將其值清空,讓組成 ViewLocationCacheKey 的元素是 For Desktop 版本的。

這樣子 ViewLocation 的 Cache 機制就會 Cache 到正確版本的 ViewLocationCacheResult,不會再出現張冠李戴的情形。

相關資源

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