【橙子】.NetMvc--企业级三层架构思路解剖详解

2 篇文章 0 订阅

作者的话
感谢指导老师:徐照兴

强烈建议在各大平台购买企业级架构徐造型该课程

本项目只为改课程的其中一部分,重在后端思路解剖详解
第一次接触企业级别的程序,第一感觉是非常繁琐复杂,本来实现一个很简单的功能,比如该项目实现的功能,用最简单的方法也能实现,为何要大费周章的使用企业级架构呢?

为何?在我自己的理解
1.低耦合(为前后端分离做好准备)
2.模块化(后续更换技术,只需要在相应的地方实现接口,对于后续的更改优化非常方便)
3.高效性(不同于之前,在企业级的架构中绝对不会直接通过创建对象访问其他层,通过工厂与spring.Net确保了程序运行的高效性,比简单的架构线程开放的要少的多)
4.高解耦、高扩展、高可用(优点绝对不止如此)

相对于高耦合度的程序,对于后续的更改,基本可以把自己绕死,深有体会
而该项目,甚至连数据库表都是后续生成,基本框架搭好,后面根据模型生成数据库,转换一下t4模版,直接就可以在DAL层写入代码

演示效果:
(重在企业级后端,前端实现,增删改查功能)
登入模块:
在这里插入图片描述
主界面模块:
在这里插入图片描述
操作模块,进行增删查改:
在这里插入图片描述

全部项目文件展示:

总览:
在这里插入图片描述

DAl数据处理层:
在这里插入图片描述
BLL业务逻辑层:
在这里插入图片描述
UI视图访问层:
在这里插入图片描述
所有视图:
在这里插入图片描述
Model模型层:
在这里插入图片描述
Common工具层:
在这里插入图片描述
本项目采用到的所有技术
Ajax、MVC、ORM(EF)、简单工厂、抽象工厂、Ioc(Spring.Net)、Log4Net、T4、LigerUI

图解逻辑
完整项目
在这里插入图片描述
后端三层架构详解
在这里插入图片描述

正式开始解刨

后端三层架构解刨

我们从顶往下走
UI>BLL>DAL

UI:
在UserInfoController控制器下调用BLL层下的查询方法

        public ActionResult Index()
        {
            ViewData.Model = UserInfoBll.GetEntities(u => true);
            return View();
        }

可以看到调用了UserInfoBll.GetEntities,传入的参数是一个匿名方法
通过传入参数的不同,可以获得根据参数筛选出的model

但是该控制器下UserInfoBll被定义成一个属性

     public IUserInfoBll UserInfoBll { get; set; }

并且没有其他地方给他赋值,很显然这是通过Spring .Net技术,依赖注入进这个属性

UserInfoBll访问到BaseBLL层下的GetEntities()方法

        public IQueryable<T> GetEntities(Expression<Func<T, bool>> whereLambda)
        {
            return CurrentDal.GetEntities(whereLambda);
        }

这里可以看到引入了一个新的对象CurrentDal
该对象在BaseBLL里依旧是属性定义

       public IBaseDal<T> CurrentDal { get; set; }

很明显,这又是通过Spring .Net技术,依赖注入进这个属性
由于该条路是查询方法,并未执行

DbSession.SaveChanges();

如果是增,删,改将在上方GetEntities()改成

public T Add(T entity)
{
    CurrentDal.Add(entity);
    DbSession.SaveChanges();
    return entity;
}

而DbSession依旧是上方定义的一个属性

public IDbSession DbSession
{
    get
    {
        return DbSessionFactory.GetGurrentDbSession();
    }
}

可以看出,这不用于Spring .Net,这是掉用了DbSessionFactory下的方法

public class DbSessionFactory
    {
        public static IDbSession GetGurrentDbSession()
        {
             IDbSession db = CallContext.GetData("DbSession") as IDbSession;
            if (db == null)
            {
                db = new DbSession();
                CallContext.SetData("DbSession", db);
            }
            return db;
        }
    }

db通过CallContext活动的单线程获得对象
如果CallContext找到对象那么直接返回不错处理,返回之前的对象
如果CallContext没有找到,那么通过DbSession()创建一个

public partial class DbSession:IDbSession
{
    public int SaveChanges()
    {
        return DbContentFactory.GetCurrentDbContent().SaveChanges();
    }
}

而DbSession是执行修改数据库的地方
这就确保了在Dal层都并未真正把数据加载到内存中,而真正意义上的加载到内存中就是该处的SaveChanges()方法

好的,刚刚已经说明如果是增删改的方式,回到上方的查询方式:

        public IQueryable<T> GetEntities(Expression<Func<T, bool>> whereLambda)
        {
            return CurrentDal.GetEntities(whereLambda);
        }

CurrentDal通过工厂创建,保证只有一个对象
CurrentDal是调用了DAL层下的代码
即:BaseDal下的代码

public IQueryable<T> GetEntities(Expression<Func<T, bool>> whereLambda)
{
    return Db.Set<T>().Where(whereLambda).AsQueryable();
}

在这里,才是最开始包装的方法,通过传入一个Lambda表达式方法,来从数据库获取数据,但执行AsQueryable()并未真正加载到了内存中,如果想要直接加载到内存中执行ToList()方法,显然,在Dal层下不应该将大批数据加载到内存中,而应该通过最后的BLL层对DAL进行最后一步处理之后再加载到内存中,能极大的节约内存

上面的BaseDal下的GetEntities方法,发现了一个新的对象Db
同样的,这个是在上方定义的属性:

public DbContext Db
{
    get { return DbContentFactory.GetCurrentDbContent();}
}

通过DbContentFactory创建

public class DbContentFactory
{
    public static DbContext GetCurrentDbContent()
    {
        DbContext db= CallContext.GetData("DbContext") as DbContext;//从线程池查找
        if (db == null)
        {
            db = new DataModelContainer();
            CallContext.SetData("DbContext", db);
        }
        return db;
    }
}

此处类是于BLL的工厂,先从CallContext获取,如果获取到了就直接返回,如果没有获取到,那么新建一个,并通过SetData()放进一个新的

综上所述,从UI层走向BLL层再到DAL层与层之间都并未真正的直接调用,而是通过Spring.Net注入,并且创建对象时,也要先通过工厂来进行单链操作,极大优化了程序,提高了高效性

前端登录操作解刨

这里有个点要注意,就是验证码

首先访问登录UserLogin视图

<img id="img" src="/UserLogin/ShowCode/id=1" style="float: left; height: 24px;" />

访问验证码会跳转连接到/UserLogin控制器下的ShowCode方法中

 public ActionResult ShowCode()
        {
            ValidateCode validateCode = new ValidateCode();
            string strCode = validateCode.CreateValidateCode(4);
            Session["Vcode"] = strCode;
            byte[] imgBytes = validateCode.CreateValidateGraphic(strCode);
            return File(imgBytes, @"image/jpeg");
        }

这里控制器是掉用创建了ValidateCode这个类的对象
此类是已经对验证码封装好的类,大家根据方法名应该就能推测出用处了,在此不多进行介绍
最后控制器返回一个图片,这就形成了验证码

 Session["Vcode"] = strCode;

这行代码是将后端生成的验证码放到 Session保存以便之后和用户输入的验证码进行比较

现在回到登录的前端,当你点击了看不清再来一张的时候,触发前端的单机事件changeCheckCode()

   <a href="javascript:void(0)" onclick="changeCheckCode(); return false;">看不清,换一张</a>
   function changeCheckCode() {
            var old = $("#img").attr("src");
            //var now = new Date();
            //var str = old + now.getHours() + now.getMinutes() + now.getMilliseconds();
            var str = old + Math.floor(Math.random() * 10000);
            $("#img").attr("src", str);
        }

通过改变自己的链接最后的id,导致浏览器刷新图片,便做到了刷新的操作
其实尾巴跟着那串id基本就是为了刷新页面都并未传送到后端

接下来,当你点击登入时:

 @using (Ajax.BeginForm("ProcessLogin", "UserLogin", new AjaxOptions() { OnSuccess = "afterLogin" }))

表达提交给UserLogin控制器下的ProcessLogin方法
并执行Ajax异步请求,当接收到后端的方法时,执行前端的afterLogin()方法

        function afterLogin(data) {
            if (data == "ok") {
                window.location.href = "/Home/index";
            } else {
                alert(data);
                changeCheckCode();//验证码或用户名密码错误就重新生成一个验证码
            }
        }

这里很好理解,就是当接受到了后端传来的数据,如果是ok,那么就跳转,如果是不是,那么就提示,然后再刷新一下验证码

现在回到后端

  public ActionResult ProcessLogin()
        {
            string strCode = Request["vCode"];
            string sessionCode = Session["Vcode"] as string;
            Session["Vcode"] = null;
            if (string.IsNullOrEmpty(sessionCode))
            {
                return Content("验证码不能为空");
            }
            if (strCode != sessionCode)
            {
                return Content("验证码输入错误");
            }
            string name = Request["LoginCode"];
            string pwd = Request["LoginPwd"];
            short delNormal = (short)CC.RolePermission.Model.Enum.DelFlagEnum.Normal;
            var userInfo = UserInfoBll.GetEntities(u => u.UName == name && u.Pwd == pwd && u.DelFlag == delNormal).FirstOrDefault();
            if (userInfo == null)
            {
                return Content("用户名或者密码错误");
            }
            Session["loginUser"] = userInfo;
            return Content("ok");
        }

这里就调用了我们之前说的UserInfoBll,而UserInfoBll也是我们之前说的通过Spring.Net属性注入

综上所述,这里便完成了验证码和登入的功能

前端增删改查操作解刨

如果我们在上一步登入成功,我们已经被提交给Home控制器下的index方法
最后我们将处在index视图下
这里是JQUI前端包装好的方法,我们只需要会用就行,具体实现过程,不需要深究
当我们点击桌面上的图片,便是开启子窗口,跳转到下方的控制器

 { icon: '/Content/Images/Home/3DSMAX.png', title: '用户管理', url: '/UserInfo/Index' }

在UserInfo控制器下:

        public ActionResult Index()
        {
            ViewData.Model = UserInfoBll.GetEntities(u => true);
            return View();
        }

这里属于Mvc的知识,将ViewData.Model绑定我们通过后端的UserInfoBll得到的数据,返回给了UserInfo下的Index视图中

现在我们位于桌面的子窗口中,这里要要实现查询所有信息列表
此处为JQUI包装好的方法,会在页面启动时,向后端发送ajax请求,接收到Json文件自动转化成数据显示

   function initTable(queryParam) {
            $('#tt').datagrid({
                url: '/UserInfo/GetAllUserInfos',
                title: '用户管理',
                width: 680,
                height: 380,
                fitColumns: true,
                idField: 'ID',
                loadMsg: '正在加载用户的信息...',
                pagination: true,
                singleSelect: false,
                pageSize: 10,
                pageNumber: 1,
                pageList: [10, 20, 30],
                queryParams: queryParam,
                columns: [[
                    { field: 'ck', checkbox: true, align: 'left', width: fixWidth(0.1) },
                    { field: 'ID', title: '用户编号', width: fixWidth(0.1) },
                    { field: 'UName', title: '用户名', width: fixWidth(0.15) },
                    { field: 'Pwd', title: '密码', width: fixWidth(0.15) },
                    { field: 'Remark', title: '备注', width: fixWidth(0.15) },
                    {
                        field: 'SubTime', title: '提交时间', width: fixWidth(0.15)
                    },

                    {
                        field: 'DelFlag', title: '操作', width: fixWidth(0.15), formatter: function (value, row, index) {
                            var str = "";
                            str += "<a href='javascript:void(0)' class='editLink' uid='" + row.ID + "'>修改</a> &nbsp;&nbsp;";
                            str += "<a href='javascript:void(0)' class='deleteLink' uid='" + row.ID + "'>删除</a>";
                            return str;
                        }
                    }
              
                ]],
                toolbar: [{
                    id: 'btnDownShelf',
                    text: '添加用户',
                    iconCls: 'icon-add',
                    handler: function () {
                        addClickEvent();
                    }
                }, {
                    id: 'btnDelete',
                    text: '删除',
                    iconCls: 'icon-cancel',
                    handler: function () {
                        deleteEvent();
                    }
                }, {
                    id: 'btnEdit',
                    text: '修改',
                    iconCls: 'icon-edit',
                    handler: function () {
                        //校验是否只选中一个用户
                        var selectedRows = $('#tt').datagrid("getSelections");
                        if (selectedRows.length != 1) {
                            $.messager.alert("提醒", "请选择要修改的1行数据", "warning");
                            return;
                        }
                        editEvent(selectedRows[0].ID);
                    }
                }],
                onHeaderContextMenu: function (e, field) {

                },
                onLoadSuccess: function (data) {
                    $('.editLink').click(function () {//表格初始化后绑定修改事件
                        editEvent($(this).attr("uid"));
                        return false;//取消单击修改连接时选中当前行
                    }),
                        $('.deleteLink').click(function () {//表格初始化后绑定删除事件
                            deleteLinkEvent($(this).attr("uid"));
                            return false;//取消单击删除连接时选中当前行
                        })
                }
            });
        }

这个ajax请求是发送给

url: '/UserInfo/GetAllUserInfos'

现在跳转到了GetAllUserInfos方法

  public ActionResult GetAllUserInfos()
        {
            int PageSize = int.Parse(Request["row"] ?? "10");
            int PageIndex = int.Parse(Request["page"] ?? "10");
            int total = 0;
            short delFlagNormal = (short)CC.RolePermission.Model.Enum.DelFlagEnum.Normal;
            var pageData = UserInfoBll.GetPageEntities(PageSize, PageIndex, out total, u => u.DelFlag == delFlagNormal, u => u.Id, true).Select(u => new { ID = u.Id, u.ModifyOn, u.Pwd, u.Remark, u.ShowName, u.SubTime, u.UName });
            var data = new { total = total, rows = pageData.ToList() };
            return Json(data, JsonRequestBehavior.AllowGet);

        }

这里没什么说的通过掉用bll层下的UserInfoBll.GetPageEntities方法得到数据然后转化层Json格式发回前端

到这里,前端已经显示了数据库中的数据
即实现了查询的功能

在查询的界面上,有增加,更改,删除3个按钮
增加与更改操作基本类型,我将合并在一起讲,而这里的删除是多行删除,与普通删除一个不一样

当我们点击增加或者更改的按钮时,都将显示一个编辑界面
这个界面在UserInfo界面上就存在,只是通过样式hide隐藏罢了
而当我们点击按钮,便触发事件

  function addClickEvent() {
            $('#addFormDialogDiv').css("display", "block");
            $('#addFormDialogDiv').dialog({
                title: "添加用户",
                modal: true,
                width: 400,
                height: 400,
                collapsible: true,
                minimizable: true,
                maximizable: true,
                resizable: true,
                buttons: [{
                    id: 'btnOk',
                    text: '添加',
                    iconCls: 'icon-ok',//有没有这种图标查看Content\jquery-easyui-1.3.1\themes\icon.css是否有这样的样式

                    //handler: function () {
                    //    alert("test1");
                    //
                    //}
                    handler: subAddForm
                }, {
                    id: 'btnCancel',
                    text: '取消',
                    iconCls: 'icon-cancel',
                    handler: function () {
                        $('#addFormDialogDiv').dialog("close");
                    }
                }],
                //toolbar: {

                //}
            })
        }

当我们点击保存时,调用上方的subAddForm()

        function subAddForm() {
            $("#addFormDialogDiv form").submit();//  把ID为addFormDialogDiv下的表单整体异步提交
        }

相当于把表单提交给后端,同样的这是一个ajax请求,当前端接收到后端的信息之后继续调用

        function afterAdd(data) {
            if (data == "ok") {
                //关闭对话框,刷新表格
                $('#addFormDialogDiv').dialog("close");
                initTable();
            } else {
                $.messager.alert(data);
            }
        }

把增加的窗口关闭,并刷新表格
综上所述,已经完成了表格的增加功能
同理,修改功能类似

接下来,是多行删除功能
当我们点击了删除按钮,注意,展示界面有2个删除方式,一个是通过右边的链接删除单个,一个是通过左侧的选择框进行多行删除,调用的方法其实是一直的,只需要传入含有编号id即可

通过左侧选择框进行多行删除:

{
   handler: function () {
   deleteEvent();
  }
}

掉用前端的deleteEvent方法

 function deleteEvent() {
            //1.拿到easyui里面被选中的项,参见jQuery EasyUI 1.2 API文档
            var selectedRows = $('#tt').datagrid("getSelections");
            if (selectedRows.length <= 0) {
                $.messager.alert("提醒", "请选择要删除的数据", "warning");
                return;
            }
            else {
                $.ligerDialog.confirm('确定要删除吗?', function (confirm) {
                    if (confirm) {
                        //2.把数据删除
                        //2.1获取要删除数据的ID
                        var ids = "";
                        for (var key in selectedRows) {
                            ids = ids + selectedRows[key].ID + ",";

                        }
                        ids = ids.substring(0, ids.length - 1);
                        //alert(ids);
                        //2.2把要删除的ID通过发送异步请求到后台去处理
                        $.post("/UserInfo/Delete", { ids: ids }, function (data) {
                            if (data == "ok") {
                                initTable();
                                //ligerui选中行删除后,释放选中状态
                                $("#tt").datagrid('clearSelections');
                            } else {
                                $.messager.alert("提示", "删除失败", "error");
                            }
                        });
                    }
                })
            }
        }

把拿到的前端要删除的数据的id通过ajax发送给后端UserInfo控制器的Delete方法
这个方法很特殊在业务逻辑层已经进行封装,传入 List即可

 var ids = "";
for (var key in selectedRows) {
ids = ids + selectedRows[key].ID + ",";
}

上述代码对前端选择的id进行处理,整合到一个能被后端识别的字符串

   public ActionResult Delete(string ids)
    {
        if (string.IsNullOrEmpty(ids))
        {
            return Content("请选中要删除的数据");
        }
        string[] strIds = ids.Split(',');
        List<int> idList = new List<int>();
        foreach (var strId in strIds)
        {
            idList.Add(int.Parse(strId));
        }
        //UserInfoBll.DeleteList(idList);
        UserInfoBll.DeleteListByLogical(idList);
        return Content("ok");
    }

如果你选择的是左侧的选择框进行多行删除,我们发送给后端的数据一定要先处理成能让后端的识别的字符串

  string[] strIds = ids.Split(',');

可以看的出,字符串通过“,”拼接即可
类似的,如果是通过单机链接进行删除,直接向后端返回一个要删除的id即可

综上所述,增删改查所有功能全部具备!

最后:
感谢你的阅读!希望对你们的学习有一定的思路帮助!

源代码上传至:
github

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值