[料理佳餚] ASP.NET Core 的虛擬目錄哪去了?

在傳統 ASP.NET 的年代,我們別無選擇,寫好的 ASP.NET 應用程式只能 Host 在 IIS 上執行,其中虛擬目錄的服務是由 StaticFile 這個 HTTP Handler 來負責處理。

而 ASP.NET Core 內建就有 Kestrel 這個輕量化的網頁伺服器,不需要再依賴 IIS,但是脫離 IIS 之後,我們要怎麼設定虛擬目錄?

ASP.NET Core 已經不再依賴 IIS,那麼 IIS 上內建一大堆的 HTTP Handlers 及 HTTP Modules 也跟 ASP.NET Core 沒啥關係了,而這些 HTTP Handlers 及 HTTP Modules 在 ASP.NET Core 統一由 Middleware 來負責。

像是我們需要支援靜態檔案,在 ASP.NET Core 就使用 app.UseStaticFiles() 這個 Middleware,但是預設只對 Web Root(wwwroot)底下的靜態檔案有作用,虛擬目錄它就沒有支援,那怎麼辦? 那就自己寫一個。

我個人覺得,對比開發傳統 ASP.NET 的 HTTP Handler 或 HTTP Module,開發 ASP.NET Core 的 Middleware 真的容易多了,接下來我們就以虛擬目錄為例,使用擴充方法寫一個支援虛擬目錄的 Middleware,原始碼如下,說明就在註解中。

public static class IApplicationBuilderExtension
{
    public static void UseVirtualDirectory(this IApplicationBuilder me, string virtualDirectory, string physicalDirectory)
    {
        me.Use(
            async (ctx, next) =>
                {
                    // 比對 Request Path
                    var match = Regex.Match(ctx.Request.Path.Value, $"^/{virtualDirectory}/(.+)", RegexOptions.IgnoreCase);

                    if (match.Success)
                    {
                        // 用實體目錄路錄建立 FileProvider
                        var fileProvider = new PhysicalFileProvider(physicalDirectory);

                        // 使用相對路徑取得 FileInfo
                        var fileInfo = fileProvider.GetFileInfo(match.Groups[1].Value);

                        if (fileInfo.Exists)
                        {
                            // 取得預設的 ContentType Mappings
                            var contentTypeProvider = new FileExtensionContentTypeProvider();

                            // 依據檔案的副檔名取得 ContentType
                            if (!contentTypeProvider.TryGetContentType(fileInfo.PhysicalPath, out var contentType))
                            {
                                contentType = "application/octet-stream";
                            }

                            ctx.Response.ContentType = contentType;

                            // 回應靜態檔案內容
                            await ctx.Response.SendFileAsync(fileInfo);
                        }
                        else
                        {
                            await next();
                        }
                    }
                    else
                    {
                        await next();
                    }
                });
    }
}

接著在 Startup.cs 的 Configure() 方法中加入 app.UseVirtualDirectory() 方法,並且指定虛擬目錄名稱及對應的實體路徑。

測試結果

除此之外,如果我們有在 ASP.NET Core 應用程式前面擋一層 IIS、Nginx、Apache、...等的網頁伺服器的話,把虛擬目錄設定在這些網頁伺服器上或許會比寫在 ASP.NET Core 更適合,以上分享給大家。

參考資料

相關資源

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