首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> 關於ASP.NET

掌握 System.Web.Abstractions:打造可測試、可擴充的 ASP.NET MVC 應用

     編輯:關於ASP.NET

ASP.NET 內建 HttpContext、HttpRequest、HttpResponse 皆為具象密封類別,難以 Mock、難以擴充、綁死 Web 宿主環境。.NET 3.5 SP1 引入 System.Web.Abstractions.dll,提供 HttpContextBase、HttpRequestBase 等抽象基類與 HttpContextWrapper 適配器,徹底解耦框架與宿主,是 MVC 可測試性的基石。

核心類別架構

HttpContextBase (abstract)
    ├─ HttpContextWrapper : HttpContextBase  // 適配真實 HttpContext
    └─ MockHttpContext : HttpContextBase     // 測試用實作

HttpRequestBase  → HttpRequestWrapper / MockHttpRequest
HttpResponseBase → HttpResponseWrapper / MockHttpResponse
HttpSessionStateBase → HttpSessionStateWrapper / MockHttpSession
HttpServerUtilityBase → HttpServerUtilityWrapper / MockHttpServer

為何要避免直接使用具象類別?

具象類別缺點抽象基類優勢
無法在單元測試中模擬 輕鬆建立 Mock/Stub 驗證邏輯
綁定 IIS/ASP.NET 管線 可在 Console App、Service、Azure Functions 執行
方法多為 non-virtual,無法覆寫 設計為可覆寫,支援擴充行為
靜態 HttpContext.Current 造成隱性依賴 透過建構子注入 HttpContextBase 明確相依

實戰:Kooboo CMS 替換請求上下文

需求:在路由解析之前修改請求環境(如重寫路徑、注入租戶資訊)。

1. 攔截點分析

UrlRoutingModule.PostResolveRequestCache 為路由解析入口,首先建立 HttpContextWrapper:

private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
{
    var context = new HttpContextWrapper(((HttpApplication)sender).Context);
    this.PostResolveRequestCache(context); // 此處開始路由解析
}

2. 自訂 HttpModule 提前介入

public class ContextReplacementModule : IHttpModule
{
    public void Init(HttpApplication app)
    {
        // 比 UrlRoutingModule 更早:PreRequestHandlerExecute
        app.PreRequestHandlerExecute += OnPreRequestHandlerExecute;
    }

    private void OnPreRequestHandlerExecute(object sender, EventArgs e)
    {
        var app = (HttpApplication)sender;
        var originalContext = app.Context;
        
        // 建立自訂 HttpContextBase 實作
        var customContext = new CustomHttpContext(originalContext);
        
        // 替換內部 Context(需反射或繼承 HttpApplication)
        // 此處示意:實際需透過 HttpApplication.Context 属性設定器
        // 或實作自訂 HttpApplication 類別
    }
}

3. 自訂 HttpContextBase 實作


public class CustomHttpContext : HttpContextBase
{
    private readonly HttpContext _inner;
    private HttpRequestBase _request;
    private HttpResponseBase _response;

    public CustomHttpContext(HttpContext inner) { _inner = inner; }

    public override HttpRequestBase Request 
    { 
        get { return _request ?? (_request = new CustomHttpRequest(_inner.Request)); } 
    }

    public override HttpResponseBase Response 
    { 
        get { return _response ?? (_response = new CustomHttpResponse(_inner.Response)); } 
    }

    // 其他屬性直接委派
    public override IDictionary Items => _inner.Items;
    public override IPrincipal User => _inner.User;
    // ...
}

public class CustomHttpRequest : HttpRequestBase
{
    private readonly HttpRequest _inner;
    public CustomHttpRequest(HttpRequest inner) { _inner = inner; }

    public override string Path 
    { 
        get => _inner.Path; 
        // 可攔截修改:return ModifiedPath; 
    }
    
    public override NameValueCollection QueryString => _inner.QueryString;
    // ...
}

Controller 中正確使用抽象類別


public class ProductController : Controller
{
    // 悪例:直接用 HttpContext.Current
    public ActionResult Bad() 
    {
        var url = HttpContext.Current.Request.Url; // 難測試
        return View();
    }

    // 佳例:使用基類屬性(內部已為 HttpContextBase)
    public ActionResult Good() 
    {
        var url = Request.Url; // Controller.Request 為 HttpRequestBase
        var userAgent = Request.UserAgent;
        return View();
    }

    // 最佳:建構子注入(適用於非 Controller 服務類別)
    public class ProductService
    {
        private readonly HttpContextBase _context;
        public ProductService(HttpContextBase context) { _context = context; }
        
        public string GetClientIp() => _context.Request.UserHostAddress;
    }
}

單元測試範例


[Test]
public void ProductService_GetClientIp_ReturnsCorrectIp()
{
    // Arrange
    var mockContext = new Mock();
    var mockRequest = new Mock();
    mockRequest.Setup(r => r.UserHostAddress).Returns("192.168.1.100");
    mockContext.Setup(c => c.Request).Returns(mockRequest.Object);

    var service = new ProductService(mockContext.Object);

    // Act
    var ip = service.GetClientIp();

    // Assert
    Assert.AreEqual("192.168.1.100", ip);
}

遷移檢查清單

  • [ ] 移除所有 HttpContext.Current 靜態存取
  • [ ] Controller/Service 建構子注入 HttpContextBase / HttpRequestBase
  • [ ] View 中改用 Request、Response、Session 基類屬性
  • [ ] 自訂 HttpModule/Handler 參數改為 HttpContextBase
  • [ ] 測試專案引用 System.Web.Abstractions、Moq

擴充應用:多租戶、本地化、離線模式

  • 多租戶:CustomHttpRequest 解析 Host/Header 注入 TenantId 至 Items
  • 本地化:CustomHttpRequest 覆寫 UserLanguages 依域名/路徑決定文化特性
  • 離線/桌面模式:實作 OfflineHttpContext 讀取本地檔案/資料庫,Controller 零修改即可運行

善用 System.Web.Abstractions,不僅獲得測試能力,更賦予應用程式脫離 Web 宿主、支援多種執行環境的彈性,是現代 ASP.NET 架構師的必修內功。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved