過去在 IIS 跑 ASP.NET MVC 的同學,前進 ASP.NET Core 後會發現,在網站使用靜態檔案的做法跟以前很不一樣。

在 ASP.NET MVC4/5 專案中,靜態內容與程式檔案、.cshtml 是參雜在一起的,除了依習慣命名的 Content、Views、Models、Controllers 資料夾,我們新增一個 About 目錄放進 index.html,就能用網址 ~/About/index.html 瀏覽它。換句話說,在專案下建立的資料夾都可用來放置靜態檔案(.html、.css、.png、.js),由 IIS 的靜態檔案模組 StaticFileModule 處理。

對映到 ASP.NET Core,Request 是由 Middleware 處理,要順利回轉靜態內容,也需要對映的 Middleware。借用前一篇文章的全新 Empty ASP.NET Core 專案,我們來練習在 ASP.NET Core 設定及使用靜態檔案。

ASP.NET Core Empty 專案預設未支援靜態檔案,要在上面使用 .html、.png 得自己動手設定。ASP.NET Core 慣例上會將靜態檔案放在 wwwroot 目錄,其中再區分 js、css、images 等資料夾。故我先在專案中加入 wwwroot 資料夾,建立 about、css、images、js 等子資料夾,在 images 加入 selfie.jpg,在 about 下新增 index.html 如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>關於我</title>
</head>
<body>
    <img src="../images/selfie.jpg" />
    <div style="font-size: 20pt">
        天才、億萬富翁、花花公子、慈善家。
    </div>
</body>
</html>

下一步要在 Startup.cs Configure() 中宣告使用靜態內容,寫法如下:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseStaticFiles(); //設定使用靜態檔案

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

備妥 wwwroot 資料夾並加入 app.UseStaticFiles(),在瀏覽器輸入 about/index.html 即可檢視靜態網頁:

(提醒:ASP.NET Core 將靜態檔案視為可直接提供客戶端輔助網站運作的資產(Assets),一律都開放匿名存取,不像 IIS 可以針對資料夾或檔案額外設定權限。故若檔案有敏感性需管控存取權限,請不要放進 wwwroot。)

如果要省略 index.html 做到輸入 about/ 檢視 default.html、index.html、default.htm、index.htm 等預設文件,則需再加上 app.UseDefaultFiles()。而依據 Middleware 的執行順序原理,UseDefaultFiles() 必須放在 UseStaticFiles() 之前,先進行 URL 導向(將 /about 轉成 /about/index.html)才能發揮效果。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    //設定預設文件,要在 UseStaticFiles 之前
    app.UseDefaultFiles();
    //設定使用靜態檔案
    app.UseStaticFiles(); 

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

設定後,index.html 可省略,用 /about 即可連上網頁。

但在實務上,我們多半會用 app.UseFileServer() 取代 app.UseDefaultFiles() + app.UseStaticFiles()。UseFileServer 還包含使用網頁瀏覽資料夾檔案的功能(app.UseDirectoryBrowser(),但預設關閉以減少資安風險)。

所以常見的寫法會是這樣:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    //啟用 wwwroot 靜態檔案功能
    app.UseFileServer(); 

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
}

關於靜態檔案,還有一些進階議題,例如:

  • 在 wwwroot 以外的目錄下使用靜態檔案
  • 設定 Cache-Control 等 HTTP Header
  • 啟用目錄瀏覽功能
  • 自訂 MIME Type 對映

以上細節在官方文件都有詳細說明,有需要的同學請自行參考。(不得不說,.NET Core 的官方文件品質真是沒話說,太佛心了。)

最後,結合前一篇講過的 Middleware 原理,來玩個有趣應用。記不記得之前 ASP.NET MVC 時代用 Controller 處理 .png 路由得費一番手腳才不會被當成靜態檔案,在 ASP.NET Core 要實現同樣效果,直覺簡單多了。 我們搶在 app.UseFileServer() 之前,加上一個 Middleware 攔截 /files/*.txt 網址,動態生成文字檔讓使用者下載,就這麼簡單:

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.Use(async (context, next) =>
            {
                var m = Regex.Match(context.Request.Path, "/files/(?<n>[A-Za-z0-9_-]+)\\.txt");
                if (m.Success)
                {
                    context.Response.ContentType = "application/octet-stream";
                    await context.Response.WriteAsync(
$@"FileName={m.Groups["n"].Value}.txt
{DateTime.Now:HHmmss}");
                }
                else
                    await next();
            });


            //啟用 wwwroot 靜態檔案功能
            app.UseFileServer(); 

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
            });
        }

測試成功!

Tutorial of using static files in ASP.NET Core.


Comments

# by 凱大

關於 路徑相關的設定有幾個 1. 實體檔案路徑: wwwroot 其實是 HostingEnvironment中叫做 webroot 可以透過修改此值改變其靜態檔案的路徑 該值設定為 從 content Root 開始算起 content root 是網站程式的 root (但以現在的 aspnetcore來說 這件事情會被跳過) 2. 靜態檔案服務設定: 透過 staticFileOptions設定 如您的文章所描述 3. 靜態檔案服務的prefix route: 透過 IApplicationBuilder 所設定的子路線 4. 特定檔案由特定路由傳送: 如您的文章所描述

# by 凱大

關於 路徑相關的設定有幾個 1. 實體檔案路徑: wwwroot 其實是 HostingEnvironment中叫做 webroot 可以透過修改此值改變其靜態檔案的路徑 該值設定為 從 content Root 開始算起 content root 是網站程式的 root (但以現在的 aspnetcore來說 這件事情會被跳過) 2. 靜態檔案服務設定: 透過 staticFileOptions設定 如您的文章所描述 3. 靜態檔案服務的prefix route: 透過 IApplicationBuilder 所設定的子路線 4. 特定檔案由特定路由傳送: 如您的文章所描述

# by 桂馬

Abount? About

# by Jeffrey

to 桂馬,不意外地我又打錯字了,謝謝指正。

Post a comment