在
dot Net应用开发中,如何利用对象的概念,方便、快速、灵活地创建稳定的软件系统,是许多软件开发公司的目标,本文基于一个通用的
dot Net开发框架——Class Engine,简要阐述对象技术的使用,以及Class如何与界面结合,组成整个软件开发流程,下面简称此框架为NGSDF,要了解更多的信息,请发Email至(jiangjian2002_cn@sina.com)
l
前言
l
Class Engine的体系结构
l
Class Engine 的特性
l
Class Engine 的使用
l
O/R 映射定义
l
Class Engine 在ASP.Net开发中的应用
l
由NGSDF实现零代码开发
l
NGSDF提供的其他功能
l
总结
一、前言
随着 dot Net 技术的推广,越来越多的公司正在向 dot Net 转型,采用
dot Net 开发系统,但是如何方便、快速、灵活地创建稳定的软件系统,是许多软件开发公司追求的目标。为实现这个梦想,通用的
dot Net开发框架——Class Engine孕育而生。设计人员可以专注于部件的功能设计,也就是Class的定义
。
二、Class
Engine 的体系结构
Class
Engine 被设计成由客户端来管理对象状态,持久化管理器(DataObjectManager,负责持久化对象获取、创建、更新、删除)不管理持久化对象的状态,也就是说不存在一个"容器"来保存所有管理器创建的对象,而是根据客户端的请求进行对象的获取、创建、更新、删除等操作。这使得开发者只需要定义一个可序列化的实体对象,即可实现分布式应用的对象持久化。
Class
Engine 客户/服务器模式 O/R Mapping
Class
Engine考虑在Microsoft Windows平台下分布式应用存在不同的应用模式,典型的如Entity-Collection-Class模式(简称为ECC模式),这些构架模式代表了整个应用的结构,它根据不同的应用特征变化。Class
Engine不期望提供一个通用的分布式应用的构架模式,而是允许在Class
Engine持久化框架的核心服务上扩展不同的应用模式来满足系统的要求。
基于Class
Engine的应用框架
三、Class
Engine 的特性
1、支持的数据源
Class
Engine仅支持数据源为关系型数据库的对象持久化,目标数据库需要提供相应的Microsoft
.Net Data Provider引擎,支持的数据库包括Oracle,
Sybase, SQLServer, DB2, Access, Dabase, Foxpro, VFP
2、映射定义
Class
Engine目前支持的映射配置
- 代码文档属性元数据定义。利用映射属性类在持久化类或类的属性定义。
3、事务支持
Class
Engine本身并不管理事务,但当在事务环境中时,支持事务的完成标志(Complete)和异常时取消(Abort)。
4、其他特性
- 基于客户/服务器模式的框架。
- 自动属性类型匹配。对象属性与字段映射时不需要定义数据类型,简化了映射的定义。
- 支持持久化对象的关联。允许建立不同持久化对象之间的关联,定义关联多重性(一对一、一对多)。
- 事务支持。Class
Engine本身并不管理事务,但提供对事务的控制(同意或取消事务)。
当前Class
Engine还不支持"懒装载"(Lazy Loading)或"按需装载"(On-Demand
Loading)。
四、Class
Engine 使用
1、定义一个持久化类
持久化类实际上是一个定义了属性(Property)或字段(Field)的类,因此持久化类的定义非常简单,但持久化类不能是抽象类并且需要有一个无参数的public的构造化函数。
字段或属性只有被定义为持久化属性时才会进行持久化,持久化属性的数据类型必须定义为:
- .Net框架的基本类型
- Byte数组(二进制数据)
- 实现System.Collections.IList的类
- 其它持久化类(对象关联时)
下面是一个简单的持久化类"Customer"的定义:
[C#]
public class Customer
{
//必须定义无参数的构造函数
public Customer()
{
}
public string Id { get; set; }
public string Name {get; set; }
public string Phone {get; set; }
}
持久化类的映射配置可以使用属性在类上定义。类映射必须定义至少一个主键。有关对象映射配置的定义参见O/R映射定义
Customer类的采用属性进行映射配置的定义如下:
[C#]
[ObjectMapping("customer","customer")] //定义映射的表名
public class Customer
{
//必须定义无参数的构造函数
public Customer()
{
}
[PropertyMapping("id",true)] //定义对象属性映射的数据库字段,true表示本属性为主键
public string Id { get; set; }
[PropertyMapping("name")]
public string Name {get; set; }
[PropertyMapping("phone")]
public string Phone {get; set; }
}
Class
Engine并不定义属性映射的字段类型,Class
Engine采用自动匹配的方式进行处理。.
2、管理持久化对象
持久化对象通过数据对象管理器(DataObjectManager)来进行访问。DataObjectManager提供了对象的获取、新增、更新和删除接口。
新增持久化对象实例
新增对象到持久存储中,首先必须创建一个新持久化对象实例,然后利用DataObjectManager接口将新对象保存到持久存储中
下面的示例简单演示了一个创建新Customer对象到持久存储中:
[C#]
Customer customer = new Customer(); //创建持久化对象实例
customer.Id = "0001"; //设置对象属性
customer.Name = "john";
customer.Phone = "555-55555";
customer.Complete(); //保存到持久化存储中
获取持久化对象实例
持久化对象实例必须通过对象的主键获取,如果主键只有一个属性,则可以直接利用属性定义的类型的对象;如果主键有多个属性,则需要定义一个主键对象来访问。
下面的示例简单演示了存在在持久存储中的Customer实例,因为Customer的主键只定义个一个属性:Id属性,因此可以直接用Id值获取对象实例:
[C#]
//因为同一个DataObjectManager可以为多个持久化类服务,因此需要指定要返回的对象类型,这里通过
typeof(Customer)获得。
Customer customer = (Customer) DataObjectManager.Instance.GetObject(typeof(Customer),"0001");
更新持久化对象实例
当一个持久化对象被修改后需要更新,已保存这种修改。
下面的示例演示了保存修改后的Customer实例:
[C#]
//首先获得对象实例。这步不是必需的,也可以用new的方式创建新对象,但这个对象必须存在,否则将出错。
Customer customer = (Customer) DataObjectManager.Instance.GetObject(typeof(Customer),"0001");
customer.Name = "Will Smith"; //修改Name属性
customer.Complete(); //保存修改
删除持久化对象
删除存在于持久化存储中的对象实例
下面的示例演示了删除Customer实例:
[C#]
//首先获得对象实例。这步不是必需的,也可以用new的方式创建新对象,但这个对象必须存在,否则将出错。
Customer customer = (Customer) DataObjectManager.Instance.GetObject(typeof(Customer),"0001");
customer.Complete(); //删除对象
3、定义关联关系类
对象系统中,各个实体对象之间常常存在关系,它们需要相互协作,持久化类之间可以建立关联,用于定义父类与子类的关系称之为关联关系,用于代表子对象的类属性称为关联属性。关联关系必须在父类定义(RelationKeyMappingAttribute).
4、使用持久化控制接口
持久化管理器DataObjectManager支持IPersistenceControllable接口,实现IPersistentControllable接口持久化对象会被DataObjectManager的持久化操作回调,以提供持久化对象对持久化操作的控制。
IPersistenceControllable接口定义控制持久化操作新增、更新、删除、获取的接口。
下面的例子演示实现IPersistenceControllable接口的Student类利用BeforeNew接口在新增前检查是否有null值,并将null值设置为缺省置,以避免写入数据库时产生异常(表的字段被定义为不允许为空):
[C#]
//学生类定义
[ObjectMapping("student")]
public class Student:IPersistenceControllable
{
[PropertyMapping("student_id",true)]
public int StudentId { get; set; }
[PropertyMapping("name")]
public string Name;
[PropertyMapping("homeplace")]
public string HomePlace; //学生的出生地,映射表不允许为空
//实现IPersistenceControllable接口
public void BeforeNew(){
//在新增对象到持久化存储前检出对象数据的有效性
if (Name == null || Name.Trim()=="")
throw new ArgumentNullException("Name不允许为空");
if (HomePlace == null) HomePlace = "";
}
public void AfterNew(){};
public void AfterLoad(){};
public void BeforeUpdate(){};
public void AfterUpdate(){};
public void BeforeDelete(){};
public void AfterDelete(){};
}
//下面的代码演示新增了实现IPersistentControllable接口的持久化对象
//首先创建一个新的学生
Student student = new Student();
student.Id = 2;
//未设置Name属性
//student.Name = "Mark";
//下面的代码出现异常,因为未设置Name属性
dom.NewObject(student);
//下面的代码将不会出现异常,因为HomePlace在BeforeNew方法中设置了缺省值
student.Name = "Mark";
student.Complete();
IPersistentControllable在事务环境中因为事务可能被回滚,但对IPersistentControllable的调用
不能回滚,因此不能在事务环境中使用IPersistentControllable接口来处理对持久化成功依赖的要求。
5、O/R映射定义
定义类映射
持久化类需要定义保存数据的表。对象映射定义映射的表名,属性与表字段的映射参见定义属性映射。
属性定义
ObjectMappingAttribute用于定义对象映射的表名,属性只能定义在类上。
下面例子演示了持久化类定义ObjectMapping属性:
[C#]
[ObjectMapping("customer")] //Customer映射数据库的表"customer"
public class Customer
{
//...
}
持久化类也可定义数据源属性(DataSourceAttribute),该数据源仅用于该类,如下:
[C#]
[ObjectMapping("customer")] //Customer映射数据库的表"customer"
[DataSource("accessdb","OleDb",@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb")]
//定义用于Customer类的数据源
public class Customer
{
//...
}
定义属性映射
持久化类通过属性映射定义类属性与表字段的映射。Class
Engine并不要求定义属性的数据类型,持久化处理器自动进行数据类型的匹配。
属性定义
属性映射使用PropertyMappingAttribute进行设置,PropertyMappingAttribute只能定义在类的Field和Property上。
PropertyMappingAttribute可以设置以下属性:
|
属性
|
类型
|
描述
|
|
Field
|
System.String(.Net框架类型),string(c#)
|
映射表的字段名
|
|
IsPrimaryKey
|
System.Boolean(.Net框架类型),bool(c#)
|
是否主键。true表示该属性是主键属性
|
|
IsIdentity
|
System.Boolean(.Net框架类型),bool(c#)
|
是否自动赋值字段。某些数据库支持自动增长字段类型,在新增记录是不需要提供该字段的值,而由数据库自动设置。
|
|
DefaultValue
|
Sysem.Object(.Net框架类型),
object(c#) |
缺省值
|
下面的例子演示属性的映射定义:
[C#]
[ObjectMapping("customer")]
public class Customer
{
[PropertyMapping("id",true)] //映射表字段id,并且是对象主键
public string CustomerId;
[PropertyMapping("name")] //映射字段name
public string Name;
//映射字段valid,并且需要数字化布尔值的转换
[PropertyMapping("valid",DataTransform = DataTransformFormat.NumbericBoolean)]
public bool Valid;
}
定义关联
关联定义持久化对象之间的关系,在Class
Engine中关联由父端和子端组成,父端和子端均可以定义关联对方的属性,可以通过关联属性来访问关联的对象。
定义父类和子类之间的关联,需要定义关联关系,关联键、和在持久化类的关联属性。
关联关系定义
关联定义持久化类与持久化类的关系,关系的定义端为父类,父类连接子类,子类也可以连接父类。持久化管理器在装载父类时将自动装载关联子类对象,但装载子类时则不会装载父类,则主要是为了避免循环装载。
关联关系定义包括关系的名称和父类、子类以及关联的键定义、关联表等关联信息。
RelationKeyMappingAttribute定义关联关系,一般定义在父类上,可以设置以下属性:
|
属性
|
类型
|
描述
|
|
childType
|
System.
Type
(.Net框架类型)
|
子类型。
|
|
primaryKey
|
System.String(.Net框架类型),string(c#)
|
父类主关键字。
|
|
relationKey
|
System.String(.Net框架类型),string(c#)
|
子类主关键字。
|
|
cascadeDelete
|
System.Boolean(.Net框架类型),bool(c#)
|
是否允许级联删除,即删除父对象时同时删除子对象。可选,默认为false。
|
实体对象的状态管理
Class
Engine库在进行持久化实体对象的管理操作时,依赖于对象的状态。为了管理对象的状态,实体类需要实现IPeristentObject接口,IPeristentObject提供了查询状态、设置状态的接口。
对象状态由类ObjectState定义,包括以下几种状态:
- New:对象为新对象(未存在持久存储中)
- Dirty:对象已被修改
- Persisted:对象已持久化,即对象的数据与持久存储一致
- Deleted:对象已被删除
实体对象可以通过State属性获得对象的状态。
除此之外,IPersistentObject还提供了一个用于标记预备删除的属性DeletionMark,当属性为true时表明对象请求从持久化存储中删除。
客户端维护的对象状态
为了支持客户/服务器方式的应用,Class
Engine持久化管理器并不提供对象的状态管理,在Class
Engine库中持久化管理器同样不提供对象的状态的管理,而由客户端负责维护其对象的状态,这意味着客户端调用管理器的Save接口后,客户端的实体类的状态并不会变为"Persisted",客户端必须通过调用IPersistent的Complete接口来同步状态。
设计支持对象状态管理的实体类
对象正确的状态变化应当如下:
在一个对象被创建时对象的状态为"New";对象在保存后通过调用Complete接口同步对象的状态,此时对象的状态应当为"Persisted"或"Deleted"(DeletionMark为true);已保存的对象(状态为"Persisted")的持久化属性修改后,其对象状态应当为"Dirty"。
在Class
Engine中为了管理实体对象状态,实体类必须实现IPersistObject接口。Class
Engine库提供了了实体类基类BasePersistentObject,该类已经实现了IPersistObject接口,因此可以直接从BasePersistentObject继承来产生自己的实体类。
正确地实现对象管理,实体类必须符合以下原则:
- 新建的对象实例,初始状态必须为ObjectState.New(当实体类从PersistentObject继承时,其对象状态将自动为"New")。
· [C#]
· Customer customer = new Customer();
- 修改了对象持久化属性后,应当设置对象为"脏"状态。IPersistentObject的MakeDirty接口设置属性为Dirty,当对象属性修改后应当利用该接口修改对象的状态。
· [C#]
· //下面是Customer类定义,演示如何在对象持久化属性修改后,修改对象状态
· //(持久化类映射定义略)
· public class Customer
· {
· [PropertyMapping("id",true)]
· public string Id;
·
· private string name;
· [PropertyMapping("name")]
· public string Name
· {
· get { return name; }
· set {
· name = value;
· //Name属性修改后,设置对象状态为"脏"
· this.MakeDirty("Name");
· }
· }
· }
持久化管理器处理对象状态的方式
要设置对象的删除标记,只需将DeletionMark设置为true,DeletionMark标记优先于State,Class
Engine库的持久化管理器在进行Save操作,遵循以下原则(优先级从上到下):
- 当DeletionMark为true,在执行Save时,如果State为"New",则持久化管理器不作处理,否则将从持久化存储中删除该实体对象。
- 当State为"New",则新增该对象。
- 当State为"Dirty",则更新该对象。
6、视图持久化定义
在Class
Engine 中,视图的持久化对象定义和普通表的定义相同,Class
Engine支持视图更新
五、Class
Engine 在ASP.Net开发中的应用
1、说明
Class
Engine 较好的解决了应用程序数据的存储和逻辑的封装问题,可是现实开发中还需要实现表现层(UI),如何使Class
Engine 与UI完美的结合是解决快速开发的又一大难题。下面简单介绍NGSDF体系结构及其。
2、开发步骤
在开发过程中,开发者只要专注于组件的定制,以及UI的配置(DataGrid的Columns和WayControlTable的Items)。NGSDF提供了快速组件生成工具和UI配置工具,使得整个开发效率加倍提高。
配置流程如下,假设开发者需要做一个员工信息维护模块:
在数据库中创建表(Employee)及其关联表
例如员工信息、民族,政治面貌,家庭成员等,并在应用程序项目中建立EmployeeForm.Aspx页面(页面的界面风格和CS代码可以利用NGSDF附带的例子),此页面的相应类必须继承Forceland.Web.AbstractPageBase,实际使用中可以建立一个PageBase类,用于继承Forceland.Web.AbstractPageBase作为桥接,之后只要继承PageBase类即可。
利用NGSDF的
“框架表信息”配置Employee数据
界面如下,通过“系统表和框架表数据匹配”,把Employee表的信息自动导入NGSDF框架表的TableItem
和 FieldItem表中, 通过点击Employee记录配置对应的字段信息
1、
通过代码生成器自动产生Employee的类代码,
拷贝Employee类代码,并应用到项目中(记得在CS文件头加上using
Forceland.DataMapping;)
通过列生成器自动产生UI界面
可以根据需要产生显示记录的DataGrid的Columns和显示详细信息用的WayControlTable
的Items
修改_CurrentEntryType
= typeof(Employee)即可
这样就完成了整个Employee添加,删除,修改,导出,查询等功能,还有权限控制,
操作跟踪,非法输入验证等,代码页面如下
浏览EmployeeForm界面
配置好的EmployeeForm运行页面如下

3、业务逻辑的封装
考虑到实际项目应用的复杂性,框架预留了大量的控制接口,列表如下,都可以根据实际的开发需要来重载系统功能
登陆用户信息
//当前登陆用户信息,根据用户登陆时设置的HttpContext.Current.Session["AccountID"]值来设置LoginUser内容,可以重载此属性
protected
virtual
User LoginUser
//根据指定的用户ID和类型获取用户名称,具体实现由特定的系统决定
protected
virtual
string
GetUserName(int
uId,int
uType)
持久化接口
//
在将实体从数据库删除之后调用的接口。
public
virtual
void
AfterDelete()
//
在准备将实体从数据库删除之前调用的接口。
public
virtual
void
BeforeDelete()
//
在新实体创建到数据库之前被调用的接口。实现类可以在本方法实现持久属性的初始化。
public
virtual
void
BeforeNew()
//
在新实体创建到数据库之后被调用的接口。
public
virtual
void
AfterNew()
//
在准备将实体更新到数据库之前调用的接口。
public
virtual
void
BeforeUpdate()
//
在将实体更新到数据库之后调用的接口。
public
virtual
void
AfterUpdate()
//
在准备将实体更新到数据库之前调用的接口。
protected
virtual
void
BeforeSave()
//
在将实体更新到数据库之后调用的接口。
protected
virtual
void
AfterSave()
//
在将实体从数据库装载之后调用的接口。
public
virtual
void
AfterLoad()
//
持久化对象被更新时触发
protected
virtual
void
Updateing(BasePersistentObject persistentObj)
//
持久化对象被呈现时触发
protected
virtual
void
Showing(BasePersistentObject persistentObj)
//
记录被选择之前触发
protected
virtual
void
BeforeGridSelecting()
//
记录被选择之后触发
protected
virtual
void
AfterGridSelecting()
//
按钮被点击之前触发的事件
protected
virtual
void
BeforeDoClick(object
sender, System.EventArgs e)
//
按钮被点击之后触发的事件
protected
virtual
void
AfterDoClick(object
sender, System.EventArgs e)
//
在准备将实体更新到数据库之前调用的接口。
protected
virtual
void
BeforeSave() {}
//
在将实体更新到数据库之后调用的接口。
protected
virtual
void
AfterSave() {}
数据绑定方法
//
绑定数据到DataGrid,同时初始化Grid的选择列信息
protected
virtual
void
bindDataGrid(WayDataGridPager pager,DataGrid grid)
///
得到需要绑定到
_CurrentDataGrid上的Sql语句,在Grid数据绑定时被调用,
///
可以重载此方法
protected
virtual
string
GetBindDataGridSql()
初始信息设置
//
初始化基础信息
protected
virtual
void
IniInformations()
//模板方法,封装所有页面的公共操作,在每个继承AbstractPageBase类及其子类的Page中都会被调用,
主要用来初始化一些基础数据,包括权限设置,安全检查
protected
virtual
void
BaseTemplateMethod()
///
设置控件权限
protected
virtual
void
BaseInitialzeControlEvent()
//数据库连接字符串,定义在Web.confg
的 "ConnString1",可以根据需要重载此属性
protected
virtual
string
ConnectionString
//网格页数,定义在Web.confg
的 "PageSize",可以根据需要重载此属性
protected
virtual
int
_GridPageSize
///
页面装载之前触发的事件
protected
virtual
void
BeforePageLoad()
///
页面装载之后触发的事件
protected
virtual
void
AfterPageLoad()
六、由NGSDF实现零代码开发
通过NGSDF,您还可以不用编程同样可以搭建系统,比喻我们要建立Employee模块,你只要通过NGSDF提供的系统功能(系统框架->框架表信息)配置要您期望Employee需要显示的界面,如界面控件类型,控件长度,界面风格等,配置好之后即可增加Employee的菜单项,同时指定操作码为Employee,导航地址为”../FrameWork/AutoGenerateByTableNameForm.aspx”即可。
同样NGSDF还支持主从表,你只要对操作码(在主表后以分隔符’|’加子表名称即可,对子表数量没有限制)和导航地址(../FrameWork/AutoGenerateByTableNameMaster.aspx)做简单的改变即可,下面是主从表的应用
七、NGSDF提供的其他功能
1、权限控制
本框架提供了默认的权限管理模块,模块的设计基于组和角色,与具体的业务无关,可以通过此权限控制菜单(添加、删除、修改、导出、高级查询、报表)和数据表字段的修改和可见性,具体页面如下:
菜单权限控制
例如如果需要设置角色“浏览管理员”(只有查看)的权限,只需要通过“菜单权限”进入如下界面,按照需求设置即可
字段权限控制
通过“字段权限”按钮进入字段控制界面,针对某一字段的权限控制,
如果对角色“浏览管理员”的人员基础信息的“姓名”字段设置为不可见,模块设置为不可修改,则控制后的界面如下:
2、菜单层次结构
利用本框架不用考虑任何功能菜单的显示问题,只要利用本框架的“系统菜单”模块配置好菜单层次结构和连接即可.
3、框架表信息维护
通过NGSDF
提供的 系统框架表配置应用系统的表信息,由于在实际应用开发中,系统数据库存在大量的表,通过提供的“系统表和框架表匹配”,自动同步数据库表(包括视图)和框架表字典信息,完成大部分的基础设置工作,包括表信息和字段信息(字段名、字段信息,PK信息、使用控件等)
还可以通过如下界面来设置表和字段信息
4、持久化对象代码生成器
通过框架提供的类代码生成器,你可以实现快数持久化对象类代码实现,大量节省编码和调试配置时间,操作界面如下,代码生成有两种方法,其实是通过选择界面ListBox的一个或者多个Item项,然后执行产生代码即可,也可以同时通过输入框直接键入表名。选择框“IPersistenceControllable实现”表示是否自动产生接口IpersistenceControllable的实现代码,在此接口中可以封装业务逻辑,期望在持久化对象状态改变或者同步数据库时做的工作。
5、界面代码生成器
你可以通过框架提供的“列生成器”来产生DataGrid的Columns的代码和表格控件WayControlTable的Items代码,你可以通过选择表名和直接键入来生成,界面如下。在利用此页面产生代码时最好先通过“系统框架表信息”模块配置好需要产生的表的表信息和字段信息。其中选择框“Add和Grid生效”表示是否启用框架表字段的“增加可见”和“网格可见”的设置,“Enable”表示对WayControlTable的Items的Enable属性是否设置
6、增加表
可以通过框架提供的“功能表信息配置”来增加表和字段信息,并自动和数据库表同步,页面如下:
7、自定义报表输出
通过框架提供的“报表设置”模块可以实现自定义报表,本功能支持分组报表和小计合计报表,配置界面如下:
配置好后只要在模块页面上加入报表按钮(WayImageButton类型),并设置CommandName属性为“Print”即可,或者自己在按钮下写代码:
this.Response.Redirect(string.Format(@"..\FrameWork\ReportMainFormByTableName.aspx?MenuId={0}&TableName={1}",Forceland.Web.WebUtilityCore.QueryIDFromRequest("MenuId"),
“表名”))
报表页面例子如下:
8、数据自动导入
可以通过框架提供的“数据导入”模块从Access文件中自动导入数据,在导入数据时操作步骤如下:
第一:通过“数据库导入”模块上传Access文件到服务器。
第二:数据到自动导入“同步数据库表结构”自动产生上传数据库表结构信息
第三:通过“数据导入”映射模块配置好由“第二”步产生的表结构和系统数据库表结构的关系(包括表和字段的对应关系)
第四:在通过“数据库导入”模块中的导入数据按钮自动把数据导入当前数据库。
9、用户操作跟踪
用户操作跟踪,本框架对用户所有操作都进行Log处理
八、总结
总的来说,利用NGSDF可以超常的提高软件开发速度,基本每个页面开发速度保持在10~20分钟,整个系统的体系结构清晰,维护扩展容易,减少设计成本。经过大量的项目使用和评估,NGSDF可以使得通常的ASP.Net项目开发节约50~70%的成本。