[料理佳餚] ASP.NET 在不修改既有程式及後端資料的情況下統一置換某個特定字串

程式的生命週期往往比我們想像中的長,通常年紀愈大的程式包袱愈重,後面接手的人肩負的壓力也愈重,如果我們知道有一個字串叫 http://www.xxx.com ,現在因為政策的關係必須改成 https://www.xxx.com ,偏偏 http://www.xxx.com 被到處寫死在資料庫及程式原始碼裡面,除了把寫死的那些找出來改之外,我們還可以怎麼做?

在 ASP.NET 有一個古老的東西叫 Response.Filter,下面簡述 ASP.NET 頁面傳到 Client 端的流程。

ASP.NET 頁面透過 HttpWriter 寫到串流物件,最終傳到使用者的電腦上呈現,而我們可以用 Response.Filter 在 HttpWriter 與串流物件中間插入一個我們自訂的串流物件,來攔截頁面內容趁機加工。

建立一個自訂的串流物件

我假設我自訂的串流物件叫 ReplacerStream 繼承 Stream 類別,其中在 Write 方法中將要準備寫入的頁面資料置換掉我們想要的特定字串。

public class ReplacerStream : Stream
{
    private readonly Stream responseFilter;
    private readonly Encoding contentEncoding;

    public ReplacerStream(Stream responseFilter, Encoding contentEncoding)
    {
        this.responseFilter = responseFilter;
        this.contentEncoding = contentEncoding;
    }

    ...省略

    public override void Write(byte[] buffer, int offset, int count)
    {
        var content = this.contentEncoding.GetString(buffer);
        var contentBytes = this.contentEncoding.GetBytes(
            Regex.Replace(content, "http://www.xxx.com", "https://www.xxx.com", RegexOptions.IgnoreCase));

        this.responseFilter.Write(contentBytes, 0, contentBytes.Length);
    }
}

建立一個 HttpModule

自訂串流物件建立好後,接著就要用它來取代 Response.Filter,我們需要在 ASP.NET 管線中的其中一個 Event 去做這件事情,至於要選擇在哪一個 Event 來做還滿重要的,因為當頁面已完成輸出之後 Response.Filter 基本上就沒啥用處了,因此我們必須要在輸出頁面前來替換掉 Response.Filter。

我選擇在 PostRequestHandlerExecute 這個 Event 裡面將 Response.Filter 取代為自訂的串流物件,而且只針對 ContentType 是 text/html 的回應內容。

public class ReplacerModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.PostRequestHandlerExecute += this.OnPostRequestHandlerExecute;
    }

    public void Dispose()
    {
    }

    private void OnPostRequestHandlerExecute(object sender, EventArgs eventArgs)
    {
        var context = ((HttpApplication)sender).Context;

        if (context.Response.ContentType.Equals("text/html"))
        {
            context.Response.Filter = new ReplacerStream(context.Response.Filter, context.Response.ContentEncoding);
        }
    }
}

在 Web.Config 中新增 HttpModule

寫好 HttpModule 後,在 Web.Config 中 <system.webServer>\<modules> 的位置加進去就行了。

原本的 HTML 頁面內容是 http 字串,在經過自訂的 Response.Filter 之後就都被置換成 https 了。

參考資料

 < Source Code >

相關資源

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