跟 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!」

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

  1. [转]Bootstrap 3.0.0 with ASP.NET Web Forms – Step by Step – Without NuGet Package

    本文转自:http://www.mytecbits.com/microsoft/dot-net/bootstrap-3-0-0-with-asp-net-web-forms In my earlier ...

  2. Using Friendly URLs in ASP.NET Web Forms

    Introduction Websites often need to generate SEO friendly URLs. In ASP.NET Web Forms applications, a ...

  3. 【翻译】使用Knockout, Web API 和 ASP.Net Web Forms 进行简单数据绑定

    原文地址:http://www.dotnetjalps.com/2013/05/Simple-data-binding-with-Knockout-Web-API-and-ASP-Net-Web-Fo ...

  4. Asp.Net学习进度备忘(第一步:ASP.NET Web Forms)

    书签:“Web Pages”和“MVC”跳过:另外跳过的内容有待跟进 __________________ 学习资源:W3School. _________________ 跳过的内容: 1.ASP. ...

  5. ASP.NET Web Forms的改进

    虽然ASP.NET Web Forms不是vNext计划的一部分,但它并没有被忽视.作为Visual Studio 2013 Update 2的一部分,它重新开始支持新工具.EF集成和Roslyn. ...

  6. ASP.NET Web Forms 4.5的新特性

    作者:Parry出处:http://www.cnblogs.com/parry/ 一.强类型数据控件 在出现强类型数据控件前,我们绑定数据控件时,前台一般使用Eval或者DataBinder.Eval ...

  7. Knockout, Web API 和 ASP.Net Web Forms 进行简单数据绑定

    使用Knockout, Web API 和 ASP.Net Web Forms 进行简单数据绑定   原文地址:http://www.dotnetjalps.com/2013/05/Simple-da ...

  8. ASP.NET Web Forms - 网站导航(Sitemap 文件)

    [参考]ASP.NET Web Forms - 导航 ASP.NET 带有内建的导航控件. 网站导航 维护大型网站的菜单是困难而且费时的. 在 ASP.NET 中,菜单可存储在文件中,这样易于维护.文 ...

  9. 在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 - I ...

随机推荐

  1. iScroll4插件的使用实例

    iScroll是Matteo Spinelli开发的一个滚动插件,使用原生js编写,其不依赖与任何js框架.iScroll 4 完全重写了iScroll这个框架的原始代码.旨在解决移动webkit系浏 ...

  2. HDU 4667 Building Fence(2013多校7 1002题 计算几何,凸包,圆和三角形)

    Building Fence Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others)To ...

  3. openCV+ASM+LBP+Gabor实现人脸识别(GT人脸库)

    原理:使用GT人脸库做样本,VS2010下使用openCV2.44自带的Haar算法检測人脸区域,ASM Library特征检測,然后使用YCrCb颜色空间做肤色检測,再用LBP+Gabor小波提取特 ...

  4. Spring Bean init-method 和 destroy-method实例

    在Spring中,可以使用 init-method 和 destroy-method 在bean 配置文件属性用于在bean初始化和销毁某些动作时.这是用来替代 InitializingBean和Di ...

  5. mysql localhost登录和tcp/ip登录 strace

    http://blog.itpub.net/15480802/viewspace-1755100/

  6. HttpClient post提交数据,汉字转码

    public static String post(String url, String data) throws ClientProtocolException, IOException { Htt ...

  7. 扩展LVM 逻辑卷存储空间

    原因: 运行在Xen DomU的磁盘空间不足,需要扩展.DomU的存储主要为[os镜像文件+lv逻辑卷]的形式,现要对逻辑卷进行扩展. 过程(离线方式): 卸载逻辑卷 umount /dev/VolG ...

  8. 将List集合中的map对象转为List<对象>形式--封装类

    import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Li ...

  9. Hibernate之一对一关联映射

    Hibernate中一对一关联映射共分为两种,一种是一对一主键关联映射,另一种是一对一唯一外键关联映射.下面简单介绍一下这两种关联映射. 一对一主键关联映射 一对一主键关联映射的两个实体有相同的ID. ...

  10. 解决Windows server 2012 R2 系统使用IIS8浏览Asp程序出现"An error occurred on the server when processing the URL"错误

    进入IIS并将ASP里的“Send Error To Browser”设置为True后点击Appley保存即可 原因是IIS里的Asp设置禁用上当错误信息发送给浏览器,只要启用即可 如果没有Asp选项 ...