Asp.Net开发架构设计


Asp.Net开发架构设计 这几天园子里关于软件架构的讨论还是相当激烈,大家都想要一种能最大限度的降低 各层之间依赖关系的的架构来适应变化的需求,谁都不喜欢改一点而动全身,尽量降低各 层的改动产生的相互影响。 本篇我以理论和实践(源代码)两个方面和大家探讨一下我的方案,希望大家多提宝贵意 见。 一、软件架构的概念问题,什么是软件的架构?我的理解是:软件的架构包括两个方面的内 容,一个是软件的开发架构,一个是软件的部署架构,所谓部署架构就是指部署时的分布 式,集群等设计问题;开发架构就是我们平常说的软件分层设计问题,也就是我们今天要 谈的问题。 二、何谓分层?分层的方式有几种?分层也就是把一个大的软件解决方案分成多个项目进 行开发,分为三种,一种是按照代码的功能层次进行分层,分为数据库访问层,业务逻辑 层,UI 层等,一种是按照要实现的功能模块进行分层,例如新闻管理层,博客管理层等, 第三种就是把前两者结合起来进行分层:先按照代码功能划分好层次,然后再在每一层中 分层成各个功能模块。 三、面向接口能够降低各层之间的依赖关系吗?还需要什么?面向接口只是把对对象的直 接调用转到了对接口的调用身上,但对接口的调用也需要对象的实例化为前提,因为有对 象实例化的存在,所以接口并不能屏蔽掉背后的对象来完成功能;在这种情况下,依赖注 入/控制反转等概念应运而生,IOC 容器出现了,它能够让对象自动实例化,并管理对象的 生存空间。 四、说了这么多,到底应该怎样分?我们不妨从软件的开发过程上来考虑,做软件的都知 道我们要进行需求分析,要有需求分析文档,需求分析的任务就是把客户的需求转变为对 软件功能的需求,主要内容包括:软件要实现那些功能模块,每个功能模块下包含哪些业 务操作,这些业务操作需要哪些页面(或者窗口)支持,这些页面大概都是什么样子的, 上面都有什么?有了这些信息,我们就可以开始我们的系统设计了。 我的方案中各个项目的引用关系如下: 说明: Web:UI 层,Asp.net Web 应用程序项目,提供用户界面。 UnityConfig:依赖注入配置层,类库项目,对 Service 层和 Business 层进行实现配置 以提供给 Web 调用 Service 层。 Service:为 Web 层提供服务,类库项目,里面全是接口。 Business:商业逻辑层,通过调用 DA 数据访问层和 Entities 层来实现 Service 层的服务 接口。类库项目。 Entities:实体层,类库项目,存放业务实体,贫血型实体。 请注意:在上面的引用关系中,Web 层并没有直接引用 Business 层。 现在假设我们有这样一个需求文档:它只需要实现一个功能模块,就是查找并显示雇员列 表。页面形式如图: 首先我们根据需求,先设计服务,因为只有一个功能模块,所以我们只需要一个接口类, 再因为只有一个操作功能(查找),所以我们只需要一个服务方法,那么我们在 Service 层建立一个接口类:IQueryEmployeeService,并添加一个接口方法:QueryEmploye e. 根据页面所示,我们这个服务方法需要两个对象参数,一个是查找的条件,一个是查找的 结果;这样我们就得到了两个实体类。在 Entities 建立分别命名为 QueryEntry 和 ListE ntry。 代码如下: namespace Xiaozhuang.Service { public interface IQueryEmployeeService { /// /// 查询雇员信息 /// /// /// List QueryEmployee(QueryEntry queryentry); } } namespace Xiaozhuang.Entities { public class QueryEntry { public string DepartmentID { get; set; } public string EmployeeName { get; set; } public string EmployeeAge { get; set; } public override string ToString() { return "DeaprtmentID:" + DepartmentID + "EmployeeName:" + Employee Name + "EmployeeAge:" + EmployeeAge; } } } namespace Xiaozhuang.Entities { public class ListEntry { public string EmployeeID { get; set; } public string EmployeeName { get; set; } public string EmployeeSex { get; set; } public int EmployeeAge { get; set; } public string DepartmentName { get; set; } public string MobilePhone { get; set; } public override string ToString() { return "EmployeeID:" + EmployeeID + "EmployeeName:" + EmployeeName + "EmployeeSex:" + EmployeeSex; } } } 请注意,业务实体的设计完全是参照页面而来,对实体字段类型的设计也是参照页面而来, 多数都是 String 类型,因为这个时候并不知道打算在数据库中怎么存储这些数据。 当然在实际的项目中,因为那个部门列表还需要建立部门的业务实体,这里简单其间略去, 有了这些业务实体和服务接口,我们就可以设计数据库了,数据库设计完成后,系统设计 的工作就完成了。 接下来就是在 Business 层具体的实现这个查找的方法了: namespace Xiaozhuang.Business { public class QueryEmployeeBusiness :IQueryEmployeeService { #region IQueryEmployeeService 成员 public List QueryEmployee(QueryEntry queryentry) { List listEntry = new List(); ListEntry entry1 = new ListEntry() { EmployeeID = "1", EmployeeNam e = "雇员 1", EmployeeSex = "男", DepartmentName = "部门 1", EmployeeAge = 30, M obilePhone = "123546789" }; ListEntry entry2 = new ListEntry() { EmployeeID = "2", EmployeeNam e = "雇员 2", EmployeeSex = "女", DepartmentName = "部门 2", EmployeeAge = 29, M obilePhone = "123546789" }; listEntry.Add(entry1); listEntry.Add(entry2); return listEntry; } #endregion } } 此处略去从数据库查询的方法,可以用 Linq to sql 实现或者其他 Orm 工具。 接下来配置一下我们的 UnityConfig 层,就可以在 Web 层访问这个查找雇员的服务了。 namespace Xiaozhuang.UnityConfig { public interface IContainerAccessor { IUnityContainer Container { get; } } public class UnityContainerConfig { public IUnityContainer GetIUnityContainer() { IUnityContainer container = new UnityContainer(); container.RegisterType(); return container; } } } 当然你也可以把这个配置写到 Web.Config 中。 我们配置了一下 UnityConfig 层,在这个层中定义了一个 IContainerAccessor 的接口和 一个返回 IUnityContainer 类型的方法,这个方法的主要作用就是把 Service 层中的接口 类和 Business 层中的接口实现类装配到 UnityContainer 中并返回,也就是指定那个接 口实现类去实现某个接口类(晕,好像有点绕口啊)。 namespace Xiaozhuang.UnityConfig { public interface IContainerAccessor { IUnityContainer Container { get; } } public class UnityContainerConfig { public IUnityContainer GetIUnityContainer() { IUnityContainer container = new UnityContainer(); container.RegisterType(); return container; } } } 好了,现在终于轮到 Web 层了,要实现在 Asp.Net 页面中直接调用能够服务接口而不用 从 Unity 容器中再去取出来,就要把 Unity 容器中的接口注入到页面中去,分两步走:第 一步,在 Global.Asax.cs 中实现 UnityConfig 层中的 IContainerAccessor 接口,并把 UnityConfig 层返回的 IUnityContainer 赋值给实现接口的全局静态属性。 namespace Xiaozhuang.Web { public class Global : System.Web.HttpApplication, IContainer Accessor { #region Members private static IUnityContainer _container; #endregion #region Properties /// /// The Unity container for the current application /// public static IUnityContainer Container { get { return _container; } set { _container = value; } } #endregion #region IContainerAccessor Members /// /// Returns the Unity container of the application /// IUnityContainer IContainerAccessor.Container { get { return Container; } } #endregion #region Application Events protected void Application_Start(object sender, EventArg s e) { BuildContainer(); } protected void Session_Start(object sender, EventArgs e) { } protected void Application_BeginRequest(object sender, E ventArgs e) { } protected void Application_AuthenticateRequest(object se nder, EventArgs e) { } protected void Application_Error(object sender, EventArg s e) { } protected void Session_End(object sender, EventArgs e) { } protected void Application_End(object sender, EventArg s e) { CleanUp(); } #endregion #region Methods private static void BuildContainer() { UnityContainerConfig config = new UnityContainerConf ig(); Container = config.GetIUnityContainer(); } private static void CleanUp() { if (Container != null) { Container.Dispose(); } } #endregion } } 接下来要把 UnityContainer 中的接口注入到页面中去。建立一个 BasePage 的泛型类, 先获取到从 Gloab.Asax 传过来的应用程序实例,转化为 UnityContainer,利用 BuildU p 方法注入到页面中去。 namespace Foresee.Web { public abstract class BasePage : Page where T : class { protected override void OnPreInit(EventArgs e) { InjectDependencies(); base.OnPreInit(e); } protected virtual void InjectDependencies() { var context = HttpContext.Current; if (context == null) { ClientScript.RegisterClientScriptBlock(this.GetTy pe(), "context", ""); } var accessor = context.ApplicationInstance as IContai nerAccessor; if (accessor == null) { ClientScript.RegisterClientScriptBlock(this.GetTy pe(), "context", ""); } var container = accessor.Container; if (container == null) { ClientScript.RegisterClientScriptBlock(this.GetTy pe(), "context", ""); } container.BuildUp(this as T); } } } 我们不止在页面中要调用接口,也要在 UserControl 中调用,那么我们就参照上面的页面 基类建立一个 UserControl 的泛型基类。 namespace Foresee.Web { public abstract class BaseUserControl : UserControl wher e T : class { protected override void OnInit(EventArgs e) { InjectDependencies(); base.OnInit(e); } protected virtual void InjectDependencies() { var context = HttpContext.Current; if (context == null) { this.Page.ClientScript.RegisterClientScriptBlock (this.Page.GetType(), "context", ""); } var accessor = context.ApplicationInstance as IContai nerAccessor; if (accessor == null) { this.Page.ClientScript.RegisterClientScriptBlock (this.Page.GetType(), "context", ""); } var container = accessor.Container; if (container == null) { this.Page.ClientScript.RegisterClientScriptBlock (this.Page.GetType(), "context", ""); } container.BuildUp(this as T); } } } 接下来我们建立一个 UserControl 文件,在里面调用查询雇员的服务接口,并绑定到 List View 控件上,具体代码如下: namespace Xiaozhuang.Web { public partial class EmployeeList : BaseUserControl { #region Properties [Dependency] public IQueryEmployeeService instance { set; get; } public QueryEntry queryentry { set; get; } #endregion protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { try { ListView1.DataSource = instance.QueryEmployee (queryentry); ListView1.DataBind(); } catch { Response.Write("系统运行错误,请与管理员联系!"); } } } } } 这个 EmployeeList 继承自 BaseUserControl.UserControl 基类,这样这个用户控 件就可以实现注入了,我们只需要在属性上增加 Dependency 标记就可以用属性注入的方 式来调用接口方法,当然你也可以通过方法注入的方式来实现。 接下来我们要用 Asp.net Ajax 调用这个 UserControl 来生成 HTML,给页面上使用,我 们先建立两个类 ControlPage 和 ViewManager namespace Xiaozhuang.Web { public class ControlPage : Page { public override void VerifyRenderingInServerForm(Contro l control) { //base.VerifyRenderingInServerForm(control); } } } namespace Xiaozhuang.Web { /// /// A generic user control rendering helper, basically you in itialise the view manager and /// call render to render that control, but the benifit of th is version is you can access the control /// the view manager is rendering and can set custom properti es etc. /// /// The type of the control you are rende ring public class ViewManager where T : Control { #region Properties private T _control = default(T); /// /// Gives you access to the control you are rendering all ows /// you to set custom properties etc. /// public T Control { get { return _control; } } // Used as a placeholder page to render the control on. private ControlPage _holder = null; #endregion #region Constructor /// /// Default constructor for this view manager, pass in th e path for the control /// that this view manager is render. /// /// public ViewManager(string path) { //Init the holder page _holder = new ControlPage(); // Create an instance of our control _control = (T)_holder.LoadControl(path); // Add it to our holder page. _holder.Controls.Add(_control); } #endregion #region Rendering /// /// Renders the current control. /// /// public string Render() { StringWriter sw = new StringWriter(); // Execute the page capturing the output in the strin gwriter. HttpContext.Current.Server.Execute(_holder, sw, fals e); // Return the output. return sw.ToString(); } #endregion } } ControlPage 类是一个简单的继承 Page 的类,里面重载 VerifyRenderingInServerFor m 方法的作用是防止在 UserControl 生成 HTML 的时候如果 UserControl 中有服务器控 件而出现的“服务器控件必须放在 Form ruanat=‘server’”的错误!ViewManager 类的 作用是把在服务器端 UserControl 装在 ControlPage 页面中用 Excute 方法执行一遍并用 Render 方法获取到执行后输出的 HTML 字符串。 接下来我们到页面中去,在页面类中建立一个输出 HTML 的静态带 WebService 标记的方 法,如下 [WebMethod()] public static string GetDataPage(int page, string departm entID, string EmpName, string EmpAge) { // Create an instance of our viewmanager. ViewManager man = new ViewManager("~/EmployeeList.ascx"); QueryEntry queryentry = new QueryEntry(); queryentry.DepartmentID = departmentID; queryentry.EmployeeName = EmpName; queryentry.EmployeeAge = EmpAge; man.Control.queryentry = queryentry; return man.Render(); } 这个方法的作用是吧查询的参数传递给 EmployeeList 用户控件,通过 ViewManager 执 行并输出 HTML 字符串,在 Aspx 页面中用 Asp.Net Ajax 代码来调用这个方法,并把返 回的 html 填充到相应的 Div 中。如下 至此写完,其实这个生成 html 的方法我用了很久了,本来这次是写架构设计的,给扯到这 上面来了,也许这也算是架构设计的一部分吧。 运行效果如下:
还剩22页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 15 金币 [ 分享pdf获得金币 ] 4 人已下载

下载pdf

pdf贡献者

jelly0812

贡献于2011-12-29

下载需要 15 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf