欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ASP.NET Web Forms 的 DI 應用範例

程序员文章站 2022-04-24 22:19:20
跟 asp.net mvc 與 web api 比起來,在 web forms 應用程式中使用 dependency injection 要來的麻煩些。這裡用一個範例來說明如何注入相依物件至 web...
跟 asp.net mvc 與 web api 比起來,在 web forms 應用程式中使用 dependency injection 要來的麻煩些。這裡用一個範例來說明如何注入相依物件至 web forms 的 aspx 頁面。

 

使用的開發工具與類別庫:

 

visual studio 2013

.net framework 4.5

unity 3.5.x

 

問題描述

 

基於測試或其他原因,希望 aspx 網頁只依賴特定服務的介面,而不要依賴具象類別。 

 

假設首頁 default.x 需要一個傳回「hello world!」字串的服務,而我們將此服務的介面命名為 ihelloservice。以下為此服務的介面與實作類別:

 

复制代码

public interface ihelloservice

{

    string hello(string name);

}

 

public class helloservice : ihelloservice

{

    public string hello(string name)

    {

        return "hello, " + name;

    }

}

复制代码

 

 

default.aspx 的 code-behind 類別大概會像這樣: 

 

复制代码

public partial class default : system.web.ui.page

{

    public ihelloservice helloservice { get; set; }

 

    protected void page_load(object sender, eventargs e)

    {

        // 在網頁上輸出一段字串訊息。訊息內容由 helloservice 提供。

        response.write(this.helloservice.hello("di in asp.net web forms!"));

    }

}

复制代码

 

 

問題來了:page 物件是由 asp.net web forms 框架所建立的,我們如何從外界動態注入 ihelloservice 物件呢?

 

 

解法

 

一般而言,我們建議儘量採用 constructor injection 來注入相依物件,可是此法很難運用在 web forms 的 page 物件上。一個便宜行事的解法是採用 mark seemann 所說的「私生注入」(bastard injection),像這樣:

 

复制代码

public partial class default : system.web.ui.page

{

    public ihelloservice helloservice { get; set; }

 

    public default()

    {

        // 透過一個共用的 container 物件來解析相依物件。

        this.helloservice = appshared.container.resolve<ihelloservice>();

    }

 

    protected void page_load(object sender, eventargs e)

    {

        // 在網頁上輸出一段字串訊息。訊息內容由 helloservice 提供。

        response.write(this.helloservice.hello("di in asp.net web forms!"));

    }

}

复制代码

 

 

此解法的一個問題是,你必須在每一個 aspx 頁面的 code-behind 類別中引用 di 容器的命名空間,而這樣就變成到處都依賴特定的 di 容器了。我們希望盡可能把呼叫 di 容器的程式碼集中寫在少數幾個地方就好。

 

 

接下來的實作步驟會利用一個 http handler 來攔截 page 物件的建立程序,以便在 page 物件建立完成後,立刻以 property injection 的方式將 page 物件需要的服務給注入進去。

 

實作步驟

 

step 1:建立新專案

 

建立一個新的 asp.net web application 專案,目標平台選擇 .net framework 4.5,專案名稱命名為:webformsdemo。

 

專案範本選擇 empty,然後在 add folder and core references for 項目上勾選「web forms」。

 

專案建立完成後,透過 nuget 管理員加入 unity 套件。

 

step 2:註冊型別

 

在應用程式的「組合根」建立 di 容器並註冊相依型別。這裡選擇在 global_asax.cs 的 application_start 方法中處理這件事:

 

复制代码

public class global : system.web.httpapplication

{

    protected void application_start(object sender, eventargs e)

    {

        var container = new unitycontainer();

        application["container"] = container; // 把容器物件保存在共用變數裡

 

        // 註冊型別

        container.registertype<ihelloservice, helloservice>();

    }

}

复制代码

 

 

step 3:撰寫 http handler

 

 

在專案根目錄下建立一個子目錄:infrastructure,然後在此目錄中加入一個新類別:unitypagehandlerfactory.cs。程式碼:

 

复制代码

public class unitypagehandlerfactory : system.web.ui.pagehandlerfactory

{

    public override ihttphandler gethandler(httpcontext context, string requesttype, string virtualpath, string path)

    {

        page page = base.gethandler(context, requesttype, virtualpath, path) as page;

        if (page != null)

        {

            var container = context.application["container"] as iunitycontainer;

            var properties = getinjectableproperties(page.gettype());

 

            foreach (var prop in properties)

            {

                try

                {

                    var service = container.resolve(prop.propertytype);

                    if (service != null)

                    {

                        prop.setvalue(page, service);

                    }

                }

                catch

                {

                    // 沒辦法解析型別就算了。

                }

            }

        }

        return page;

    }

 

    public static propertyinfo[] getinjectableproperties(type type)

    {

        var props = type.getproperties(bindingflags.public | bindingflags.instance | bindingflags.declaredonly);

        if (props.length == 0)

        {

            // 傳入的型別若是由 aspx 頁面所生成的類別,那就必須取得其父類別(code-behind 類別)的屬性。

            props = type.basetype.getproperties(bindingflags.public | bindingflags.instance | bindingflags.declaredonly);

        }

        return props;

    }       

}

复制代码

程式說明:

 

asp.net web forms 框架會呼叫此 handler 物件的 gethandler 方法來建立 page 物件。

在 gethandler 方法中,先利用父類別來建立 page 物件,然後緊接著進行 property injection 的處理。首先,從 application["container"] 中取出上一個步驟所建立的 di 容器,接著找出目前的 page 物件有宣告哪些公開屬性,然後利用 di 容器來逐一解析各屬性的型別,並將建立的物件指派給屬性。

靜態方法 getinjectableproperties 會找出指定型別所宣告的所有公開屬性,並傳回呼叫端。注意這裡只針對「page 類別本身所宣告的公開屬性」來進行 property injection,這樣就不用花時間在處理由父類別繼承而來的數十個公開屬性。

 

step 4:註冊 http handler

 

在 web.config 中註冊剛才寫好的 http handler:

 

复制代码

<configuration>

  <system.web>

    <compilation debug="true" targetframework="4.5" />

    <httpruntime targetframework="4.5" />

  </system.web>

 

  <system.webserver>

    <handlers>

      <add name="unitypagehandlerfactory" path="*.aspx" verb="*" type="webformsdemo.infrastructure.unitypagehandlerfactory"/>

    </handlers>

  </system.webserver>

</configuration>

复制代码

 

 

基礎建設的部分到此步驟已經完成,接著就是撰寫各個 aspx 頁面。

 

step 5:撰寫測試頁面

 

在專案中加入一個新的 web form,命名為 default.aspx。然後在 code-behind 類別中宣告相依服務的屬性,並且在其他地方呼叫該服務的方法。參考以下範例:

 

复制代码

public partial class default : system.web.ui.page

{

    public ihelloservice helloservice { get; set; }

 

    protected void page_load(object sender, eventargs e)

    {

        // 在網頁上輸出一段字串訊息。訊息內容由 helloservice 提供。

        response.write(this.helloservice.hello("di in asp.net web forms!"));

    }

}

复制代码

 

 

你可以看到,aspx 網頁並不需要引用 unity 容器的命名空間,因為注入相依物件的動作已經由基礎建設預先幫你處理好了。