VirtualPathProvider 允許從非檔案系統來源 (資料庫、嵌入資源、壓縮檔) 提供虛擬路徑檔案。但 MSDN 限定只能在兩處註冊:App_Code 類別的 AppInitialize 靜態方法、Global.asax Application_Start。本文探討如何從外部 DLL 自動註冊。
// 位置 1: App_Code 任意類別
public class VppRegistrar {
public static void AppInitialize() {
HostingEnvironment.RegisterVirtualPathProvider(new MyVirtualPathProvider());
}
}
// 位置 2: Global.asax
protected void Application_Start() {
HostingEnvironment.RegisterVirtualPathProvider(new MyVirtualPathProvider());
}
AppInitialize 僅掃描 App_Code 目錄下原始碼檔 (.cs/.vb) 編譯產生的類別App_Code 動態編譯範圍內App_Code 有特殊編譯行為:單獨組件、支援多語言、自動觸發 AppInitialize// 外部 DLL 組件層級屬性 (AssemblyInfo.cs)
[assembly: PreApplicationStartMethod(typeof(MyVppModule), "Initialize")]
namespace MyExternalLib {
public static class MyVppModule {
public static void Initialize() {
HostingEnvironment.RegisterVirtualPathProvider(new MyVirtualPathProvider());
}
}
}
web.config 無需額外設定,ASP.NET 4.0+ 啟動時自動掃描所有參考組件的 PreApplicationStartMethod。
// 安裝 NuGet: Install-Package WebActivatorEx
// 外部 DLL
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(MyVppModule), "Start")]
namespace MyExternalLib {
public static class MyVppModule {
public static void Start() {
HostingEnvironment.RegisterVirtualPathProvider(new MyVirtualPathProvider());
}
}
}
WebActivatorEx 透過 HttpModule 在應用程式啟動早期執行註冊方法,廣泛用於套件自動初始化。
public class VppRegistrationModule : IHttpModule {
public void Init(HttpApplication context) {
// 在首次請求前註冊 (需確保早於任何虛擬路徑存取)
HostingEnvironment.RegisterVirtualPathProvider(new MyVirtualPathProvider());
}
public void Dispose() { }
}
web.config 註冊:
<system.webServer>
<modules>
<add name="VppRegistration" type="MyExternalLib.VppRegistrationModule, MyExternalLib" />
</modules>
</system.webServer>
| 方案 | 支援版本 | 優點 | 缺點 |
|---|---|---|---|
| PreApplicationStartMethod | 4.0+ | 官方標準、無額外依賴、最早執行 | 不支援 3.5 |
| WebActivatorEx | 3.5+ | 相容性好、廣泛採用、支援 PostStart/Shutdown | 需額外 NuGet 套件 |
| HttpModule | 2.0+ | 完全控制、無框架限制 | 執行時機較晚、需小心競態 |
public class EmbeddedResourceVpp : VirtualPathProvider {
private readonly Assembly _resourceAssembly;
private readonly string _resourceNamespace;
public EmbeddedResourceVpp(Assembly assembly, string resourceNamespace) {
_resourceAssembly = assembly;
_resourceNamespace = resourceNamespace;
}
public override bool FileExists(string virtualPath) {
string resourceName = GetResourceName(virtualPath);
return _resourceAssembly.GetManifestResourceStream(resourceName) != null
|| base.FileExists(virtualPath);
}
public override VirtualFile GetFile(string virtualPath) {
string resourceName = GetResourceName(virtualPath);
var stream = _resourceAssembly.GetManifestResourceStream(resourceName);
return stream != null ? new EmbeddedVirtualFile(virtualPath, stream)
: base.GetFile(virtualPath);
}
public override CacheDependency GetCacheDependency(string virtualPath,
IEnumerable virtualPathDependencies, DateTime utcStart) {
// 嵌入資源不變動,回傳 null 表示永不過期
return base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
}
private string GetResourceName(string virtualPath) {
// ~/Views/Shared/_Layout.cshtml → MyLib.Views.Shared._Layout.cshtml
return _resourceNamespace + virtualPath.Replace("~/", "").Replace("/", ".");
}
}
public class EmbeddedVirtualFile : VirtualFile {
private readonly Stream _stream;
public EmbeddedVirtualFile(string virtualPath, Stream stream) : base(virtualPath) {
_stream = stream;
}
public override Stream Open() => _stream;
}
參考:透過 DLL 共用 Master Pages。將 .master/.aspx 設為嵌入資源,透過 VPP 提供,多個 MVC 專案引用同一 DLL 即可共用版面。