public class StaticFileView : IView
{
public string FileName { get; private set; }
public StaticFileView(string fileName) { FileName = fileName; }
public void Render(ViewContext viewContext, TextWriter writer)
{
byte[] buffer;
using (var fs = new FileStream(FileName, FileMode.Open))
{
buffer = new byte[fs.Length];
fs.Read(buffer, 0, buffer.Length);
}
writer.Write(Encoding.UTF8.GetString(buffer));
}
}
internal class ViewEngineResultCacheKey
{
public string ControllerName { get; private set; }
public string ViewName { get; private set; }
public ViewEngineResultCacheKey(string controllerName, string viewName)
{
ControllerName = controllerName ?? String.Empty;
ViewName = viewName ?? String.Empty;
}
public override int GetHashCode()
=> ControllerName.ToLower().GetHashCode() ^ ViewName.ToLower().GetHashCode();
public override bool Equals(object obj)
{
var key = obj as ViewEngineResultCacheKey;
return key != null && key.GetHashCode() == GetHashCode();
}
}
public class StaticFileViewEngine : IViewEngine
{
private Dictionary<ViewEngineResultCacheKey, ViewEngineResult> _cache = new Dictionary<ViewEngineResultCacheKey, ViewEngineResult>();
private object _sync = new object();
public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
=> FindView(controllerContext, partialViewName, null, useCache);
public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
string controllerName = controllerContext.RouteData.GetRequiredString("controller");
var key = new ViewEngineResultCacheKey(controllerName, viewName);
ViewEngineResult result;
if (!useCache)
{
result = InternalFindView(controllerContext, viewName, controllerName);
_cache[key] = result;
return result;
}
if (_cache.TryGetValue(key, out result)) return result;
lock (_sync)
{
if (_cache.TryGetValue(key, out result)) return result;
result = InternalFindView(controllerContext, viewName, controllerName);
_cache[key] = result;
return result;
}
}
private ViewEngineResult InternalFindView(ControllerContext controllerContext, string viewName, string controllerName)
{
string[] searchLocations = new[]
{
string.Format("~/views/{0}/{1}.shtml", controllerName, viewName),
string.Format("~/views/Shared/{0}.shtml", viewName)
};
string fileName = controllerContext.HttpContext.Request.MapPath(searchLocations[0]);
if (File.Exists(fileName))
return new ViewEngineResult(new StaticFileView(fileName), this);
fileName = string.Format(@"\views\Shared\{0}.shtml", viewName);
if (File.Exists(fileName))
return new ViewEngineResult(new StaticFileView(fileName), this);
return new ViewEngineResult(searchLocations);
}
public void ReleaseView(ControllerContext controllerContext, IView view) { }
}
.shtml。// Global.asax
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new StaticFileViewEngine());
自訂 IViewEngine 只需實作 FindView/FindPartialView/ReleaseView。本例展示靜態檔案視圖引擎完整實作,含快取鍵設計、雙重檢查鎖定快取、慣例搜尋路徑。理解此模式可建構資料庫驅動、雲端儲存等任意來源視圖引擎。