Blazor Server 中忘記 @implements IAsyncDisposable 帶來的隱性災難

| .NET | 3 Reads

有時候,一個小細節可能讓你吃足苦頭 —— 特別是當你在 Blazor Server 中使用 DbContext 等需釋放資源的物件時。


🧠 背景:Blazor Server + DbContext 的常見寫法

在 Blazor Server 專案中,我們經常透過依賴注入使用 IDbContextFactory<T> 建立 DbContext

@inject IDbContextFactory<ApplicationDbContext> DbFactory
@code {
    private ApplicationDbContext context = default!;
    
    protected override async Task OnInitializedAsync()
    {
        context = await DbFactory.CreateDbContextAsync();
        // ...
    }

    public async ValueTask DisposeAsync()
    {
        await context.DisposeAsync();
    }
}

看起來一切都很完美,對吧?但其實……根本沒有任何資源被釋放!


🚨 問題:DisposeAsync() 永遠不會被呼叫?

是的。

如果你沒有在 Razor 頁面最上方加上這一行:

@implements IAsyncDisposable

那麼 Blazor 根本不會識別你有實作 DisposeAsync(),就算你寫了也完全不會執行。

原因在於 Blazor 的元件銷毀邏輯:

Blazor 在處理元件生命周期時,會這樣檢查是否應釋放資源:

if (component is IAsyncDisposable asyncDisposable)
    await asyncDisposable.DisposeAsync();

只有「真正」實作了介面的元件(透過 @implements 宣告)才會符合 is IAsyncDisposable,否則一律跳過。


🔬 實際後果:DbContext 泄漏、連線耗盡、記憶體飆高

如果你像我一樣,在多個頁面中都動態建立了 DbContext,又沒有正確釋放,最終你會看到以下現象:

  • PostgreSQL / MySQL 連線數暴增不回收

  • Memory usage 持續飆升

  • EF Core 查詢變慢甚至 timeout

  • 無法釋放的 Socket 資源導致應用程式崩潰

而罪魁禍首竟然只是你少寫了一行 @implements IAsyncDisposable


✅ 解法:加上這一行,立即見效

在你的 .razor 檔案最上方加入:

@implements IAsyncDisposable

這樣 Blazor 才會正確在元件銷毀時呼叫你定義的 DisposeAsync() 方法。


📦 範例修正前後對比

❌ 錯誤寫法(未釋放資源):

@page "/example"
@inject IDbContextFactory<AppDbContext> DbFactory

@code {
    private AppDbContext context = default!;

    protected override async Task OnInitializedAsync()
    {
        context = await DbFactory.CreateDbContextAsync();
    }

    public async ValueTask DisposeAsync()
    {
        await context.DisposeAsync(); // 永遠不會被呼叫
    }
}

✅ 正確寫法(Blazor 可識別並釋放資源):

@page "/example"
@implements IAsyncDisposable
@inject IDbContextFactory<AppDbContext> DbFactory

@code {
    private AppDbContext context = default!;

    protected override async Task OnInitializedAsync()
    {
        context = await DbFactory.CreateDbContextAsync();
    }

    public async ValueTask DisposeAsync()
    {
        await context.DisposeAsync(); // 現在會被自動呼叫
    }
}

🧩 補充:IDisposable vs IAsyncDisposable

如果你用的是同步釋放方法:

public void Dispose()
{
    context.Dispose();
}

那麼你應改寫為:

@implements IDisposable

但對於 EF Core 的 DbContext建議使用 DisposeAsync()(即 IAsyncDisposable),以支援 async 背景下的連線釋放。


🧼 建議習慣:凡是用 DbContext,就 @implements IAsyncDisposable

無論是:

  • 分類頁面

  • 歸檔頁面

  • 首頁

  • 詳情頁

  • 搜索結果頁

只要有手動建立 DbContext,就應該記得加上 @implements IAsyncDisposable

否則你遲早會在生產環境中因連線、記憶體泄漏等問題付出代價。


📌 總結

問題 解法
DisposeAsync() 沒有生效? 加上 @implements IAsyncDisposable
DbContext 沒有釋放? IDbContextFactory 並在元件釋放時處理
多頁面都需要釋放資源? 每頁都應補上 @implements 宣告
沒補會怎樣? 資源不回收、記憶體爆漲、連線用盡

🔚 結語

這是一個 Blazor Server 特有的坑 —— 小到你可能忽略它,大到會讓整個站台掛掉。

如果你也中招了,希望這篇文章能幫你止血。

This article was last edited at