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

Happy coding!

 
 

Unity 3.5的更多相关文章

  1. Unity3d入门 - 关于unity工具的熟悉

    上周由于工作内容较多,花在unity上学习的时间不多,但总归还是学习了一些东西,内容如下: .1 根据相关的教程在mac上安装了unity. .2 学习了unity的主要的工具分布和对应工具的相关的功 ...

  2. 聊聊Unity项目管理的那些事:Git-flow和Unity

    0x00 前言 目前所在的团队实行敏捷开发已经有了一段时间了.敏捷开发中重要的一个话题便是如何对项目进行恰当的版本管理.项目从最初使用svn到之后的Git One Track策略再到现在的GitFlo ...

  3. Unity游戏内版本更新

    最近研究了一下游戏内apk包更新的方法. ios对于应用的管理比较严格,除非热更新脚本,不太可能做到端内大版本包的更新.然而安卓端则没有此限制.因此可以做到不跳到网页或应用商店,就覆盖更新apk包. ...

  4. Unity 序列化

    Script Serialization http://docs.unity3d.com/Manual/script-Serialization.html 自定义序列化及例子: http://docs ...

  5. Unity 序列化 总结

    查找了 Script Serialization http://docs.unity3d.com/Manual/script-Serialization.html 自定义序列化及例子: http:// ...

  6. ASP.NET MVC5+EF6+EasyUI 后台管理系统(64)-补充WebApi与Unity注入-配置文件

    系列目录 上一篇演示了WebApi利用Unity注入 很多人问我如何用配置文件来配置注入,本节演示如何利用配置文件来注入,道理是一样的,跳转到上一节下载源码一起来动手! 1.打开源码定位到文件Depe ...

  7. ASP.NET MVC5+EF6+EasyUI 后台管理系统(64)-WebApi与Unity注入

    系列目录 前言: 有时候我们系统需要开放数据给手机App端或其他移动设备,不得不说Asp.net WebApi是目前首选 本节记录Asp.net MVC WebApi怎么利用Unity注入.系列开头已 ...

  8. 使用Microsoft的IoC框架:Unity来对.NET应用进行解耦

    1.IoC/DI简介 IoC 即 Inversion of Control,DI 即 Dependency Injection,前一个中文含义为控制反转,后一个译为依赖注入,可以理解成一种编程模式,详 ...

  9. Unity C#最佳实践(上)

    本文为<effective c#>的读书笔记,此书类似于大名鼎鼎的<effective c++>,是入门后提高水平的进阶读物,此书提出了50个改进c#代码的原则,但是由于主要针 ...

  10. Unity插件之plyGame教程:DiaQ对话系统

    本文为孤月蓝风编写,转载请注明出处:http://fengyu.name/?cat=game&id=296 DiaQ是plyGame旗下的一款对话及任务系统.拥有可视化的对话及任务编辑器,能够 ...

随机推荐

  1. java处理Excel文件---excel文件的创建,删除,写入,读取

    这篇文章的代码是我封装的excel处理类,包含推断excel是否存在,表格索引是否存在,创建excel文件,删除excel文件,往excel中写入信息,从excel中读取数据. 尤其在写入与读取两个方 ...

  2. CODE - TSQL convert Query to JSON

    原文 ODE - TSQL convert Query to JSON TSQL - Query to JSON It is my philosophy that good development s ...

  3. C++ Primer 学习笔记_29_STL实践与分析(3) --操作步骤集装箱(下一个)

    STL实践与分析 --顺序容器的操作(下) 六.訪问元素 假设容器非空,那么容器类型的front和back成员将返回容器的第一个和最后一个元素的引用. [与begin和end的对照:] 1)begin ...

  4. Javascript学习6 - 类、对象、继承

    原文:Javascript学习6 - 类.对象.继承 Javasciprt并不像C++一样支持真正的类,也不是用class关键字来定义类.Javascript定义类也是使用function关键字来完成 ...

  5. (hdu step 6.3.2)Girls and Boys(比赛离开后几个人求不匹配,与邻接矩阵)

    称号: Girls and Boys Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others ...

  6. crawler_大型舆情架构图

  7. EF4.1: Add/Attach and Entity States(EF中的实体状态转换说明)

    实体的状态,连接以及 SaveChanges 方法 数据库上下文对象维护内存中的对象与数据库中数据行之间的同步.这些信息在调用 SaveChanges方法被调用的时候使用.例如,当使用 Add 方法传 ...

  8. C#程序读取MAC地址的五种方法(转)

    public class GetMac { ///<summary> /// 根据截取ipconfig /all命令的输出流获取网卡Mac ///</summary> ///& ...

  9. WebService对跨域的支持

    WebService对跨域的支持 跨域问题来源于JavaScript的同源策略,即只有 协议+主机名+端口号 (如存在)相同,则允许相互访问.也就是说JavaScript只能访问和操作自己域下的资源, ...

  10. .net mvc mssql easyui treegrid 及时 编辑 ,支持拖拽

    这里提到了,1个问题,怎么扩展 Easyui 参见: http://blog.csdn.net/chenkai6529/article/details/17528833 @{ ViewBag.Titl ...