ASP.NET Web Forms 的 DI 應用範例

跟 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.aspx 需要一个传回“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 容器的命名空间,因为注入相依物件的动作已经由基础建设预先帮你处理好了。

Step 6:執行看看

执行时,浏览器应该会显示一行讯息:「Hello, DI in ASP.NET Web Forms!」

时间: 2024-10-11 01:21:55

ASP.NET Web Forms 的 DI 應用範例的相关文章

ASP.NET Web Forms 4.5的新特性

作者:Parry出处:http://www.cnblogs.com/parry/ 一.强类型数据控件 在出现强类型数据控件前,我们绑定数据控件时,前台一般使用Eval或者DataBinder.Eval(Container.DataItem,"FieldName")的形式. 1 <%# DataBinder.Eval(Container.DataItem,"FieldName") %>2 <%# Eval("FieldName")

ASP.NET Web Forms的改进

虽然ASP.NET Web Forms不是vNext计划的一部分,但它并没有被忽视.作为Visual Studio 2013 Update 2的一部分,它重新开始支持新工具.EF集成和Roslyn. 为什么Web Forms不是ASP.NET vNext的一部分 作为开始,让我们先为这个坏消息做下解释.为了改进性能和跨平台可移植性,ASP.NET vNext正在消除对System.Web的依赖.与OWIN相比,它缓慢而庞大,使测试工作多了不必要的麻烦. 虽然他们已经多次尝试将其分离出来,但Web

Asp.Net Web Forms/MVC/Console App中使用Autofac

本来简单介绍了Autofac在Asp.Net Web Forms中的应用,后来又添加了mvc.控制台应用程序中使用Autofac,详情请看源码. ASP.NET Web Forms使用Autofac,至少需要一下步骤: 1,引用Autofac程序集. 2,添加Autofac Web Modules 到 Web.config. 3,在Global.asax中实现IContainerProviderAccessor接口. 我们创建一个ASP.NET Web Forms项目,命名为WebFormStu

在ASP.NET Web Forms中用System.Web.Optimization取代SquishIt

将一个ASP.NET Web Forms项目从.NET Framework 4.0升级至.NET Framework 4.5之后,发现SquishIt竟然引发了HTTP Error 500.0 - Internal Server Error. SquishIt是一个开源的支持ASP.NET的js/css打包工具,项目地址:https://github.com/jetheredge/SquishIt,出生早于Microsoft ASP.NET Web Optimization Framework(

Using Friendly URLs in ASP.NET Web Forms

Introduction Websites often need to generate SEO friendly URLs. In ASP.NET Web Forms applications, a URL is tied to a physical .aspx file. This default mapping between a URL and physical file makes it difficult for Web Forms applications to generate

在ASP.NET Web Forms中使用页面导出伪xls Excel表格

将数据导出为Excel表格是比较常见的需求,也有很多组件支持导出真正的Excel表格.由于Excel能打开HTML文件,并支持其中的table元素以及p之类的文本元素的显示,所以把.html扩展名改为.xls是比较常用的一种方式.当然这只是一种骗人的伎俩,所以我称之为伪xls表格.不过对于要求不高的需求,这种方法还是比较简单快捷的. 在Web Forms项目中,除了用代码拼凑HTML元素标记外,还可以直接用窗体页面渲染HTML表格.Web Forms就是用来渲染动态HTML的,直接利用它,支持代

一个简单的ASP.NET Web Forms应用程序

ASP.NET 支持三种不同的开发模式: Web Pages(Web 页面).MVC(Model View Controller 模型-视图-控制器).Web Forms(Web 窗体) 我给大家展示的是Web Forms(Web 窗体) 第一:我们需要了解下,什么是Web Forms. Web Forms 是三种创建 ASP.NET 网站和 Web 应用程序的编程模式中的一种. Web Forms 是最古老的 ASP.NET 编程模式,是整合了 HTML.服务器控件和服务器代码的事件驱动网页.

jQueryMobile 網頁使用 ASP.NET Web API 服務

微軟的 ASP.NET Web API 框架,能以 RESTful 輕量級的架構風格,建立 HTTP 服務,讓多種不同的用戶端,如: 手機.平板.電腦(PC),透過 HTTP 的 GET.POST.PUT.DELETE 方法來「存取(访问)」服務. 而 jQuery Mobile 框架,設計的目標,是希望能統一,市面上常見的手機.平板...等各種行動裝置.其特性如下: 以 jQuery 為核心 支援 HTML 5 支援滑鼠(鼠标).手指的觸碰事件 內建多種佈景主題 內建豐富的 UI 控制項(控件

Using ASP.Net WebAPI with Web Forms

Asp.Net WebAPI is a framework for building RESTful HTTP services which can be used across a wide range of clients including web, phone and desktop applications. With WebAPI we can use XML and JSON to send and retrieve data from the service. The use o