基于MVC+EasyUI的Web开发框架形成之旅--MVC控制器的设计

本文主要介绍整个Web开发框架中的MVC控制器的设计。在设计之初,我就希望尽可能的减少代码,提高编程模型的统一性。因此希望能够以基类继承的方式,和我Winform开发框架一样,尽可能通过基类,而不是子类的重复代码来实现各种通用的操作。

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

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


public class TestController : Controller
{
    // GET: /Test/

    public ActionResult Index()
    {
        return View();
    }

}

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

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

/// <summary>
/// 所有需要进行登录控制的控制器基类
/// </summary>
public class BaseController : Controller 
{
    /// <summary>
    /// 当前登录的用户属性
    /// </summary>
    public UserInfo CurrentUserInfo { get; set; }

    /// <summary>
    /// 重新基类在Action执行之前的事情
    /// </summary>
    /// <param name="filterContext">重写方法的参数</param>
    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);
        //得到用户登录的信息
        CurrentUserInfo = Session["UserInfo"] as UserInfo;

        //判断用户是否为空
        if (CurrentUserInfo == null)
        {
            Response.Redirect("/Login/Index");
        }
    }

    protected override void OnException(ExceptionContext filterContext)
    {
        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控制类,就可以使用用户信息对象了进行操作了,而且必须要求客户登陆了。

public class HomeController : BaseController
{
    public ActionResult Index()
    {
        if (CurrentUserInfo != null)
        {
            ViewBag.FullName = CurrentUserInfo.FullName;
            ViewBag.Name = CurrentUserInfo.Name;
        }
        return View();
    }
................
}

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

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

namespace WHC.Security.BLL
{
    /// <summary>
    /// 角色信息业务管理类
    /// </summary>
    public class Role : BaseBLL<RoleInfo>
    {

....................
/// <summary>
/// 业务基类对象
/// </summary>
/// <typeparam name="T">业务对象类型</typeparam>
public class BaseBLL<T> where T : BaseEntity, new()
{

    /// <summary>
    /// 插入指定对象到数据库中
    /// </summary>
    /// <param name="obj">指定的对象</param>
    /// <returns>执行操作是否成功。</returns>
    public virtual bool Insert(T obj)
    {
        return baseDal.Insert(obj);
    }

............

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



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

/// <summary>
/// 本控制器基类专门为访问数据业务对象而设的基类
/// </summary>
/// <typeparam name="B">业务对象类型</typeparam>
/// <typeparam name="T">实体类类型</typeparam>
public class BusinessController<B, T> : BaseController
    where B : class
    where T : WHC.Framework.ControlUtil.BaseEntity, new()
{

    /// <summary>
    /// 插入指定对象到数据库中
    /// </summary>
    /// <param name="info">指定的对象</param>
    /// <returns>执行操作是否成功。</returns>
    public virtual ActionResult Insert(T info)
    {
        bool result = false;
        if (info != null)
        {
            result = baseBLL.Insert(info);
        }
        return Content(result);
    }

................

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

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

/// <summary>
/// 角色业务操作控制器
/// </summary>
public class RoleController : BusinessController<Role, RoleInfo>
{
    public RoleController() : base()
    {
    }

...............

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

public class MenuController : BusinessController<Menu, MenuInfo>
{
    public override ActionResult FindWithPager()
    {
        string where = GetPagerCondition(); //基类实现
        PagerInfo pagerInfo = GetPagerInfo(); //基类实现            
        List<MenuInfo> list = baseBLL.FindWithPager(where, pagerInfo);
        list = CollectionHelper<MenuInfo>.Fill("-1", 0, list, "PID", "ID", "Name");

        //Json格式的要求{total:22,rows:{}}
        //构造成Json的格式传递
        var result = new { total = pagerInfo.RecordCount, rows = list };
        return JsonDate(result);
    }

    /// <summary>
    /// 用作下拉列表的菜单Json数据
    /// </summary>
    /// <returns></returns>
    public ActionResult GetDictJson()
    {
        List<MenuInfo> list = baseBLL.GetAll();
        list = CollectionHelper<MenuInfo>.Fill("-1", 0, list, "PID", "ID", "Name");

        List<CListItem> itemList = new List<CListItem>();
        foreach (MenuInfo info in list)
        {
            itemList.Add(new CListItem(info.Name, info.ID));
        }
        return Json(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集合,增删改查的操作及返回,分页数据的返回,以及一些特殊的操作都可以实现。而这些都不需要子类进行任何实现。
如下面实际案例的用户登陆日志,里面的界面功能还是很丰富的,当他的控制器业务类不需要任何实现,只需要继承基类即可。


public class LoginLogController : BusinessController<LoginLog, LoginLogInfo>
{
    public LoginLogController() : base()
    {
    }

}

界面部分代码如下所示。

//实现对DataGird控件的绑定操作
function InitGrid(queryData) {
    $('#grid').datagrid({   //定位到Table标签,Table标签的ID是grid
        url: '/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界面部分的处理和相关经验,希望大家多多提出宝贵的意见。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容