自从上篇《基于MVC4+EasyUI的Web开发框架形成之旅--总体介绍》总体性的概括,得到很多同行的关注和支持,不过上一篇主要是介绍一个总体的界面效果和思路,本系列的文章将逐步介绍其中的细节,本文主要介绍整个Web开发框架中的MVC控制器的设计。在设计之初,我就希望尽可能的减少代码,提高编程模型的统一性。因此希望能够以基类继承的方式,和我Winform开发框架一样,尽可能通过基类,而不是子类的重复代码来实现各种通用的操作。

1、登录控制的控制器基类设计

我们知道,一般我们创建一个MVC的控制器,都是基于Controller这样的基类来实现。如下代码所示。

publicclassTestController:Controller{////GET:/Test/publicActionResultIndex(){returnView();}}

在我的Winform开发框架里面,用到了泛型的类型,非常方便实现业务逻辑和数据访问基类的设计,控制器是否也可以这样做的呢?

我们知道,一般的MVC控制器需要验证用户是否已经登陆了,这也是很多常见Web操作前的验证,还有对异常的处理,在MVC的基类,可以一并进行记录(这个非常不错),于是我们先来设计一个验证用户身份是否登陆的基类BaseController

///<summary>///所有需要进行登录控制的控制器基类///</summary>publicclassBaseController:Controller{///<summary>///当前登录的用户属性///</summary>publicUserInfoCurrentUserInfo{get;set;}///<summary>///重新基类在Action执行之前的事情///</summary>///<paramname="filterContext">重写方法的参数</param>protectedoverridevoidOnActionExecuting(ActionExecutingContextfilterContext){base.OnActionExecuting(filterContext);//得到用户登录的信息CurrentUserInfo=Session["UserInfo"]asUserInfo;//判断用户是否为空if(CurrentUserInfo==null){Response.Redirect("/Login/Index");}}protectedoverridevoidOnException(ExceptionContextfilterContext){base.OnException(filterContext);//错误记录WHC.Framework.Commons.LogTextHelper.Error(filterContext.Exception);//当自定义显示错误mode=On,显示友好错误页面if(filterContext.HttpContext.IsCustomErrorEnabled){filterContext.ExceptionHandled=true;this.View("Error").ExecuteResult(this.ControllerContext);}}........................}

有了这个基类,我们在主页的Home控制类,就可以使用用户信息对象了进行操作了,而且必须要求客户登陆了。

publicclassHomeController:BaseController{publicActionResultIndex(){if(CurrentUserInfo!=null){ViewBag.FullName=CurrentUserInfo.FullName;ViewBag.Name=CurrentUserInfo.Name;}returnView();}................}

2、数据访问业务基类控制器的设计

我在我的Winform开发框架里面,对很多基类都使用泛型进行设计,这样可以传递相应的数据类型到基类里面进行处理,如下面的BLL层的业务对象定义代码如下所示。

namespaceWHC.Security.BLL{///<summary>///角色信息业务管理类///</summary>publicclassRole:BaseBLL<RoleInfo>{....................

///<summary>///业务基类对象///</summary>///<typeparamname="T">业务对象类型</typeparam>publicclassBaseBLL<T>whereT:BaseEntity,new(){///<summary>///插入指定对象到数据库中///</summary>///<paramname="obj">指定的对象</param>///<returns>执行操作是否成功。</returns>publicvirtualboolInsert(Tobj){returnbaseDal.Insert(obj);}............

业务对象Role,要求传入RoleInfo给基类处理,这样基类就能定义到都对应的T为具体的RoleInfo类型了。在MVC的控制器是否也可以这样做呢?当然可以,下面是我定义的一个控制器继承关系图。

上面的介绍也已经比较明白了,其实就是在BusinessController<B, T>里面传入了两个参数,定义代码如下所示。

///<summary>///本控制器基类专门为访问数据业务对象而设的基类///</summary>///<typeparamname="B">业务对象类型</typeparam>///<typeparamname="T">实体类类型</typeparam>publicclassBusinessController<B,T>:BaseControllerwhereB:classwhereT:WHC.Framework.ControlUtil.BaseEntity,new(){///<summary>///插入指定对象到数据库中///</summary>///<paramname="info">指定的对象</param>///<returns>执行操作是否成功。</returns>publicvirtualActionResultInsert(Tinfo){boolresult=false;if(info!=null){result=baseBLL.Insert(info);}returnContent(result);}................

我根据传入的BLL业务对象类型B,对象实体类类型T,那么我们就可以构造对应的baseBLL对象,然后调用其基类接口实现基本的操作,如插入,删除,更新,查找等等,这样的模式就和我的Winform开发框架的理念非常吻合了。

我们以角色控制器来说明,它的定义如下所示,如果不需要实现额外的接口(除了常见的操作),基本上不需要写任何代码了,因为所有很多常见的操作,都已经封装在了基类控制器BusinessController<B, T>里面了。

///<summary>///角色业务操作控制器///</summary>publicclassRoleController:BusinessController<Role,RoleInfo>{publicRoleController():base(){}...............

对于一些需要特殊数据处理的操作,可以增加一些自定义的接口函数,也可以重写基类的一些接口,实现数据的相应处理。如我的菜单界面显示中,需要根据缩进的层级对菜单名称进行缩进,以便更好的展示它们的层级结构,那么我就需要对分页函数进行重写了,如下代码所示是整个菜单Menu类的控制器类代码。

publicclassMenuController:BusinessController<Menu,MenuInfo>{publicoverrideActionResultFindWithPager(){stringwhere=GetPagerCondition();//基类实现PagerInfopagerInfo=GetPagerInfo();//基类实现List<MenuInfo>list=baseBLL.FindWithPager(where,pagerInfo);list=CollectionHelper<MenuInfo>.Fill("-1",0,list,"PID","ID","Name");//Json格式的要求{total:22,rows:{}}//构造成Json的格式传递varresult=new{total=pagerInfo.RecordCount,rows=list};returnJsonDate(result);}///<summary>///用作下拉列表的菜单Json数据///</summary>///<returns></returns>publicActionResultGetDictJson(){List<MenuInfo>list=baseBLL.GetAll();list=CollectionHelper<MenuInfo>.Fill("-1",0,list,"PID","ID","Name");List<CListItem>itemList=newList<CListItem>();foreach(MenuInfoinfoinlist){itemList.Add(newCListItem(info.Name,info.ID));}returnJson(itemList,JsonRequestBehavior.AllowGet);}}

我们来看看一段HTML页面里面,使用javascript脚本调用控制器API来实现数据的绑定的操作,也就是使用示例。

$.getJSON("/Role/FindById?r="+Math.random()+"&id="+id,function(json){$("#txtID").val(json.ID);$("#txtName").val(json.Name);$("#txtNote").val(json.Note);});

上面这个很标准的接口FindById是业务基类控制器BusinessController<B, T>里提供的。

当然,BusinessController里面可以类似我Winform开发框架里面基类一样,提供很丰富的操作接口,如返回列表Json集合,增删改查的操作及返回,分页数据的返回,以及一些特殊的操作都可以实现。而这些都不需要子类进行任何实现。

如下面实际案例的用户登陆日志,里面的界面功能还是很丰富的,当他的控制器业务类不需要任何实现,只需要继承基类即可。

publicclassLoginLogController:BusinessController<LoginLog,LoginLogInfo>{publicLoginLogController():base(){}}

界面部分代码如下所示。

//实现对DataGird控件的绑定操作functionInitGrid(queryData){$('#grid').datagrid({//定位到Table标签,Table标签的ID是gridurl:'/LoginLog/FindWithPager',//指向后台的Action来获取当前用户的信息的Json格式的数据title:'用户登陆日志',//下面的这些属性如果谁不太清楚的话我建议去官方网站去学习iconCls:'icon-view',height:450,nowrap:true,autoRowHeight:false,striped:true,collapsible:true,pagination:true,rownumbers:true,//sortName:'ID',//根据某个字段给easyUI排序sortOrder:'asc',remoteSort:false,idField:'ID',queryParams:queryData,//异步查询的参数columns:[[{field:'ck',checkbox:true},//选择{title:'ID',field:'ID',width:40,sortable:true},//主键{title:'登录用户ID',field:'User_ID',width:80,sortable:true},{title:'登录名称',field:'LoginName',width:80,sortable:true},{title:'真实名称',field:'FullName',width:80,sortable:true},{title:'日志描述',field:'Note',width:100,sortable:true},{title:'IP地址',field:'IPAddress',width:100,sortable:true},{title:'Mac地址',field:'MacAddress',width:120,sortable:true},{title:'系统编号',field:'SystemType_ID',width:120,sortable:true},{title:'记录日期',field:'LastUpdated',width:120,sortable:true},]],toolbar:[{id:'btnAdd',text:'添加',iconCls:'icon-add',handler:function(){ShowAddDialog();//实现添加记录的页面}},'-',{id:'btnEdit',text:'修改',iconCls:'icon-edit',handler:function(){ShowEditOrViewDialog();//实现修改记录的方法}},'-',{id:'btnDelete',text:'删除',iconCls:'icon-remove',handler:function(){Delete();//实现直接删除数据的方法}},'-',{id:'btnView',text:'查看',iconCls:'icon-table',handler:function(){ShowEditOrViewDialog("view");//实现查看记录详细信息的方法}},'-',{id:'btnReload',text:'刷新',iconCls:'icon-reload',handler:function(){//实现刷新栏目中的数据$("#grid").datagrid("reload");}}]});$('#grid').datagrid({onDblClickRow:function(rowIndex,rowData){$('#grid').datagrid('uncheckAll');$('#grid').datagrid('checkRow',rowIndex);ShowEditOrViewDialog();}});}

这个就是我的控制器设计的中心思想了,下一篇继续介绍整体的MVC系列的Web开发框架,介绍其中Web界面部分的处理和相关经验,希望大家多多提出宝贵的意见。