Asp.Net开发架构设计

转:http://www.cnblogs.com/xiaozhuang/archive/2008/08/15/1268907.html

这几天园子里关于软件架构的讨论还是相当激烈,大家都想要一种能最大限度的降低各层之间依赖关系的的架构来适应变化的需求,谁都不喜欢改一点而动全身,尽量降低各层的改动产生的相互影响。

本篇我以理论和实践(源代码)两个方面和大家探讨一下我的方案,希望大家多提宝贵意见。

一、软件架构的概念问题,什么是软件的架构?我的理解是:软件的架构包括两个方面的内容,一个是软件的开发架构,一个是软件的部署架构,所谓部署架构就是指部署时的分布式,集群等设计问题;开发架构就是我们平常说的软件分层设计问题,也就是我们今天要谈的问题。

二、何谓分层?分层的方式有几种?分层也就是把一个大的软件解决方案分成多个项目进行开发,分为三种,一种是按照代码的功能层次进行分层,分为数据库访问层,业务逻辑层,UI层等,一种是按照要实现的功能模块进行分层,例如新闻管理层,博客管理层等,第三种就是把前两者结合起来进行分层:先按照代码功能划分好层次,然后再在每一层中分层成各个功能模块。

三、面向接口能够降低各层之间的依赖关系吗?还需要什么?面向接口只是把对对象的直接调用转到了对接口的调用身上,但对接口的调用也需要对象的实例化为前提,因为有对象实例化的存在,所以接口并不能屏蔽掉背后的对象来完成功能;在这种情况下,依赖注入/控制反转等概念应运而生,IOC容器出现了,它能够让对象自动实例化,并管理对象的生存空间。

四、说了这么多,到底应该怎样分?我们不妨从软件的开发过程上来考虑,做软件的都知道我们要进行需求分析,要有需求分析文档,需求分析的任务就是把客户的需求转变为对软件功能的需求,主要内容包括:软件要实现那些功能模块,每个功能模块下包含哪些业务操作,这些业务操作需要哪些页面(或者窗口)支持,这些页面大概都是什么样子的,上面都有什么?有了这些信息,我们就可以开始我们的系统设计了。

我的方案中各个项目的引用关系如下:

说明:

Web:UI层,Asp.net Web应用程序项目,提供用户界面。

UnityConfig:依赖注入配置层,类库项目,对Service层和Business层进行实现配置以提供给Web调用Service层。

Service:为Web层提供服务,类库项目,里面全是接口。

Business:商业逻辑层,通过调用DA数据访问层和Entities层来实现Service层的服务接口。类库项目。

Entities:实体层,类库项目,存放业务实体,贫血型实体。

请注意:在上面的引用关系中,Web层并没有直接引用Business层。

现在假设我们有这样一个需求文档:它只需要实现一个功能模块,就是查找并显示雇员列表。页面形式如图:

首先我们根据需求,先设计服务,因为只有一个功能模块,所以我们只需要一个接口类,再因为只有一个操作功能(查找),所以我们只需要一个服务方法,那么我们在Service层建立一个接口类:IQueryEmployeeService,并添加一个接口方法:QueryEmployee.

根据页面所示,我们这个服务方法需要两个对象参数,一个是查找的条件,一个是查找的结果;这样我们就得到了两个实体类。在Entities建立分别命名为QueryEntry和ListEntry。

代码如下:

namespace Xiaozhuang.Service

{

public interface IQueryEmployeeService

{

/// <summary>

/// 查询雇员信息

/// </summary>

/// <param name="queryentity"></param>

/// <returns></returns>

List<ListEntry> 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:" + EmployeeName + "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<ListEntry> QueryEmployee(QueryEntry queryentry)

{

List<ListEntry> listEntry = new List<ListEntry>();

ListEntry entry1 = new ListEntry() { EmployeeID = "1", EmployeeName = "雇员1", EmployeeSex = "男", DepartmentName = "部门1", EmployeeAge = 30, MobilePhone = "123546789" };

ListEntry entry2 = new ListEntry() { EmployeeID = "2", EmployeeName = "雇员2", EmployeeSex = "女", DepartmentName = "部门2", EmployeeAge = 29, MobilePhone = "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<IQueryEmployeeService, QueryEmployeeBusiness>();

return container;

}

}

}

当然你也可以把这个配置写到Web。Config中。

接下来的问题就是怎样在Web层调用这个服务的问题了,这个问题其实还是比较复杂的,文章太长了大家都看烦了,我将在下次再详细说说这个问题。

上回说到,我们配置了一下UnityConfig层,在这个层中定义了一个IContainerAccessor的接口和一个返回IUnityContainer类型的方法,这个方法的主要作用就是把Service层中的接口类和Business层中的接口实现类装配到UnityContainer中并返回,也就是指定那个接口实现类去实现某个接口类(晕,好像有点绕口啊)。

Xiaozhuang.UnityConfig

namespace Xiaozhuang.UnityConfig

{

public interface IContainerAccessor

{

IUnityContainer Container { get; }

}

public class UnityContainerConfig

{

public IUnityContainer GetIUnityContainer()

{

IUnityContainer container = new UnityContainer();

container.RegisterType<IQueryEmployeeService, QueryEmployeeBusiness>();

return container;

}

}

}

好了,现在终于轮到Web层了,要实现在Asp.Net页面中直接调用能够服务接口而不用从Unity容器中再去取出来,就要把Unity容器中的接口注入到页面中去,分两步走:第一步,在Global.Asax.cs中实现UnityConfig层中的IContainerAccessor接口,并把UnityConfig层返回的IUnityContainer赋值给实现接口的全局静态属性。

Code

namespace Xiaozhuang.Web

{

public class Global : System.Web.HttpApplication, IContainerAccessor

{

Members#region Members

private static IUnityContainer _container;

#endregion

Properties#region Properties

/**//// <summary>

/// The Unity container for the current application

/// </summary>

public static IUnityContainer Container

{

get

{

return _container;

}

set

{

_container = value;

}

}

#endregion

IContainerAccessor Members#region IContainerAccessor Members

/**//// <summary>

/// Returns the Unity container of the application

/// </summary>

IUnityContainer IContainerAccessor.Container

{

get

{

return Container;

}

}

#endregion

Application Events#region Application Events

protected void Application_Start(object sender, EventArgs e)

{

BuildContainer();

}

protected void Session_Start(object sender, EventArgs e)

{

}

protected void Application_BeginRequest(object sender, EventArgs e)

{

}

protected void Application_AuthenticateRequest(object sender, EventArgs e)

{

}

protected void Application_Error(object sender, EventArgs e)

{

}

protected void Session_End(object sender, EventArgs e)

{

}

protected void Application_End(object sender, EventArgs e)

{

CleanUp();

}

#endregion

Methods#region Methods

private static void BuildContainer()

{

UnityContainerConfig config = new UnityContainerConfig();

Container = config.GetIUnityContainer();

}

private static void CleanUp()

{

if (Container != null)

{

Container.Dispose();

}

}

#endregion

}

}

接下来要把UnityContainer中的接口注入到页面中去。建立一个BasePage的泛型类,先获取到从Gloab.Asax传过来的应用程序实例,转化为UnityContainer,利用BuildUp方法注入到页面中去。

BasePage

namespace Foresee.Web

{

public abstract class BasePage<T> : 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.GetType(), "context", "<script>alert('当前Http上下文为空,请与系统管理员联系!');</script>");

}

var accessor = context.ApplicationInstance as IContainerAccessor;

if (accessor == null)

{

ClientScript.RegisterClientScriptBlock(this.GetType(), "context", "<script>alert('当前应用程序实例为空,请与系统管理员联系!');</script>");

}

var container = accessor.Container;

if (container == null)

{

ClientScript.RegisterClientScriptBlock(this.GetType(), "context", "<script>alert('未找到依赖注入容器,请与系统管理员联系!');</script>");

}

container.BuildUp(this as T);

}

}

}

我们不止在页面中要调用接口,也要在UserControl中调用,那么我们就参照上面的页面基类建立一个UserControl的泛型基类。

BaseUserControl

namespace Foresee.Web

{

public abstract class BaseUserControl<T> : UserControl where 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", "<script>alert('当前Http上下文为空,请与系统管理员联系!');</script>");

}

var accessor = context.ApplicationInstance as IContainerAccessor;

if (accessor == null)

{

this.Page.ClientScript.RegisterClientScriptBlock(this.Page.GetType(), "context", "<script>alert('当前应用程序实例为空,请与系统管理员联系!');</script>");

}

var container = accessor.Container;

if (container == null)

{

this.Page.ClientScript.RegisterClientScriptBlock(this.Page.GetType(), "context", "<script>alert('未找到依赖注入容器,请与系统管理员联系!');</script>");

}

container.BuildUp(this as T);

}

}

}

接下来我们建立一个UserControl文件,在里面调用查询雇员的服务接口,并绑定到ListView控件上,具体代码如下:

Code

namespace Xiaozhuang.Web

{

public partial class EmployeeList : BaseUserControl<EmployeeList>

{

#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<T>.UserControl基类,这样这个用户控件就可以实现注入了,我们只需要在属性上增加Dependency标记就可以用属性注入的方式来调用接口方法,当然你也可以通过方法注入的方式来实现。

接下来我们要用Asp.net Ajax调用这个UserControl来生成HTML,给页面上使用,我们先建立两个类ControlPage和ViewManager

Code

namespace Xiaozhuang.Web

{

public class ControlPage : Page

{

public override void VerifyRenderingInServerForm(Control control)

{

//base.VerifyRenderingInServerForm(control);

}

}

}

namespace Xiaozhuang.Web

{

/// <summary>

/// A generic user control rendering helper, basically you initialise the view manager and

/// call render to render that control, but the benifit of this version is you can access the control

/// the view manager is rendering and can set custom properties etc.

/// </summary>

/// <typeparam name="T">The type of the control you are rendering</typeparam>

public class ViewManager<T> where T : Control

{

#region Properties

private T _control = default(T);

/// <summary>

/// Gives you access to the control you are rendering allows

/// you to set custom properties etc.

/// </summary>

public T Control

{

get

{

return _control;

}

}

// Used as a placeholder page to render the control on.

private ControlPage _holder = null;

#endregion

#region Constructor

/// <summary>

/// Default constructor for this view manager, pass in the path for the control

/// that this view manager is render.

/// </summary>

/// <param name="inPath"></param>

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

/// <summary>

/// Renders the current control.

/// </summary>

/// <returns></returns>

public string Render()

{

StringWriter sw = new StringWriter();

// Execute the page capturing the output in the stringwriter.

HttpContext.Current.Server.Execute(_holder, sw, false);

// Return the output.

return sw.ToString();

}

#endregion

}

}

ControlPage类是一个简单的继承Page的类,里面重载VerifyRenderingInServerForm方法的作用是防止在UserControl生成HTML的时候如果UserControl中有服务器控件而出现的“服务器控件必须放在Form ruanat=‘server’”的错误!ViewManager类的作用是把在服务器端UserControl装在ControlPage页面中用Excute方法执行一遍并用Render方法获取到执行后输出的HTML字符串。

接下来我们到页面中去,在页面类中建立一个输出HTML的静态带WebService标记的方法,如下

Code

[WebMethod()]

public static string GetDataPage(int page, string departmentID, string EmpName, string EmpAge)

{

// Create an instance of our viewmanager.

ViewManager<EmployeeList> man = new ViewManager<EmployeeList>("~/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中。如下

Code

<script type="text/javascript">

var currentPage = 0;

function LoadPage(page) {

var departmentID = document.getElementById("txtDept").value;

var empName = document.getElementById("txtName").value;

var empAge = document.getElementById("txtAge").value;

PageMethods.GetDataPage(page,departmentID,empName,empAge, function(result) {

// We loaded our data populate our div.

document.getElementById("DivContent").innerHTML = result;

},

function(error) {

alert(error.get_message());

});

}

Sys.Application.add_load(LoadPage);

</script>

至此写完,其实这个生成html的方法我用了很久了,本来这次是写架构设计的,给扯到这上面来了,也许这也算是架构设计的一部分吧。

运行效果如下:

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/xiaokuang513204/archive/2009/10/29/4743739.aspx