Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Static Files

Overview Questions

  • ASP.NET Core phục vụ static files như thế nào?
  • wwwroot folder là gì và cấu hình ra sao?
  • Làm sao để enable directory browsing?
  • File providers là gì và khi nào cần custom file provider?
  • Cache headers cho static files được cấu hình ra sao?

Serving Static Files

Default Configuration

// Program.cs
var app = builder.Build();

// Enable static files middleware
app.UseStaticFiles();

// Static files được phục vụ từ wwwroot folder
// http://localhost/images/logo.png -> wwwroot/images/logo.png

Custom Static File Location

// Serve từ folder khác
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(builder.Environment.ContentRootPath, "MyStaticFiles")),
    RequestPath = "/static"
});

// http://localhost/static/logo.png -> MyStaticFiles/logo.png

wwwroot Structure

Project/
├── wwwroot/
│   ├── css/
│   │   └── site.css
│   ├── js/
│   │   └── site.js
│   ├── images/
│   │   └── logo.png
│   └── lib/
│       └── bootstrap/
├── Program.cs
└── appsettings.json

HTML Reference

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="/css/site.css" />
</head>
<body>
    <img src="/images/logo.png" alt="Logo" />
    <script src="/js/site.js"></script>
</body>
</html>

Directory Browsing

Enable Directory Browsing

var app = builder.Build();

// Enable directory browsing
app.UseDirectoryBrowser(new DirectoryBrowserOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(builder.Environment.ContentRootPath, "wwwroot")),
    RequestPath = "/files"
});

// Hoặc cho toàn bộ static files
app.UseStaticFiles();
app.UseDirectoryBrowser();

Security Warning

⚠️ Chỉ enable directory browsing cho development hoặc khi cần thiết. Production nên disable để tránh expose file structure.


File Providers

Physical File Provider

// Default - sử dụng PhysicalFileProvider
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(builder.Environment.ContentRootPath, "wwwroot"))
});

Embedded File Provider

// Serve files từ embedded resources
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new EmbeddedFileProvider(
        typeof(Program).Assembly,
        "MyApp.EmbeddedFiles")
});

Composite File Provider

// Kết hợp nhiều file providers
var compositeProvider = new CompositeFileProvider(
    new PhysicalFileProvider(Path.Combine(builder.Environment.ContentRootPath, "wwwroot")),
    new EmbeddedFileProvider(typeof(Program).Assembly, "MyApp.EmbeddedFiles")
);

app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = compositeProvider
});

Content Types

Default Content Types

// ASP.NET Core tự động detect content type từ file extension
// .css -> text/css
// .js  -> application/javascript
// .png -> image/png
// .html -> text/html

Custom Content Types

var provider = new FileExtensionContentTypeProvider();

// Add custom mapping
provider.Mappings[".webp"] = "image/webp";
provider.Mappings[".custom"] = "application/x-custom";

// Remove mapping
provider.Mappings.Remove(".rtf");

app.UseStaticFiles(new StaticFileOptions
{
    ContentTypeProvider = provider
});

Cache Headers

Default Behavior

// Static files middleware tự động thêm Last-Modified header
// Browser sẽ cache và send If-Modified-Since request
// Server trả về 304 Not Modified nếu file không thay đổi

Custom Cache Headers

app.UseStaticFiles(new StaticFileOptions
{
    OnPrepareResponse = ctx =>
    {
        // Cache 1 năm cho versioned files
        const int durationInSeconds = 60 * 60 * 24 * 365;
        ctx.Context.Response.Headers["Cache-Control"] = 
            $"public, max-age={durationInSeconds}";
    }
});

Response Caching Middleware

// Add response caching
app.UseResponseCaching();

app.UseStaticFiles(new StaticFileOptions
{
    OnPrepareResponse = ctx =>
    {
        ctx.Context.Response.Headers["Cache-Control"] = "public, max-age=3600";
        ctx.Context.Response.Headers["Vary"] = "Accept-Encoding";
    }
});

Security Considerations

Block Sensitive Files

// Block access to sensitive file types
app.UseStaticFiles(new StaticFileOptions
{
    ServeUnknownFileTypes = false, // Default: false
    OnPrepareResponse = ctx =>
    {
        var path = ctx.Context.Request.Path.Value;
        if (path.EndsWith(".json") || path.EndsWith(".config"))
        {
            ctx.Context.Response.StatusCode = 403;
        }
    }
});

Best Practices

┌─────────────────────────────────────────────────────────────────┐
│                    STATIC FILE SECURITY                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ✅ DO:                                                         │
│  - Chỉ serve từ wwwroot hoặc folder chỉ định                    │
│  - Disable directory browsing trong production                  │
│  - Set appropriate cache headers                                │
│  - Use CDN for production static files                         │
│                                                                 │
│  ❌ DON'T:                                                      │
│  - Serve từ folders chứa sensitive data                        │
│  - Enable directory browsing không cần thiết                    │
│  - Serve .config, .json, .env files                            │
│  - Forget to validate file uploads                              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Default Files

Serve Default File

// Serve default.html khi access root
app.UseDefaultFiles(new DefaultFilesOptions
{
    DefaultFileNames = new List<string> { "index.html", "default.html" }
});

// Phải đặt TRƯỚC UseStaticFiles
app.UseStaticFiles();

// http://localhost/ -> wwwroot/index.html

UseFileServer (Combined)

// Kết hợp UseDefaultFiles + UseStaticFiles + UseDirectoryBrowser
app.UseFileServer();

// Hoặc với custom options
app.UseFileServer(new FileServerOptions
{
    FileProvider = new PhysicalFileProvider(
        Path.Combine(builder.Environment.ContentRootPath, "wwwroot")),
    EnableDirectoryBrowsing = false
});