业务接口+UI层的设计(基于Castle实现的Repository)
Repository层设计的文章见:【http://www.cnblogs.com/yomho/p/3297042.html】
一、概要设计
上面Reposity 应该为 Repository
特此更正,也不打算作图更正了。
二、业务Server层
业务层Server是承Repository层,启UI层的重要层,
UI层的数据和Repository层的数据传递必须经过它
业务层的扩展非常必要
所以采用IServer<TEntity>的设计方式
接口设计如下:
1 namespace Yom.NFramework2_0 2 { 3 public interface IServer<TEntity, TPrimaryKey> 4 where TEntity : IEntity 5 where TPrimaryKey : IComparable 6 { 7 TEntity FindBy(TPrimaryKey primaryKey); 8 IEnumerable<TEntity> FindAll(); 9 IEnumerable<TEntity> FindAll<TWhere>(TWhere[] where) where TWhere : IWhere; 10 IEnumerable<TEntity> FindAll<TWhere, TOrder>(TWhere[] where, TOrder[] order) 11 where TWhere : IWhere 12 where TOrder : IOrder; 13 IEnumerable<TEntity> FindAll<TWhere, TOrder>(int pageIndex, int pageSize, TWhere[] where, TOrder[] order, out int count) 14 where TWhere : IWhere 15 where TOrder : IOrder; 16 void Add(TEntity entity); 17 void Delete(TEntity entity); 18 void DeleteAll(); 19 void DeleteAll<TWhere>(TWhere[] where) where TWhere : IWhere; 20 void DeleteAll(string where); 21 void DeleteAll(IEnumerable<TPrimaryKey> pkValues); 22 void Update(TEntity entity); 23 bool Exists(TPrimaryKey primaryKey); 24 25 TPrimaryKey PrimaryKeyPropertyName 26 { 27 get; 28 } 29 } 30 }
Server层和Repository的接口设计大同小异
大同:方法大致相同
小异:实现方式不同,Server是IServer<TEntity>,Repository是IRepository,Server的如此设计是为了业务扩展。
其中Server层要多了一个获取主键属性名称的方法。
Server层依附Repository的实现如下:
1 namespace Yom.NFramework2_0 2 { 3 public abstract class ISinglePrimaryKeyServer<TEntity> : IServer<TEntity, String> 4 where TEntity : IEntity 5 { 6 #region IServer<TEntity,string> 成员 7 8 public TEntity FindBy(string primaryKey) 9 { 10 return RepositoryFactory.Instance.FindBy<TEntity>(primaryKey); 11 } 12 13 public IEnumerable<TEntity> FindAll() 14 { 15 return RepositoryFactory.Instance.FindAll<TEntity>(); 16 } 17 public IEnumerable<TEntity> FindAll<TWhere>(TWhere[] where) where TWhere : IWhere 18 { 19 return RepositoryFactory.Instance.FindAll<TEntity>(where as IWhere[]); 20 } 21 public IEnumerable<TEntity> FindAll<TWhere, TOrder>(TWhere[] where, TOrder[] order) 22 where TWhere : IWhere 23 where TOrder : IOrder 24 { 25 return RepositoryFactory.Instance.FindAll<TEntity>(where as IWhere[], order as IOrder[]); 26 } 27 public IEnumerable<TEntity> FindAll<TWhere, TOrder>(int pageIndex, int pageSize, TWhere[] where, TOrder[] order, out int count) 28 where TWhere : IWhere 29 where TOrder : IOrder 30 { 31 return RepositoryFactory.Instance.FindAll<TEntity>(pageIndex, pageSize, where as IWhere[], order as IOrder[], out count); 32 } 33 34 public void Add(TEntity entity) 35 { 36 RepositoryFactory.Instance.Add<TEntity>(entity); 37 } 38 39 public void Delete(TEntity entity) 40 { 41 RepositoryFactory.Instance.Delete<TEntity>(entity); 42 } 43 44 public void DeleteAll() 45 { 46 RepositoryFactory.Instance.DeleteAll<TEntity>(); 47 } 48 49 public void DeleteAll<TWhere>(TWhere[] where) 50 where TWhere : IWhere 51 { 52 RepositoryFactory.Instance.DeleteAll<TEntity>(where as IWhere[]); 53 54 } 55 public void DeleteAll(string where) 56 { 57 RepositoryFactory.Instance.DeleteAll<TEntity>(where); 58 } 59 60 public void DeleteAll(IEnumerable<string> pkValues) 61 { 62 RepositoryFactory.Instance.DeleteAll<TEntity>(pkValues); 63 } 64 65 public void Update(TEntity entity) 66 { 67 RepositoryFactory.Instance.Update<TEntity>(entity); 68 } 69 70 public bool Exists(string primaryKey) 71 { 72 return RepositoryFactory.Instance.Exists<TEntity>(primaryKey); 73 } 74 75 public abstract string PrimaryKeyPropertyName 76 { 77 get; 78 } 79 #endregion 80 } 81 }
当到用特定的ORM扩展的时候
只需要实现
1 public abstract string PrimaryKeyPropertyName 2 { 3 get; 4 }
就可以了
用Castle实现如下
1 namespace Yom.NFramework2_0.CastleExtend 2 { 3 public class SinglePrimaryKeyServerBase<TEntity> : Yom.NFramework2_0.ISinglePrimaryKeyServer<TEntity> 4 where TEntity : EntityBase 5 { 6 string GetSinglePrimaryKeyName() { 7 System.Reflection.PropertyInfo[] pis = typeof(TEntity).GetProperties(); 8 if (pis != null && pis.Length > 0) { 9 object[] customAttributes; 10 foreach (System.Reflection.PropertyInfo pi in pis) { 11 customAttributes = pi.GetCustomAttributes(typeof(Castle.ActiveRecord.PrimaryKeyAttribute), true); 12 if (customAttributes != null && customAttributes.Length > 0) { 13 string result = (customAttributes[0] as Castle.ActiveRecord.PrimaryKeyAttribute).Column; 14 if (!string.IsNullOrEmpty(result)) { 15 return result; 16 } 17 return pi.Name; 18 } 19 } 20 } 21 return null; 22 } 23 24 public override string PrimaryKeyPropertyName 25 { 26 get { return GetSinglePrimaryKeyName(); } 27 } 28 } 29 }
这样一个完整的基于Castle的ServerBase就准备好了
三、组合控件UI层
UI层一般有List和Edit这2个常用的控件
这2个控件是SkinnedControlBase的子类
在UI的设计的时候,实现了SkinnedControlBase控件
大家也许想特想知道这控件有什么功能
其实这个控件是实现框架里UI和组装层(实际就是项目)分离的重要控件
这个控件的设计思想请见【http://www.cnblogs.com/yomho/archive/2013/03/10/2953132.html】
简单说就是:
UI的业务逻辑和表现数据的HTML代码(html代码写在项目里)完全分离
组合控件分为自定义控件和用户控件两个部分:
1、自定义控件封装后台代码(负责后台操作);
2、用户控件负责前台HTML代码(实现布局和样式以及客户端验证)。
在用aspx页面承载组合控件时候,会结合组合控件中自定义控件的后台逻辑代码和用户控件的Html代码展示数据给客户操作。
这种分离是约定下分离的,就像MVC里面视图和控制器名称的约定一样,
当然也可以个性化配置,实现特定的开发的目录结构,
就像MVC里的Area的设计概念,可以根据项目,对控制器和视图的映射路径进行个性化配置
虽然SkinnedControlBase有点像MVC的思想,但是却完全不是这种MVC概念。
四、Project项目的功能模块的组装
实现项目的时候,需要费力开发的层次有:Entity层和Server层以及UI层.
这三个层次开发的时候是有顺序的:按Entity - > Server - > UI 层的顺序逐步细化
当有了UI层,比如有XXX/List.cs这个控件后,就可以开始实施Views/XXX/Lit.ascx这个控件了
XXX/List.cs和Views/XXX/Lit.ascx是相互绑定的:业务逻辑在UI层实现,对应表现数据的Html代码就在ascx里表现
真正加载数据的时候是在aspx里放组合控件
说了这么多,大家一定和头疼
下面给出一个简单的实现样例,比如我要实现部门的增删查改:
先Entity: YOM_DEPARTMENT : Yom.NFramework2_0.CastleExtend.EntityBase
然后Server:YOM_DEPARTMENTServer : Yom.NFramework2_0.CastleExtend.ServerBase<Yom.WbTest.Entity.YOM_DEPARTMENT.YOM_DEPARTMENT>
最后2个UI:
List
1 namespace Yom.WbTest.UI.YOM_DEPARTMENT 2 { 3 public class List : Yom.NFramework2_0.CastleExtend.Web.WebForm.UI.List.SinglePrimaryKey.CustomSkinnedListBase<Yom.WbTest.Entity.YOM_DEPARTMENT.YOM_DEPARTMENT , Server.YOM_DEPARTMENT.YOM_DEPARTMENTServer> 4 { 5 protected override Yom.NFramework2_0.IWhere[] Where 6 { 7 get { 8 return new NFramework2_0.IWhere[] { 9 new Yom.NFramework2_0.CastleExtend.WhereBase(){ Instance=Yom.NFramework2_0.CastleExtend.WhereBase.Like(this.searchObject["DEPARTMENT_NAME"].PropertyName,this.searchObject["DEPARTMENT_NAME"].Value.ToString(),NHibernate.Expression.MatchMode.Anywhere)} 10 }; 11 } 12 } 13 } 14 }
Edit
1 public class Edit : Yom.NFramework2_0.CastleExtend.Web.WebForm.UI.Edit.CustomSkinnedEditBase<Yom.WbTest.Entity.YOM_DEPARTMENT.YOM_DEPARTMENT,Yom.WbTest.Server.YOM_DEPARTMENT.YOM_DEPARTMENTServer> 2 { 3 protected override bool IsValid(out string msg) 4 { 5 return base.IsValid(out msg); 6 } 7 }
其中IsValid负责服务器端的数据验证,如果返回False则不会执行保存操作。
在真正做项目的时候,按照约定排版如下:
Edit.ascx
1 <%@ Control Language="C#" AutoEventWireup="true" %> 2 3 <table> 4 5 <tr> 6 <td style=" 10%; white-space:nowrap">部门名称:</td> 7 <td> 8 <asp:TextBox ID="DEPARTMENT_NAME" runat="server" CssClass="input"></asp:TextBox></td> 9 </tr> 10 <tr> 11 <td style=" 10%; white-space:nowrap">部门编号:</td> 12 <td> 13 <asp:TextBox ID="DEPARTMENT_CODE" runat="server" CssClass="input"></asp:TextBox></td> 14 </tr> 15 <tr> 16 <td style=" 10%; white-space:nowrap">排序:</td> 17 <td> 18 <asp:TextBox ID="ORDER_ID" runat="server" Text="0" CssClass="input"></asp:TextBox></td> 19 </tr> 20 <tr> 21 <td style=" 10%; white-space:nowrap">部门描述:</td> 22 <td> 23 <asp:TextBox ID="DEPARTMENT_DESC" runat="server" TextMode="MultiLine" CssClass="textarea"></asp:TextBox></td> 24 </tr> 25 <tr> 26 <td style=" 10%; white-space:nowrap">是否禁用:</td> 27 <td> 28 <asp:RadioButtonList ID="IS_DISABLED" runat="server" 29 RepeatDirection="Horizontal" RepeatLayout="Flow"> 30 <asp:ListItem Value="1">是</asp:ListItem> 31 <asp:ListItem Selected="True" Value="0">否</asp:ListItem> 32 </asp:RadioButtonList> 33 </td> 34 </tr> 35 </table> 36 <asp:Button ID="bSubmit" runat="server" Text="保存" /><input type="button" value="返回" onclick="window.location='List.aspx'" />
List.ascx(实现了点击创建时间,升降开关式排序功能)
1 <%@ Control Language="C#" AutoEventWireup="true" %> 2 <script type="text/javascript" src="../../Scripts/jquery-1.4.1.min.js"></script> 3 <asp:Panel ID="pSearcher" runat="server"> 4 部门名称: 5 <asp:TextBox ID="DEPARTMENT_NAME" runat="server" PropertyName="DEPARTMENT_NAME"></asp:TextBox><asp:Button 6 ID="btnSearch" runat="server" Text="搜索" /> 7 </asp:Panel> 8 <div style=" display:block;"><a href="Edit.aspx">新增</a></div> 9 <asp:Repeater ID="lvList" runat="server"> 10 11 <HeaderTemplate> 12 <table class="gridview" border="0" cellpadding="0" cellspacing="1" width="100%"> 13 <tr> 14 <th><input type="checkbox" title="全选/全消" onclick="$(':checkbox.cbSelectItem').attr('checked',$(this).attr('checked'))" /></th> 15 <th> 16 部门名称</th><th>部门编号</th><th>部门描述</th><th><asp:LinkButton ID="LinkButton4" runat="server" CommandName="Order" CommandArgument="CREATE_DATE">创建时间</asp:LinkButton></th><th>操作</th> 17 </tr> 18 </HeaderTemplate> 19 <ItemTemplate> 20 <tr> 21 <th><%# string.Format("<input type="checkbox" name="cbSelectItem" class="cbSelectItem" value="{0}" />", Eval("DEPARTMENT_ID"))%></th> 22 <td style=" text-align:center"><%# Eval("DEPARTMENT_NAME")%></td> 23 <td style=" text-align:center"><%# Eval("DEPARTMENT_CODE")%></td> 24 <td style=" text-align:center"><%# Eval("DEPARTMENT_DESC")%></td> 25 26 <td style=" text-align:center"><%# string.Format("{0:yyyy-MM-dd}", Convert.ToDateTime(Convert.ToString(Eval("CREATE_DATE"))))%></td> 27 <td style=" text-align:center"> 28 <a href="Edit.aspx?id=<%# Eval("DEPARTMENT_ID")%>">编辑</a>|<a href="Edit.aspx?action=delete&id=<%# Eval("DEPARTMENT_ID")%>" onclick="return confirm('确定要删除吗?')">删除</a></td> 29 </tr> 30 </ItemTemplate> 31 <FooterTemplate> 32 </table> 33 </FooterTemplate> 34 </asp:Repeater>
其中Edit.ascx 和List.ascx 不涉及后台代码和任何业务逻辑,可以完全交给美工
如果设计和开发的时候约定好,美工可先行开发Edit.ascx 和List.ascx
下面是aspx页面承载组装控件:
Edit.aspx
<%@ Page Language="C#" AutoEventWireup="true" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <div> <yom:YOM_DEPARTMENT.Edit ID="edit" runat="server"> </yom:YOM_DEPARTMENT.Edit> </div> </form> </body> </html>
List.aspx
1 <%@ Page Language="C#" AutoEventWireup="true" %> 2 3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 4 5 <html xmlns="http://www.w3.org/1999/xhtml"> 6 <head runat="server"> 7 <title></title> 8 </head> 9 <body> 10 <form id="form1" runat="server"> 11 <div> 12 <yom:YOM_DEPARTMENT.List ID="list" runat="server"> 13 </yom:YOM_DEPARTMENT.List> 14 15 </div> 16 </form> 17 </body> 18 </html>
有些细心的朋友可能已经发现:
无论是ascx还是aspx页面都没有code behind的cs文件
这个就和MVC2里aspx和ascx页面的作用一样了
只作为Html的承载文件,并没有什么cs文件去写这些文件的后台代码
否则无法实现后台代码和前台代码分离
五、总结
1、框架的设计可以很好的支持技术开发人员和美工的分工,且美工知道表结构的情况下可以先行开发;
2、模块化组装的开发模式,在框架成熟的情况下,可以避免页面业务逻辑复杂化,减少bug的出现;
3、开发流程化,减少组织代码的复杂性
4、规范了编程的代码,新人在熟悉后,可以快速进入开发和维护旧项目的状态;
5、换一个UI层和一种Project,可以快速进行项目类型的转换。
六、关于框架
1、框架的重构,难免有bug未发现,所以还得经过一些项目的开发测试;
2、框架现在只封装了asp.net Webform的开发,后续将接入Winform和MVC等的开发模块;
3、框架没有接入缓存层,所以并不适合百万大数据的项目开发,目前只能应付一些数据量不大的项目;
4、易用性方面解决了旧框架的弊端,有没有新的弊端还需要后续的使用测试。