经过微软.net Framework 2.0的革新,.net平台的开发越来越精彩。笔者收集基于.net开发的n个项目,一方面为新手提供有价值学习资源,培养起软件开发的乐趣,另一方面共勉已从事.net领域工作多年并有明确发展目标的同仁,提高自己,更上一层楼。
SharpDevelop
强大的插件系统,通过Addin构建成一个功能齐全的.net开发IDE。核心是AddInTree。跟随这个项目开发许多有用的组件,比如功能文本编辑器(ICSharpCode.TextEditor),SharpZipLib等。
链接:http://www.icsharpcode.net/
DotNetNuke
这个就是著名DNN,使用VB.NET进行开发。通过其基本架构可进行堆积木式快速建站。而且支持子网站系统。其由ASP.NET Portal start kit进化而来。
链接:http://www.dnnchina.net/ ,http://www.dotnetnuke.com
Community Server
这个也是一个很著名的ASP.NET项目,记得好像最早系统原形为ASP.NET Forums,后来加入了.Text Blog 和nGallery成为一个完整的通用系统。对应的中文版本为宝玉修改的CCS。
链接:http://communityserver.org/
Rainbow
另外一个类似于DNN的系统,使用C#进行开发。
链接:http://www.rainbowportal.net/
RssBandit
一个客户端的RSS查看器,使用C# 进行开发,最大的特点是数据存储采用xml文件。
链接:http://www.rssbandit.org/
FreeTextbox
Web上的一个Html超文本编辑器,早些版本是开源的,现在还是免费使用,不过源代码需要购买。
链接:http://www.freetextbox.com
World Wind
使用.NET开发的一个Windows窗体系统,以地球外观看得角度提供全球定位功能,类似于Google Earth。
链接:http://worldwind.arc.nasa.gov
log4net
对应Java中的log4j。一个强大的日志管理模块。
链接:http://logging.apache.org/log4net/
Monodevelop
非Windows 系统下的.net 平台开发工具。
链接:http://www.monodevelop.com
Paint.NET
使用.net开发的画图软件,功能不错。
链接:http://www.getpaint.net/index.html
Nunit
对应Java中的Junit,非常著名的单元测试工具。
链接:http://www.nunit.org/
FCKeditor
Web上的又一个Html超文本编辑器。
链接:http://www.fckeditor.net/
Nlog
一个日志管理库,类似于Log4Net。
链接:http://www.nlog-project.org/
ManagedSpy
.net 平台下的Spy ++,支持通过.net 2.0开的Windows Forms应用程序。
链接:http://msdn.microsoft.com/msdnmag/issues/06/04/ManagedSpy/
Guidance Explore
类似于一个.net平台编程问题简答的FAQ。不过提供的都是英文。
链接:http://www.codeplex.com/Wiki/View.aspx?ProjectName=guidanceExplorer
Terrarium
一个.net开发的多人游戏。玩这个游戏可以提高编程能力^_^。
链接:http://www.windowsforms.net/Default.aspx?tabindex=4&tabid=49
TaskVision
任务管理系统,一个经典的Smart Client智能客户端程序。
链接:http://www.windowsforms.net/Default.aspx?tabindex=4&tabid=49
IssueVision
事务管理系统,Smart Client智能客户端程序。
链接:http://www.windowsforms.net/Default.aspx?tabindex=4&tabid=49
FotoVision
又一个值得学习的.net开发的Windows应用程序。
链接:http://www.windowsforms.net/Default.aspx?tabindex=4&tabid=49
Infragistics Tracker Application
由Infragistics开发的Smart Client智能客户端程序。
链接:http://www.windowsforms.net/Default.aspx?tabindex=4&tabid=49
Windows Forms RSS Portal
一个.net 开发的聚合器。
链接:http://www.windowsforms.net/Default.aspx?tabindex=4&tabid=49
Enterprise Library
微软的企业库,对原早些时候开发的一些Block 模块进行整合提供企业统一的接口,新版本使用了.net 2.0的的许多功能。
链接:http://msdn.microsoft.com/practices/
PetShop
基于N-tier设计的电子商务网站,没什么好说的了。
链接:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdasamppet4.asp
XmlNotepad
一个用.net 开发的xml文件编辑器。
链接:http://www.microsoft.com/china/msdn/library/data/xml/xmlnotepad.mspx
DockManager Control
一个很不错的实现类似于VS 2005的界面某些功能的组件。
链接:http://www.codeproject.com/vb/net/DockPanelSkin.asp
Starter Kits for Visual Web Developer
该套件为学习研究应用asp.net2.0的开发人员提供许多宝贵资源,很不错。
链接:http://www.asp.net/downloads/starterkits/default.aspx?tabid=62
以上是笔者的一些资源积累,还有需多经典项目案例没有包括,比如一些早期微软提供的项目案例以及现在热门的ORM经典框架。
[教程]ASP.net学习路线
如果你已经有较多的面向对象开发经验,跳过以下这两步:
第一步 掌握一门.NET面向对象语言,C#或VB.NET 我强烈反对在没系统学过一门面向对象(OO)语言的前提下去学ASP.NET。 ASP.NET是一个全面向对象的技术,不懂OO,那绝对学不下去!
第二步 对.NET Framework类库有一定的了解 可以通过开发Windows Form应用程序来学习.NET Framework。ASP.NET是建构在.NET Framework之上的技术,你对.NET Framework了解得越深,学习ASP.NET就越快。 举个例子:下面简单列出几个对掌握ASP.NET非常重要的概念:对象的内存模型,委托,事件,多线程,程序集和应用程序域,安全模型 .NET Framework当然还有许多其它的东西,但不理解与把握清楚上述这些东西,是很难真正把握ASP.NET的。出于急迫的心情与现实的考虑,不少人没有扎实的面向对象基础就想直接学习ASP.NET,其结果只能是欲速则不达。在具备了OO基础之后,以下是具体的学习ASP.NET技术步骤。
第一步:学习HTML与CSS
这并不需要去学一大堆的诸如Dreamweaver,Firework之类的各种网页设计工具,关键是理解HTML网页嵌套的block结构与CSS的box模型。许多 ASP.NET控件最后都必须转化为HTML。而且,div+CSS是当前主流的网页布局模型。学习这部分时,关键在于理解概念,而不需要将精力花在美化页面的许多技巧上,那是网站美工的工作,不是程序员的工作。
第二步:学习JavaScript
JavaScript不是Java,它主要运行于浏览器端。可以完成许多工作,功能也很强大:比如它将客户端网页中的HTML元素看成一棵树,可以编写代码访问并修改树节点,动态生成新的HTML代码,从而达到动态修改网页显示特性的目的。
JavaScript在目前的网站开发中用得很多,非常重要。
另外,它也是目前非常流行的AJAX技术的基础。
第三步:学习计算机网络原理
找一本大学<计算机网络>教材,重点看看它的有关互联网的部分,了解一些域名解析和HTTP协议等知识。这是进行互联网开发的理论基础。
第四步:学习ASP.NET表示层技术,会设计Web页面
需要结合先前打好的面向对象技术的基础,至少掌握以下内容:
(1) 各种Web控件的使用方法,
(2) 理解信息在网页中的传送方式,比如Cookie,ViewState,Session等的使用。
(3) ASP.NET应用程序与网页的生命周期,以及相关对象(比如httpcontext,response,request)的用途。
(4) ASP.NET实现事件驱动的内幕
(5) 自定义用户控件
再次强调一下,没有OO基础,很难掌握上述技术,就只能被这些东东牵着鼻子走了,会很被动。
第五步 掌握数据库技术
具体地说,要学习以下内容:
(1) 学会使用SQL Server 2005:不要求精通它的各种工具与管理配置技术,但至少知道如何连接,如何建表,如何创建存储过程
(2) 学习ADO.NET,掌握使用代码人工访问数据库(别用VS2005的向导)的方法
(3) 学习数据绑定控件的使用
第六步 理解多层架构
这时,先前在OO学习阶段涉及到的程序集与应用程序域等就派上用场了,现在,网站架构大多采用多层架构:表示层、业务逻辑层、数据存取层以及数据库本身。
可以先上网找一此多层架构的资料看,再找一个现成的比较复杂的开源ASP.NET项目分析一下其架构就差不多了。基本上都是一个套路,到处应用。
有的朋友问:学习架构是不是必须学习设计模式。
我的看法是:不必!当然,你如果学习过设计模式,那当然更好。但在实际开发中,如果只想着机械地套用某种模式,反而起不到好的结果。我的观点:在学习设计模式时要多思多悟,其思想就会渐渐地融入你的大脑,在真实的设计实践中,忘掉所有的写在书上的模式,一切从实际出发,相信你的直觉,只要达到设计要求的方案就是可行的方案,事实上,你这样做了之后,回过头来再看,会发现你的设计往往暗合设计模式的理论。
第七步 学习XML与Web Service
先了解XML的基础知识,找本讲XML的书看一下就差不多了,然后,再学习Web Service。Web Service其实可类比为远程方法调用(以XML格式表达的调用信息)。
学 到了这里,如果你还有兴趣,不妨再去看看SOA,不过SOA的资料都是理论与概念,看起来比较郁闷,而且离实际开发可能比较远。所以,这是可选项。
第八步 学习AJAX
学习AJAX的主要目的是创建具有更丰富特性的Web表示层,而经过前面七步的学习,到此再学习AJAX已水到渠成,所有的基础都已具备,不会有太大的问题了。
在基础未具备的前提下直接学AJAX,就象“浮沙之上筑高台”,所以,将AJAX的学习次序安排在这里。
第九步 学习RIA技术
RIA:Rich internet application,可看成是将C/S与B/S优点合二为一的尝试。就具体技术来说,主要指微软的Silverlight(WPF_E),毕竟前头一路行来都是微软路线。
不过目前学习它主要是为了技术储备。估计此技术的普及需要2到3年的时间,因为预装Vista的机器今年才开始大卖。
本贴来自ZDNetChina中文社区 http://bbs.zdnet.com.cn ,本贴地址:http://bbs.zdnet.com.cn/viewthread.php?tid=108315
[教程]解读ASP.NET TimeTracker Starter Kit(2)——重构篇
看了ASP.NET Time Tracker Starter Kit的代码以后,觉得这个程序是学习面向对象编程开发的一个好案例。整个程序从功能上来讲就是记录人员参加项目工作的工时记录。分析得出主要有人员、项目、工时记录这几个类。整个程序正好是以这几个类为基础展开的。在业务逻辑层里TTUser(用户类)、Project(项目类)和TimeEntry(工时记录类)分别就包括了对相应表的增、删、改等操作和整个程序中所用到的对这几个主要对象的各种操作。整个程序在类的划分和功能上做的很好,但是我觉得在三层架构的划分上不是很理想。
业务逻辑层包含的许多数据访问层的东西,如:数据库连接信息、用到的存储过程等。而这些信息我觉得放在数据访问层应该会更好一些。作为数据访问层,应该为数据逻辑层提供访问数据库的方法,而不是只提供一个简化数据访问的组件(DAAB)。假设要使用Oracle数据,那么改动的代码就需要改动很多,同时也不利于将来的改动。
我的想法是:彻底的将与数据相关的操作从数据逻辑层分离出去,对数据访问层只提供访问方法。在数据访问层的设计上,还是沿用“为数据访问层编写一个基类”的方法,通过基类访问DAAB。这样一来如果要变换为Oracle数据库,只要修改DAAB中的内容即可。大家帮忙看看我的改进代码,看看思路有无问题。
数据访问层基类:
数据访问层代码:(以项目类数据访问层代码为例)
业务逻辑层代码:(以项目类业务逻辑层代码为例)
更多相关内容:点击这里>>
[教程]解读ASP.NET Portal Starter Kit(1)——数据库篇
ASP.NET Portal Starter Kit数 据库结构总体上来讲是由网站引擎的核心表(用户表、角色表和角色关系表)和各个用户模块相关的表组成。核心表存储整个网站的用户权限的配置信息(详细的用 法及说明将放到《角色身份认证篇》中讲)。各用户模块存储各个功能模块的信息。各个用户功能模块表都是独立的。这样有利于新增模块扩展功能。同时也可将各 个功能模块的表分别部署到不同的数据库中提高程序的高伸缩性和可扩展性。数据的访问方式是全部通过存储过程进行的。这样做的好处有:1、提高了数据库的性能;2、杜绝了SQL注入式攻击(在我的理解上应该是);3、可将可将连接到SQL Server数据库用户的权限只配置成只能对指定存储过程进行执行操作,这样就进一步提高了数据库的安全性。
数据库中的表:
Portal_Announcements:公告信息表(在公告信息模块中用)
|
字段名 |
类型 |
含义 |
备注 |
|
ItemID |
Int |
链接Id |
主键(自动编号) |
|
ModuleID |
Int |
所属模块ID |
决定该公告在那个模块中显示(模块ID:用户配置文件PortalCfg.xml中的模块表ID,以下皆同) |
|
CreatedByUser |
Nvarchar(100) |
创建者 |
记录创建和修改该公告的用户 |
|
CreatedDate |
Datetime |
创建时间 |
记录创建和修改该公告的时间 |
|
Title |
Nvarchar(100) |
名称 |
|
|
MoreLink |
Nvarchar(150) |
更多细节的链接 |
|
|
MobileMoreLink |
Nvarchar(150) |
移动细节链接地址 |
在“移动设备浏览器”上的更多细节链接地址 |
|
ExpireDate |
Datetime |
有效日期 |
|
|
Description |
Nvarchar(2000) |
描述 |
|
Portal_Contacts:联系方式信息表(在联系方式模块中用)
|
字段名 |
类型 |
含义 |
备注 |
|
ItemID |
Int |
链接Id |
主键(自动编号) |
|
ModuleID |
Int |
所属模块ID |
决定该联系方式在那个模块中显示 |
|
CreatedByUser |
Nvarchar(100) |
创建者 |
记录创建和修改该联系方式的用户 |
|
CreatedDate |
Datetime |
创建时间 |
记录创建和修改该联系方式的时间 |
|
Name |
Nvarchar(50) |
联系人姓名 |
|
|
Role |
Nvarchar(100) |
联系人角色 |
与系统框架中的角色不同,理解成职位更合适 |
|
Email |
Nvarchar(100) |
联系人Email |
|
|
Contact1 |
Nvarchar(250) |
联系方式1 |
|
|
Contact2 |
Nvarchar(250) |
联系方式2 |
|
Portal_Discussion:用户讨论留言信息表(用户讨论模块用)
|
字段名 |
类型 |
含义 |
备注 |
|
ItemID |
Int |
链接Id |
主键(自动编号) |
|
ModuleID |
Int |
所属模块ID |
决定该讨论在那个模块中显示 |
|
Title |
Nvarchar(50) |
讨论标题 |
|
|
CreatedDate |
Datetime |
创建时间 |
记录创建该讨论的时间 |
|
Body |
Nvarchar(100) |
留言内容 |
|
|
CreatedByUser |
Nvarchar(100) |
创建者 |
记录创建和修改该讨论的用户 |
|
DisplayOrder |
Nvarchar(100) |
显示顺序 |
记录回复的讨论的时间和本身提交的时间 (可用作讨论留言的排序和显示回复关系) |
Portal_Documents:用户上传文档信息表(在显示文档信息模块中用)
|
字段名 |
类型 |
含义 |
备注 |
|
ItemID |
Int |
链接Id |
主键(自动编号) |
|
ModuleID |
Int |
所属模块ID |
决定该联系方式在那个模块中显示 |
|
CreatedByUser |
Nvarchar(100) |
创建者 |
记录创建和修改该联系方式的用户 |
|
CreatedDate |
Datetime |
创建时间 |
记录创建和修改该联系方式的时间 |
|
FileNameUrl |
Nvarchar(250) |
|
上传至服务器中的文件路径 |
|
FileFriendlyName |
Nvarchar(150) |
文档名称 |
友好的文件名称显示 |
|
Category |
Nvarchar(50) |
类别 |
|
|
Content |
Image |
内容(二进制) |
上传到数据库中的内容 |
|
ContentType |
Nvarchar(50) |
类型 |
上传文件的类型 |
|
ContentSize |
Int |
大小 |
上传文件的大小 |
Portal_Events:事件信息表(在显示事件信息的模块中使用)
|
字段名 |
类型 |
含义 |
备注 |
|
ItemID |
Int |
链接Id |
主键(自动编号) |
|
ModuleID |
Int |
所属模块ID |
决定该事件在那个模块中显示 |
|
CreatedByUser |
Nvarchar(100) |
创建者 |
记录创建和修改该事件的用户 |
|
CreatedDate |
Datetime |
创建时间 |
记录创建和修改该事件的时间 |
|
Title |
Nvarchar(100) |
事件名称 |
|
|
WhereWhen |
Nvarchar(150) |
发生地点 |
|
|
Description |
Nvarchar(2000) |
描述 |
|
|
ExpireDate |
Datetime |
有效日期 |
|
Portal_HtmlText:静态HTML信息表(在显示静态静态HTML的模块中用,可用于显示新闻等文本)
|
字段名 |
类型 |
含义 |
备注 |
|
ModuleID |
Int |
所属模块ID |
决定该静态HTML在那个模块中显示(主键,限定一个模块只对应一个静态HTML文件) |
|
DesktopHtml |
Ntext |
桌面HTML内容 |
|
|
MobileSummary |
Ntext |
移动摘要 |
在“移动设备浏览器”上显示的摘要 |
|
MobileDetails |
Ntext |
移动细节 |
在“移动设备浏览器”上显示的细节 |
Portal_Links:链接信息表(快速链接模块和连接模块用)
|
字段名 |
类型 |
含义 |
备注 |
|
ItemID |
Int |
链接Id |
主键(自动编号) |
|
ModuleID |
Int |
所属模块ID |
决定该链接在那个模块中显示 |
|
CreatedByUser |
Nvarchar(100) |
创建者 |
记录创建和修改该链接的用户 |
|
CreatedDate |
Datetime |
创建时间 |
记录创建和修改该链接的时间 |
|
Title |
Nvarchar(100) |
名称 |
|
|
Url |
Nvarchar(250) |
链接地址 |
|
|
MobileUrl |
Nvarchar(250) |
移动链接地址 |
|
|
ViewOrder |
Int |
排序号 |
|
|
Description |
Nvarchar(2000) |
描述 |
|
Portal_Roles:角色信息表(门户网站引擎核心表)
|
字段名 |
类型 |
含义 |
备注 |
|
RoleID |
Int |
角色Id |
主键(自动编号) |
|
PortalID |
Int |
门户网址ID |
可架设多个门户站点而共用一个数据库,通过PortalID区分 |
|
RoleName |
Nvarchar(50) |
角色名称 |
Portal_UserRoles:用户角色关系表(门户网站引擎核心表)
|
字段名 |
类型 |
含义 |
备注 |
|
UserID |
Int |
用户Id |
关联用户信息表(Portal_Roles) |
|
RoleID |
Int |
角色Id |
管理角色信息表(Portal_Users) |
Portal_Users:用户信息表(门户网站引擎核心表)
|
字段名 |
类型 |
含义 |
备注 |
|
UserID |
Int |
用户Id |
主键(自动编号) |
|
Name |
Nvarchar(50) |
用户姓名 |
|
|
Password |
Nvarchar(50) |
密码 |
采用MD5的加密方式存储 |
|
Email |
Nvarchar(100) |
用户Email |
用于登录,并设置成唯一性索引(可防止注册相同的Email,这样设置后当有相同的Email插入时程序就会抛出异常,捕获这个异常就可判断Email是否重复,这样就可以省掉判断Email是否重复的代码) |
数据库中的存储过程:
|
存储过程名称 |
说明 |
|
Portal_AddAnnouncement |
添加新公告 |
|
Portal_AddContact |
添加新联系方式 |
|
Portal_AddEvent |
添加新事件 |
|
Portal_AddLink |
添加新链接 |
|
Portal_AddMessage |
新建一条新的讨论留言,其中@ParentID的参数为被回复留言的Id,通过该ID找到该留言的DisplayOrder,加上新增留言的时间就是新留言的DisplayOrder。 |
|
Portal_AddRole |
添加角色信息 |
|
Portal_AddUser |
添加一个新用户,返回用户的Id |
|
Portal_AddUserRole |
添加用户角色关系 |
|
Portal_DeleteAnnouncement |
删除公告信息(注:以下删除部分若无特殊说明均为删除指定ItemID的信息) |
|
Portal_DeleteContact |
删除联系方式 |
|
Portal_DeleteDocument |
删除用户上传文档 |
|
Portal_DeleteEvent |
删除事件信息 |
|
Portal_DeleteLink |
删除链接信息 |
|
Portal_DeleteModule |
当删除一个模块时,联动的删除该模块相关的全部信息 |
|
Portal_DeleteRole |
删除角色信息 |
|
Portal_DeleteUser |
删除用户 |
|
Portal_DeleteUserRole |
删除角色用户关系 |
|
Portal_GetAnnouncements |
根据ModuleID(模块ID)返回有效期内的公告信息 |
|
Portal_GetAuthRoles |
像是没有用到该存储过程,而且该存储过程涉及的表数据库中没有(在用户配置文件中有类似的表),需要在深入研究 |
|
Portal_GetContacts |
根据ModuleID(模块ID)返回联系方式 |
|
Portal_GetDocumentContent |
根据文档的(ItemID)获取存储在数据库中的文档信息 |
|
Portal_GetDocuments |
根据ModuleID(模块ID)返回用户上传文档信息 |
|
Portal_GetEvents |
根据ModuleID(模块ID)返回有效期内的事件信息 |
|
Portal_GetHtmlText |
根据ModuleID(模块ID)返回静态HTML文本信息 |
|
Portal_GetLinks |
根据ModuleID(模块ID)返回连接信息,并按(ViewOrder)排序号排序 |
|
Portal_GetNextMessageID |
获取讨论的下一条留言 |
|
Portal_GetPortalRoles |
获取指定门户站点(指定PortalID)的全部角色信息 |
|
Portal_GetPrevMessageID |
获取讨论的上一条留言 |
|
Portal_GetRoleMembership |
根据角色ID获取该角色对应的用户成员信息 |
|
Portal_GetRolesByUser |
根据用户Email获取用户角色信息 |
|
Portal_GetSingleAnnouncement |
根据公告的(ItemID),获取单个公告的信息 |
|
Portal_GetSingleContact |
根据联系方式的(ItemID),获取单个联系方式的信息 |
|
Portal_GetSingleDocument |
根据文档的(ItemID),获取单个文档的信息 |
|
Portal_GetSingleEvent |
根据事件的(ItemID),获取单个事件的信息 |
|
Portal_GetSingleLink |
根据链接的(ItemID),获取单个链接的信息 |
|
Portal_GetSingleMessage |
根据留言的(ItemID),获取单个留言的信息 |
|
Portal_GetSingleRole |
根据角色ID,获取单个角色的信息 |
|
Portal_GetSingleUser |
根据用户Email,获取单个用户的信息 |
|
Portal_GetThreadMessages |
根据父留言信息的DisplayOrder,返回按时间顺序和回复关系返回子留言信息 |
|
Portal_GetTopLevelMessages |
根据ModuleID(模块ID)返回顶层留言信息 |
|
Portal_GetUsers |
获取全部的用户信息,并按Email排序 |
|
Portal_UpdateAnnouncement |
更新公告信息 |
|
Portal_UpdateContact |
更新联系方式信息 |
|
Portal_UpdateDocument |
更新文档信息,当未找到指定文档ID时添加新的文档信息 |
|
Portal_UpdateEvent |
更新事件信息 |
|
Portal_UpdateHtmlText |
更新静态HTML文本信息 |
|
Portal_UpdateLink |
更新指定的连接信息 |
|
Portal_UpdateRole |
更新角色信息 |
|
Portal_UpdateUser |
更新用户信息 |
|
Portal_UserLogin |
根据email和password返回登录的用户姓名(用于判断用户登录是否通过) |
更多相关内容:点击这里>>
[教程]Portal应用点滴(一) —— Portal简介
[引言] 早在2003年底,Portal这个名词第一次出现在了我的技术辞典中。当时对Portal的理解仅仅限于“页面上的几个可以随意摆放的小窗口”,觉得没 有什么特别的,也没有把这项技术放在心上了。2004年底,我参与了一个基于Portal的项目开发。在过去将近一年的时间里,一直都在和IBM WebShpere Portal打交道,这给了我认识并学习Portal的好机会。Daily Portal这一系列的文章算是我这一年来对Portal认识的小小一个总结吧。
Portal是一个内涵很丰富的名词,其中文意思是“大门,正门,入口或者进入的手段”。而这项技术也确实人如其名,它为用户提供了一种简单而且统一的访问Web Application的入口。在这个定义中,统一是最为重要的方面,因为在一个企业中,其IT Support可能是由很多很多相对独立的系统组成,而它们都有各自的访问入口。在没有使用Portal技术之前,不管是企业内部员工,企业的合作伙伴以及企业用户都不得不在几个系统中来回跳转以获得对自己有用的信息,对效率有很大的影响。更糟糕的是,跨系统对信息进行采集和总括的时候,散布在各个系统的相关信息将会以不同的呈现方式出现在用户面前,要迅速从中获得所需要的信息集将是一项十分艰巨的任务。而Portal的统一意味着集成(integrate),它所提供的不仅仅是将几个系统的信息采集过来在一张页面上显示出来,还要提供安全,权限,统一的登陆服务,个性化设置等等。利用Portal来进行Legacy系统的整合是一个十分重要而且有意义的课题,可惜这一年来所做的项目仅仅是对一个系统升级,还没有机会亲身体验Portal在系统整合方面的强大优势,希望有经验的朋友可以不吝赐教。
说完Portal的定义,我们来看一下Portal的组成结构:

从图中我们可以看出,Horizontal Portals Infrastructure部分为建立不同的Portal系统提供了最基础的服务,通常它由以下几个方面组成:
1、Presentation:提供了在不同的client端显示的服务;
2、Personalization:基于User Profile的个性化设置服务;
3、Collaboration:提供与其他系统,如果Mail,IM等的协作服务;
4、Portlets:这个名词是不是很熟悉呢?Portlet提供了访问Web Application的服务;
5、Search and Workflow:提供与Legacy系统集成的服务;
6、Publish and subscribe:提供内容发布与订阅服务;
7、Administration and Security: 管理及安全服务;
8、Integration:提供与数据库连接以及元数据共享服务。
而在这八个方面当中,Portlets是最为核心的,通常我们对Portal的第一步感性认识也是从Portlet开始。简单的说,从页面上看到的一 个个小窗口就是portlet。我们可以把Portlet看成是Web世界中的可重用的Component,它为用户提供了访问Web Application,Web Service或者Web Content的服务。一个Portal Page通常会包含几个Portlet,而这些Portlet之间是相互独立且互不影响的,也就是当一个Portlet无法正常工作的时候,另外的 Portlet并不会因此而罢工,仍会正常地进行处理。
最后大致说一下,使用Portal技术会给企业带来怎样的益处。首先,以 可重用的Portlet作为核心的开发方式使得企业可以更快速的响应商业上的需求;Portal提供了强大的系统集成功能,降低了系统维护以及使用的成 本,同时也降低了员工培训的成本;Portal提供了统一的访问入口使得用户访问各个系统的时候有更好的用户体验以及更好的安全性。
在昨天的Post中,偶已经对Portal进行了简单的介绍,不知道各位对Portal是不是有一点点认识了呢?如果还是云里雾里的话也没有关系,毕竟Portal所涵盖的内容是很广博的,掌握它需要时间,了解也是需要时间的。在初步介绍了Portal的概念之后,今天我们来探讨一下Portal的核心——Portlet。
首先,Portlet在表现形式上就是我们所看到的页面上一个个小窗口,但是它绝不仅限于将Web Content以模块化的方式显示出来。实际上,每一个Portlet就是一个完整的Web Application,拥有完整的MVC。而Portlet的开发,部署,管理和显示都是完全独立的,不受其他Portlet的影响,从而其functionality(功能性)也是独立的,也就是说一个Portlet就是一个处理特定义务逻辑的Component。正是Portlet这种独立性与功能性,使得Portal的Administrator或者是End User能够很方便地配置符合自己要求的页面,充分提高信息采集与概括的效率。
做完了表面功夫之后,我们将注意力集中到Portlet的底层实现中来。从Portlet的类继承关系上,我们可以看到Portlet的Superclass仍然是HttpServlet。事实上,虽然Portlet相对Servlet而言进行了很大程度上的扩展并有很大的优越性,但是其本质与Servlet并无二致,都是一个处理HttpRequest并返回HttpResponse的过程,只是这个过程已经不是Servlet中的doGet、doPost或者service那么简单了,而是分成了两个方面:Event Phase和Content Rendering Phase。这就是Portlet的Two-phase Portlet Request Processing了。也许你会对这样的划分产生了兴趣,为什么需要这样去划分呢?其实,这样做是为了保持Portlet之间独立性。你可以设想一下,在一个Portal Page中包含了两个Portlet,而你在其中的一个Portlet输入了数据并提交,那么该Portlet将会处理你的请求并返回相应的结果,而另外一个Portlet则需要保持原来的样子。在这样的一个过程,对于处理了你的请求的Porlet,其Processing包含了两个phase,而另外一个Portlet则仅是进行了Content Rendering Phase的处理并将cache的内容返回。写到这里,我不禁想到了Ajax。以上的case,可以看作是页面的部分更新,如果使用了Ajax,是不是可以简化Portlet Request的处理过程呢?我相信这是一个有趣并值得深入探讨的问题。[1]
说完了比较底层的东西,我们最后再来看看Portlet比较特殊的一个地方:Portlet Mode。一个Portlet通常会包含几种display mode,最常见的莫过于Minimize和Maxmize的display mode,除此之还会有Help Mode和Edit Mode。Help Mode为用户提供关于该Portlet的帮助信息,而Edit Mode则为用户提供了设置Portlet属性的界面。用户可以通过点击Portlet的title bar上的图标(图中黑色椭圆框住的部分)来切换不同的display mode,如下图所示:

以上对Portlet作了简单的介绍,希望能够给各位以初步的印象。讲了那么多理论上的东西,大家是不是有点厌烦了呢?不要急,在接下来的Post中就要动手实践了,敬请关注哦! 🙂
在上一篇Post中,我提到要开始实践了,但是酝酿一篇与实践密切相关的Post还真的很费劲。毕竟建立一个portlet的方法有很多种,而且还会因使用的工具不同而方法迥异。平常在工作当中都是使用IBM提供的Portal Toolkit进行Portlet开发,打开Create New Portlet Wizard,跟着提示点几下按钮,一个漂亮的Portlet就在弹指之间完成了。如果我把这样的过程step by step地写下来,似乎意义不大,毕竟这些内容在IBM的红皮书中都写得清清楚楚了,况且由Portal Tookit搭建起来的开发环境也不是唾手可得的,看来还得从Open Source的东西入手了。
在Apache的Portals项目中有一个名叫Pluto的子项目,它致力于构建一个简单的Portlet Container,让程序员可以Deploy并运行所开发的Portlet。Pluto相对于BEA、IBM等大公司提供的Portal Server而言,尽管显得功能简陋,但是作为Portlet API的一个参考实现还是挺有权威的,其简单性也正好让我们更加关注Portlet的本质。更重要的是,它是开源的,大家都很容易从网上找到它。于是,Pluto就成为我们进行实践的不二之选了。OK,想好了就开始动手咯!
首先,我们从Pluto下载页面中把最新版本1.0.1-rc4的Pluto给download下来。由于Portlet Container并不是一个可以独立运行的container,它是离不开Web Server和Servlet Container的,所下载的Pluto中就包含了Tomcat5.5.9。由于Tomcat5.5.9所要求的JRE是5.0或以上,如果你的机子上装有5.0以上版本的JRE,那么你只需要将Pluto解压到某一个目录下(<PLUTO-HOME>),然后在console中进入<PLUTO-HOME>/bin目录,运行startup即可。这样Tomcat连同Pluto都启动了,接着我们访问http://localhost:8080/pluto/portal,就会出现如下界面,准备工作就大功告成了。

如果你的机子上只有JRE1.4的话,要让Tomcat跑起来我们就还得从Tomcat5.5.9的下载页面下载一个名为compat的zip file。然后把这个zipfile解开,里面包含着bin和common两个目录,bin目录下包含着jmx.jar文件,common目录下还包含着一个endorsed目录,该目录下包含着xml-apis.jar和xercesImpl.jar文件。我们只需要把jmx.jar复制到<PLUTO-HOME>/bin目录下,改掉<PLUTO-HOME>/common/endored目录下的两个jar文件的后缀,并将xml-apis.jar和xercesImpl.jar复制到该目录下即可。
准备工作完成以后,我们就开始咱们的第一个Portlet了——HelloWorld Portlet,具体的步骤请参考OnJava上的文章:What Is a Portlet, 在这里我就不罗嗦了。与这篇文章所介绍的不同,由于我们使用的是1.0.1-rc4,发布Portlet就不再像文章所说的那样复杂了,你也不需要有 Maven的基础,只需要通过Pluto的Admin页面将war文件上传并根据页面提示进行少许配置,就轻而易举地完成Portlet的发布了。发布完 成以后,需要重新启动Pluto,这是一个非常不方便的地方,但据说在一个版本的Pluto将支持Hot Deploy,发布Portlet就不需要重新启动了。
最后,我们通过浏览器访问http://localhost:8080/pluto/portal,你会发现在左边的Portal Page列表中多了一项——HelloWorld。点击该项,我们就可以看到这个最简单的Portlet了。

[教程]ASP.net MVC Framework框架安装正确步骤
1.运行安装文件,执行安装
安装后,因为默认安装的是针对英文版的VS2008,安装文件夹为1033,但是中文VS2008的安装文件夹为2052,因此需要将1033文件夹改名或将内容拷贝到2052文件夹下
具体步骤如下:
1.将C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\ItemTemplates\Web\MVC\1033
C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\ItemTemplatesCache\Web\MVC\1033
文件夹改名为2052
2.将C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\ProjectTemplates\Test\1033
C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\ProjectTemplates\Web\1033
C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\ProjectTemplatesCache\Test\1033
C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\ProjectTemplatesCache\Web\1033
拷贝到对应路径下的2052文件夹中
记得一定要将Test文件夹下的1033内容拷贝到2052中,否则创建项目时不会创建测试项目。
[软件]VS2008Team版序列号
VS2008破解方法非常简单,在开始>设置>控制面版>添加或删除程序>卸载vs.net2008>出现卸载界面>点击Next>输入上面CD-key ->出现成功画面即可完美将试用版升级成为正式版。
VS2008正式版序列号CDKEY:PYHYP-WXB3B-B2CCM-V9DX9-VDY8T
[教程]ASP.NET AJAX (Atlas) 拖放(Drag & Drop)功能6种实现
在Ajax程序中实现传统桌面程序中异常简单的拖放功能却并不是件容易的事情。然而Web上的拖放功能又如此的让人痴迷,所以几乎每个成熟的 Ajax类库都提供了自己的一套实现拖放的封装,ASP.NET AJAX (Atlas) 自然也不例外。本文将总结并简要分析ASP.NET AJAX (Atlas) 中拖放功能的6种不同的实现方法,希望能够帮助朋友们选出最适合实际需求的方法。
其中第1到第4种方案,在我 的《ASP.NET Ajax程序设计——第I卷:服务器端ASP.NET 2.0 AJAX Extensions与ASP.NET AJAX Control Toolkit》一书中有详细介绍(4月出版),本文中的代码和图示也节选自该书。第5第6种方案,在我的《ASP.NET 2.0 Ajax程序设计——第II卷:客户端Microsoft AJAX Library》一书中将有详细介绍(暂定7月出版)。
不过纵观这些解决方案,我很遗憾的发现,要么是使用简单,可定制能力差,要么就是可定制能力强,但使用起来要写很多代码。希望ASP.NET AJAX (Atlas) 团队能够再接再厉,努力把这个重要功能做得更好。或者我有哪种方法漏掉了,也请朋友们帮忙补充一下。
[1] 使用服务器端DragOverlayExtender或客户端DragOverlayBehavior
服务器端的DragOverlayExtender就是靠着客户端DragOverlayBehavior而实现的,前者是后者在服务器端的组件化封装,所以我们将二者放在一起讨论。
这 个“拖放”功能很简单,其实这只是个“拖拽”,而没有“投放”。也就是说,你可以随意将某个Panel在页面中拖来拖去,不过却没有什么固定的地方可以 “投放”,就像在Windows桌面上拖放某个窗口的位置一样——其实用处不大,也没有提供什么可定制能力。但它使用起来非常简单,也支持将Panel的 位置保存在Profile中。
下面是一段DragOverlayExtender的示例代码,至于DragOverlayBehavior的用法,请查看DragOverlayExtender页面生成的HTML代码:
<asp:Login ID="floatLogin" BackColor="white"
BorderStyle="Solid" BorderColor="black"
runat="server">
</asp:Login>
<asp:DragOverlayExtender ID="DragOverlayExtender2"
Enabled="true" TargetControlID="floatLogin"
runat="server" />
效果如下:

[2] 使用服务器端DragPanel
DragPanel是ASP.NET AJAX Control Toolkit中的一个扩展器控件。其功能基本与DragOverlayExtender和DragOverlayBehavior一样,且同样可以保存 Panel的位置信息至Profile中。不同之处在于,DragOverlayExtender和DragOverlayBehavior的拖拽实现方 式中,鼠标放在整个Panel的任何部分都可以开始拖拽,而DragPanel在进行拖拽时,只有鼠标放在指定的DragHandle(类似于 Windows窗口的标题栏部分)中才能开始拖拽。
对于DragHandle的扩展性和实用性,同样不敢恭维。
下面是一段示例代码:
<asp:Panel ID="floatPanel" CssClass="floatPanel" runat="server">
<asp:Panel ID="floatPanelHandle" CssClass="handle" runat="server">
窗口的拖动
</asp:Panel>
<div class="content">
在Windows中,对窗口的拖动似乎成了我们习以为常的事情。
………………
………………
Window窗口的表现行为一样。
</div>
</asp:Panel>
<ajaxToolkit:DragPanelExtender ID="dpe" TargetControlID="floatPanel"
DragHandleID="floatPanelHandle" runat="server">
</ajaxToolkit:DragPanelExtender>
效果如下:

[3] 使用服务器端ReorderList
ASP.NET AJAX Control Toolkit中的ReorderList控件将在页面中呈现出一个由数据绑定自动生成的条目列表。用户可以通过鼠标拖动某一项来直接改变该列表中条目彼 此之间的相对位置关系,且在拖动的过程中,ReorderList控件提供了丰富的、可定制的视觉效果。当用户在某个位置放开鼠标之后, ReorderList控件也将同样会自动通知与其绑定的数据源控件,以Ajax的异步或整页回送的同步方式更新服务器端数据。
ReorderList控件的使用比较简单(服务器端控件),功能也相当的丰富,扩展能力也不错。不过仍称不上最灵活,比如我们想把条目在多个ReorderList之间拖放,那么就没办法实现了——因此,不要指望它能实现WebPart那样的功能。
下面是一段示例代码:
<ajaxToolkit:ReorderList ID="musicList" CssClass="musicList"
DragHandleAlignment="Left" PostBackOnReorder="false"
DataSourceID="musicDataSource" DataKeyField="Id"
SortOrderField="Order" runat="server">
<ItemTemplate>
<ajaxToolkit:Rating ID="rating" runat="server"
CssClass="rating" StarCssClass="ratingStar"
FilledStarCssClass="filledRatingStar" EmptyStarCssClass="emptyRatingStar"
CurrentRating='<%# Bind("Rating") %>' MaxRating="5"
ReadOnly="true">
</ajaxToolkit:Rating>
</ItemTemplate>
<ReorderTemplate>
<div class="dragDue">
Drop Here!
</div>
</ReorderTemplate>
<DragHandleTemplate>
<asp:Label ID="lbTitle" CssClass="dragHandle"
ToolTip="Drag Me!" runat="server" Text='<%# Bind("Name") %>'>
</asp:Label>
</DragHandleTemplate>
</ajaxToolkit:ReorderList>
效果挺酷的:

[4] 使用UpdatePanel与ASP.NET AJAX中的新版本WebPart控件
ASP.NET 2.0中的WebPart相关的控件虽然非常丰富,易于使用且功能强大,我们在程序中也很需要它所提供的拖拽功能,但它却存在着两个致命的缺陷:
- 拖放功能只支持IE浏览器。
- 每次用户通过拖放改变配件的位置之后,页面总会自动进行回送以保存当前的设定。
其中第一个问题可以通过ASP.NET AJAX中的新版本WebPart控件搞定,第二个问题可以通过添加UpdatePanel搞定,本来在CTP版本中的ASP.NET AJAX里面,这些功能均已经完美实现了。谁知道在最新的Futures CTP中,却又不好用了。
既然现在已经不能用了,也就没必要分析其优点缺点了。不过还是给出一张截图吧,缅怀一下曾经的辉煌(注意,这可是在Firefox中啊!)……

[5] 使用客户端DragDropList
DragDropList定义于ASP.NET AJAX Futures CTP中,功能非常强大,且全部在客户端实现,给服务器端减轻了不少的压力。使用DragDropList实现第4种解决方案中的WebPart类似的效 果完全没有问题,不过唯一让人感到遗憾的就是,其扩展功能不是很好,用户虽然可以随便将某个Panel从一个区域拖到另一个区域,但拖拽的结果却很难持久 化下来……且使用起来也不是那么的容易,效率上更是不敢恭维……总之,这是个很让人矛盾的东西,有些鸡肋的感觉。
我曾经在《使用ASP.NET Atlas实现拖放(Drag & Drop)效果(下)》这篇文章中给出过DragDropList的示例程序,虽然是基于CTP版本的ASP.NET AJAX ,不过实际上并不需要多少修改就能运行于最新版本之上。感兴趣的朋友可以看看,也可以到官方论坛上搜索一下相关的主题。
[6] 在客户端自行实现IDragSource和IDropTarget接口
来到了这个解决方案,可以说你对ASP.NET AJAX中的拖拽实现有了一个较深入的了解。上面的DragDropList其实就是实现了IDragSource和IDropTarget接口,其中前 者用来定义可以被拖拽的项目,后者用来定义可以被投放的区域。详细理论上的说明,可以参考我的文章《使用ASP.NET Atlas实现拖放(Drag & Drop)效果(上)》,虽然目前已经有所变化,不过仍可以参考。
这种实现方案应该说是最为强大的了,想要什么功能,肯定都能实现。不过实际开发中的难度也不小——甚至可以说是相当复杂,Jeff Prosise的这篇文章《Implementing Drag-Drop in ASP.NET AJAX》给出了一个非常简单的示例程序,感兴趣想要学习的朋友不妨看看……
[教程]C#的反射机制实例
[教程]如何用C#实现依赖注入?
1. 问题的提出
开发中,尤其是大型项目的开发中,为了降低模块间、类间的耦合关系,比较提 倡基于接口开发,但在实现中也必须面临最终是“谁”提供实体类的问题。Martin Fowler在《Inversion of Control Containers and the Dependency Injection pattern》中也提到了标准的三种实现方式——Constructor Injection、Setter Injection和Interface Injection,很全面的阐释了这个问题。
对于C#而言,由于语法元素上本身要比Java丰富,如何实施注入还有些技巧和特色之处。这方面微软的ObjectBuilder是个不错的教科书,对三种标准方式的实现也都很到位,但就是有些庞大了。
本文中,笔者借鉴Martin Fowler的撰文,也通过一些精简的代码片断向读者介绍C#实现依赖注入的基本技巧。
我有个习惯,每天晚上要看天气预报,就以这个开始好了,先定义待注入对象的抽象行为描述,然后增加一个假的实体类,相关代码和单元测试如下:

C#
using System;
namespace VisionLogic.Training.DependencyInjection.Scenario
{
/// <summary>
/// 抽象注入对象接口
/// </summary>
public interface IWeatherReader
{
string Current { get;}
}
}
C#
using System;
namespace VisionLogic.Training.DependencyInjection.Scenario.Raw
{
/// <summary>
/// 伪造的一个实现类
/// </summary>
class FakeWeatherReader : IWeatherReader
{
public string Current { get { return string.Empty; } }
}
/// <summary>
/// 客户程序
/// </summary>
public class Client
{
protected IWeatherReader reader = new FakeWeatherReader();
public virtual string Weather
{
get
{
string current = reader.Current;
switch (current)
{
case "s": return "sunny";
case "r": return "rainy";
case "c": return "cloudy";
default:
return "unknown";
}
}
}
}
}
Unit Test
using Microsoft.VisualStudio.TestTools.UnitTesting;
using VisionLogic.Training.DependencyInjection.Scenario;
using VisionLogic.Training.DependencyInjection.Scenario.Raw;
namespace VisionLogic.Training.DependencyInjection.Scenario.UnitTest.Raw
{
[TestClass]
public class WeatherReaderTest
{
[TestMethod]
public void Test()
{
Client client = new Client();
Assert.AreEqual<string>("unknown", client.Weather);
}
}
}
问题就出现了,虽然美好的愿望是Client仅仅依赖抽象的IWeatherReader,但之前总要和一个实体类“轧”一道,那么实际的效果就是实体类 作了修改、重新编译了,Client也要处理,没有真正达到隔离的目的。依赖注入通过引入第三方责任者的方法,相对好的梳理了这个关系,这位重要的角色就 是一个Assembler类,他和实体类型打交道,对Client而言他总是可以根据约定,加工出需要的IWeatherReader。

2.进一步的分析
看上去,Client被解放了,但又套住了Assembler,为了尽量让他与实体类间松散些需要做什么呢?
首先要完成自己的职责:可以找到合适的实现类实例,不管是重新构造一个还是找个现成的。
既要根据需要加工接口IWeatherReader,又要让自己尽量不与大量的实体类纠缠在一起,最好的办法就是从.Net Framework中再找到一个“第三方”,这里选中了System.Activator。
还有就是当客户程序调用Assembler的时候,它需要知道需要通过哪个实现类的实例返回,该项工作一方面可以通过一个字典完成,也可以通过配置解决,两者应用都很普遍,怎么选择呢——抽象,提取一个接口,然后都实现。
由于本文主要介绍依赖注入的实现,为了简单起见,采用一个伪造的内存字典方式,而非基于System.Configuration的配置系统实现一个Assembler的协同类。

C# 新增一个用于管理抽象类型——实体类型映射关系的类型ITypeMap
using System;
using System.Collections.Generic;
namespace VisionLogic.Training.DependencyInjection.Scenario
{
/// <summary>
/// 考虑到某些类型没有无参的构造函数,增加了描述构造信息的专门结构
/// </summary>
public class TypeConstructor
{
private Type type;
private object[] constructorParameters;
public TypeConstructor(Type type, params object[] constructorParameters)
{
this.type = type;
this.constructorParameters = constructorParameters;
}
public TypeConstructor(Type type) : this(type, null) { }
public Type Type { get { return type; } }
public object[] ConstructorParameters { get { return constructorParameters; } }
}
/// <summary>
/// 管理抽象类型与实体类型的字典类型
/// </summary>
public interface ITypeMap
{
TypeConstructor this[Type target]{get;}
}
}
C# 实现一个Assembler类型,为了示例方便,同时实现了一个ITypeMap和IWeatherReader
using System;
using System.Collections.Generic;
namespace VisionLogic.Training.DependencyInjection.Scenario
{
/// <summary>
/// 测试用的实体类
/// </summary>
public class WeatherReaderImpl : IWeatherReader
{
private string weather;
public WeatherReaderImpl(string weather)
{
this.weather = weather;
}
public string Current
{
get { return weather; }
}
}
/// <summary>
/// 管理抽象类型与实际实体类型映射关系,实际工程中应该从配置系统、参数系统获得。
/// 这里为了示例方便,采用了一个纯内存字典的方式。
/// </summary>
public class MemoryTypeMap : ITypeMap
{
private Dictionary<Type, TypeConstructor> dictionary =
new Dictionary<Type, TypeConstructor>();
public static readonly ITypeMap Instance;
/// <summary>
/// Singleton
/// </summary>
private MemoryTypeMap(){}
static MemoryTypeMap()
{
MemoryTypeMap singleton = new MemoryTypeMap();
// 注册抽象类型需要使用的实体类型
// 该类型实体具有构造参数,实际的配置信息可以从外层机制获得。
singleton.dictionary.Add(typeof(IWeatherReader), new TypeConstructor(
typeof(WeatherReaderImpl), "s"));
Instance = singleton;
}
/// <summary>
/// 根据注册的目标抽象类型,返回一个实体类型及其构造参数数组
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public TypeConstructor this[Type type]
{
get
{
TypeConstructor result;
if (!dictionary.TryGetValue(type, out result))
return null;
else
return result;
}
}
}
public class Assembler<T>
where T : class
{
/// <summary>
/// 其实TypeMap工程上本身就是个需要注入的类型,可以通过访问配置系统获得,
/// 这里为了示例的方便,手工配置了一些类型映射信息。
/// </summary>
private static ITypeMap map = MemoryTypeMap.Instance;
public T Create()
{
TypeConstructor constructor = map[typeof(T)];
if (constructor != null)
{
if (constructor.ConstructorParameters == null)
return (T)Activator.CreateInstance(constructor.Type);
else
return (T)Activator.CreateInstance(
constructor.Type, constructor.ConstructorParameters);
}
else
return null;
}
}
}
Unit Test
using Microsoft.VisualStudio.TestTools.UnitTesting;
using VisionLogic.Training.DependencyInjection.Scenario;
namespace VisionLogic.Training.DependencyInjection.Scenario.UnitTest
{
[TestClass()]
public class AssemblerTest
{
[TestMethod]
public void Test()
{
IWeatherReader reader = new Assembler<IWeatherReader>().Create();
Assert.IsNotNull(reader);
Assert.AreEqual<System.Type>(typeof(WeatherReaderImpl), reader.GetType());
}
}
}
3.经典方式下的注入实现
在完成了Assembler这个基础环境后,就是怎么注入的问题了,下面是对三种方式的经典方法实现:
3.1 Constructor Injection方式
Unit Test – Constructor
using Microsoft.VisualStudio.TestTools.UnitTesting;
using VisionLogic.Training.DependencyInjection.Scenario;
namespace VisionLogic.Training.DependencyInjection.Scenario.UnitTest
{
[TestClass]
public class ConstructorInjectionTest
{
class Client
{
private IWeatherReader reader;
public Client(IWeatherReader reader)
{
this.reader = reader;
}
}
[TestMethod]
public void Test()
{
IWeatherReader reader = new Assembler<IWeatherReader>().Create();
Client client = new Client(reader);
Assert.IsNotNull(client);
}
}
}
3.2 Setter Injection方式
Unit Test – Setter
using Microsoft.VisualStudio.TestTools.UnitTesting;
using VisionLogic.Training.DependencyInjection.Scenario;
namespace VisionLogic.Training.DependencyInjection.Scenario.UnitTest
{
[TestClass]
public class SetterInjectionTest
{
class Client
{
private IWeatherReader reader;
public IWeatherReader Reader
{
get { return reader; }
set { reader = value; }
}
}
[TestMethod]
public void Test()
{
IWeatherReader reader = new Assembler<IWeatherReader>().Create();
Client client = new Client();
client.Reader = reader;
Assert.IsNotNull(client.Reader);
}
}
}
3.3 Interface Injection方式
Unit Test – Interface
using Microsoft.VisualStudio.TestTools.UnitTesting;
using VisionLogic.Training.DependencyInjection.Scenario;
namespace VisionLogic.Training.DependencyInjection.Scenario.UnitTest
{
[TestClass]
public class InterfaceInjectionTest
{
interface IClientWithWeatherReader
{
IWeatherReader Reader { get; set;}
}
class Client : IClientWithWeatherReader
{
private IWeatherReader reader;
#region IClientWithWeatherReader Members
public IWeatherReader Reader
{
get { return reader; }
set { reader = value; }
}
#endregion
}
[TestMethod]
public void Test()
{
IWeatherReader reader = new Assembler<IWeatherReader>().Create();
Client client = new Client();
IClientWithWeatherReader clientWithReader = client;
clientWithReader.Reader = reader;
Assert.IsNotNull(clientWithReader.Reader);
}
}
}
4. 用属性(Attribute)注入
C#还可以通过Attribute注入,Enterprise Library中大量使用这种方式将各种第三方机制加入到类系统中。例如:
•运行监控需要的Performance Counter。
•用于构造过程的指标信息。
•用于日志、密码处理。
•等等
注:Java语言虽然发展比较慢,但在Java 5种也提供了类似的Annotation的机制,换了个名字省去被评估为“抄袭”的嫌疑。)
为了演示方便,下面设计一个应用情景:
Scenario
1、 应用需要一个集中的机制了解系统中实际创建过多少个特定类型对象的实例,用于评估系统的Capacity要求。
2、 为了防止系统资源被用尽,需要控制每类对象实例数量。
怎么实现呢?如下:
•增加一个内存的注册器,登记每个类已经创建过的实例实例数量。
•然后给每个类贴个标签——Attribute,让Assembler在生成的对象的时候根据标签的内容把把登记到注册器。
4.1定义抽象业务实体
C#
using System;
namespace VisionLogic.Training.DependencyInjection.Scenario.Attributer
{
/// <summary>
/// 抽象的处理对象
/// </summary>
public interface IObjectWithGuid
{
string Guid { get; set;}
}
}
定义需要注入的限制接口,并用一个Attribute管理它
C#
using System;
namespace VisionLogic.Training.DependencyInjection.Scenario.Attributer
{
/// <summary>
/// 需要注入的用以限制最大数量的接口
/// </summary>
public interface ICapacityConstraint
{
int Max { get;}
}
public class CapacityConstraint : ICapacityConstraint
{
private int max;
public CapacityConstraint(){this.max = 0;} // 默认情况下不限制
public CapacityConstraint(int max) { this.max = max; }
public int Max { get { return max; } }
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ConstraintAttribute : Attribute
{
private ICapacityConstraint capacity;
public ConstraintAttribute(int max) { this.capacity = new CapacityConstraint(max); }
public ConstraintAttribute() { this.capacity = null; }
public ICapacityConstraint Capacity { get { return capacity; } }
}
}
Assembler上增加通过Attribute注入限制的响应
C#
using System;
using System.Collections.Generic;
namespace VisionLogic.Training.DependencyInjection.Scenario.Attributer
{
public class Assembler
{
/// <summary>
/// 登记相关类型对“最大容量”属性的使用情况
/// </summary>
private IDictionary<Type, ConstraintAttribute> attributeRegistry =
new Dictionary<Type, ConstraintAttribute>();
/// <summary>
/// 登记每个类型(如须受到“最大容量”属性限制的话),实际已经创建的对象数量
/// </summary>
private IDictionary<Type, int> usageRegistry = new Dictionary<Type, int>();
public T Create<T>()
where T : IObjectWithGuid, new()
{
ICapacityConstraint constraint = GetAttributeDefinedMax(typeof(T));
if ((constraint == null) || (constraint.Max <= 0)) // max <= 0 代表是不需要限制数量的。
return InternalCreate<T>();
else
{
if (usageRegistry[typeof(T)] < constraint.Max) // 检查是否超出容量限制
{
usageRegistry[typeof(T)]++; // 更新使用情况注册信息
return InternalCreate<T>();
}
else
return default(T);
}
}
// helper method
// 直接生成特定实例,并setter 方式注入其guid。
private T InternalCreate<T>()
where T : IObjectWithGuid, new()
{
T result = new T();
result.Guid = Guid.NewGuid().ToString();
return result;
}
/// helper method.
// 获取特定类型所定义的最大数量, 同时视情况维护attributeRegistry 和usageRegistry 的注册信息。
private ICapacityConstraint GetAttributeDefinedMax(Type type)
{
ConstraintAttribute attribute = null;
if (!attributeRegistry.TryGetValue(type, out attribute)) //新的待创建的类型
{
// 填充相关类型的“最大容量”属性注册信息
object[] attributes = type.GetCustomAttributes(typeof(ConstraintAttribute), false);
if ((attributes == null) || (attributes.Length <= 0))
attributeRegistry.Add(type, null);
else
{
attribute = (ConstraintAttribute)attributes[0];
attributeRegistry.Add(type, attribute);
usageRegistry.Add(type, 0); // 同时补充该类型的使用情况注册信息
}
}
if (attribute == null)
return null;
else
return attribute.Capacity;
}
}
}
4.2对方案的测试
C#
using Microsoft.VisualStudio.TestTools.UnitTesting;
using VisionLogic.Training.DependencyInjection.Scenario.Attributer;
namespace VisionLogic.Training.DependencyInjection.Scenario.UnitTest.Attributer
{
[TestClass()]
public class AssemblerTest
{
public abstract class ObjectWithGuidBase : IObjectWithGuid
{
protected string guid;
public virtual string Guid
{
get { return guid; }
set { guid = value; }
}
}
[Constraint(2)] // 通过属性注入限制
public class ObjectWithGuidImplA : ObjectWithGuidBase { }
[Constraint(0)] // 通过属性注入限制
public class ObjectWithGuidImplB : ObjectWithGuidBase { }
[Constraint(-5)] // 通过属性注入限制
public class ObjectWithGuidImplC : ObjectWithGuidBase { }
public class ObjectWithGuidImplD : ObjectWithGuidBase { }
[TestMethod]
public void Test()
{
Assembler assembler = new Assembler();
for (int i = 0; i < 2; i++)
Assert.IsNotNull(assembler.Create<ObjectWithGuidImplA>());
Assert.IsNull(assembler.Create<ObjectWithGuidImplA>()); // 最多两个
for (int i = 0; i < 100; i++)
Assert.IsNotNull(assembler.Create<ObjectWithGuidImplB>()); // 不限制
for (int i = 0; i < 100; i++)
Assert.IsNotNull(assembler.Create<ObjectWithGuidImplC>()); // 不限制
for (int i = 0; i < 100; i++)
Assert.IsNotNull(assembler.Create<ObjectWithGuidImplD>()); // 不限制
}
}
}
5. 进一步讨论
上面的例子虽然仅仅通过Attribute注入了一个容量限制接口,但完全可以为他增加一个容器,可以把一组限制接口借助ConstraintAttribute这个通道注入进去,并在Assembler中生效。
6.其他问题
实际项目中,为了满足多核系统的需要,Assembler往往和目标对象分别运行在主进程和具体某个线程之中,如何线程安全的注入是必须面临并谨慎设计的问题。
Mikel