http://www.cnblogs.com/xqin/p/4862849.html

一、前言

半年前左右折腾了一个前后端分离的架子,这几天才想起来翻出来分享给大家。关于前后端分离这个话题大家也谈了很久了,希望我这个实践能对大家有点点帮助,演示和源码都贴在后面。

二、技术架构

这两年angularjs和reactjs算是比较火的项目了,而我选择angularjs并不是因为它火,而是因它的模块化、双向数据绑定、注入、指令等都是非常适合架构较复杂的前端应用,而且文档是相当的全,碰到问题基本上可以在网上都找到答案。所以前端基本思路就以angularjs为主、代码模块化,通过requirejs实现动态加载,ui选择dhtmlx为主配合少量bootstrap3使用。前端项目dhtmlx_web:  
    开发工具 Sublime Text  
    前端框架angularjs 
    模块加载requirejs 
    前端UI dhtmlx + bt3  
    包管理 bower 
    构建工具 gruntjs 
    服务架设 http_server.js 
    浏览器支持IE8+ 实际是为了支持IE8我做了很多的努力,因为angluarjs 1.3已经不再支持IE8了,而我使用的angularjs是1.3.9 
    引入的一些其它类库或插件就不列出来了,太多了

服务端主要是提供restful数据服务,所以.net下毫无疑问选择asp.net webapi来实现了。 后端项目dhtmlx_webapi:  
    开发工具 VS2012 
    数据服务 Asp.net WebApi 
    跨域实现 CORS 
    依赖注入 Autofac 
    日志组件 Log4net 
    数据库已改为MS Access (为了方便大家可以直接运行)

三、前端介绍

1、基本说明   
      
    项目主要分了三个文件夹assets存放引用类库及插件,app中则是项目文件,build中存放构建后的文件,先让大家看几个实现的页面,再介绍代码吧

这是查询页面,查询条件、分页、排序都可用 

这是虚拟分页的实现,也实现了过滤行,我自己也是挺喜欢这种风格的。 

编辑页面 

2、程序入口 
以上的几个页面都比较典型,如果大家右键查看源码的话只能看到:

<!DOCTYPE html>
<html>
<head>
    <title>权限管理系统</title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link href="build/app.png" rel="shortcut icon" type="image/x-icon" />
    <link href="build/app.css" rel="stylesheet" />
    <!--[if lte IE 8]><script src="build/ie.js"></script><![endif]-->
</head>
<body ng-controller="myController">
    <div id="my_header"  my-header  ></div>
    <div id="my_layout"  my-layout  ></div>
    <div id="my_footer"  my-footer  ></div>
    <div id="my_setting" my-setting ></div>
    <div id="my_chat"    my-chat    ></div>
    <script src="build/app.js"></script>
</body>
</html>

那我们就从这里开始讲起,实际上我设计的这个前端也可以看做是单页面应用SPA,只有一个页面也就是index.html点左边菜单栏打开的新tab实际只是加载了一个ng的controller渲染出来的一个层而已,当然为了实用也支持输入一个页面地址。 
    当然这个页面是build之后的结果,build之前的index.src.html不忍直视,主要是因为不想引入所有的dhtmlx类库,只是选择性引入很多文件没有用一个dhtml.js替代

<!DOCTYPE html>
<html>
<head>
    <title>权限管理系统</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <link href="app/img/logo/chitu-32.png" rel="shortcut icon" type="image/x-icon" />
    <link href="assets/css/default/icons.css" rel="stylesheet" />
    <link href="assets/css/default/scaffolding.css" rel="stylesheet" />
    <link href="assets/css/default/helpers.css" rel="stylesheet" />
    <link href="assets/vendors/buttons/css/buttons.min.css" rel="stylesheet" />
    <link href="assets/lib/dhtmlx/v412_std/skins/default/dhtmlx.css" rel="stylesheet" />
    <link href="app/css/fix.css" rel="stylesheet" />
    <link href="app/css/app.css" rel="stylesheet" />
    <!--[if lte IE 8]>
        <script src="assets/lib/ie/html5shiv/html5shiv.min.js"></script>
        <script src="assets/lib/ie/es5-shim/es5-shim.min.js"></script>
        <script src="assets/lib/ie/json/json3.min.js"></script>
        <script src="assets/lib/ie/respond/respond.min.js"></script>
        <script src="assets/lib/ie/ieupdate/ieupdate.js"></script>
    <![endif]-->
</head>
<body ng-controller="myController">
    <div id="my_header"  my-header  ></div>
    <div id="my_layout"  my-layout  ></div>
    <div id="my_footer"  my-Footer  ></div>
    <div id="my_setting" my-setting ></div>
    <div id="my_chat"    my-chat    ></div>
    <script src="assets/lib/jquery/jquery-1.11.2.min.js"></script>
    <script src="assets/lib/jquery/jquery-ui.js"></script>
    <script src="assets/lib/jquery/jquery.cookie.js"></script>
    <script src="assets/vendors/jquery-pulsate/jquery.pulsate.custom.js"></script>
    <script src="assets/vendors/jquery-slimscroll/jquery.slimscroll.min.js"></script>
    <script src="assets/vendors/bootstrap/js/bootstrap.min.js"></script>
    <script src="assets/vendors/buttons/js/buttons.js"></script>
    <script src="assets/lib/angularjs/1.3.9/ie8/angular.js"></script>
    <!--<script src="assets/lib/dhtmlx/v403_pro/codebase/dhtmlx.js"></script>
    <script src="assets/lib/dhtmlx/v412_std/sources/dhtmlxTabbar/codebase/dhtmlxtabbar.js"></script>
    <script src="assets/lib/dhtmlx/dhtmlx.custom.js"></script>-->
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxCommon/codebase/dhtmlxcommon.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxCommon/codebase/dhtmlxcore.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxCommon/codebase/dhtmlxcontainer.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxLayout/codebase/dhtmlxlayout.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxAccordion/codebase/dhtmlxaccordion.js"></script>
    <script src="assets/lib/dhtmlx/v412_std/sources/dhtmlxTabbar/codebase/dhtmlxtabbar.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxTree/codebase/dhtmlxtree.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxTree/codebase/ext/dhtmlxtree_json.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxToolbar/codebase/dhtmlxtoolbar.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxMenu/codebase/dhtmlxmenu.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/dhtmlxgrid.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_drag.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_export.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_filter.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_nxml.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_selection.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_srnd.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_validation.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/excells/dhtmlxgrid_excell_tree.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/excells/dhtmlxgrid_excell_link.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/excells/dhtmlxgrid_excell_grid.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/excells/dhtmlxgrid_excell_dhxcalendar.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/excells/dhtmlxgrid_excell_cntr.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/excells/dhtmlxgrid_excell_acheck.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/excells/dhtmlxgrid_excell_context.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_start.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_data.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_fast.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_filter_ext.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_form.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_group.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_hextra.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_hmenu.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_json.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_markers.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_math.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_mcol.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_over.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_pgn.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_post.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_rowspan.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_splt.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_ssc.js"></script>
    <script src="assets/lib/dhtmlx/v403_pro/sources/dhtmlxGrid/codebase/ext/dhtmlxgrid_undo.js"></script>
    <script src="assets/lib/dhtmlx/dhtmlx.custom.js"></script>
    <script src="assets/lib/requirejs/require.min.js"></script>
    <script src="app/main.js"></script>
</body>
</html>

不过从过里可以看出,我们的入口文件是main.js

!function () {
    //config requirejs
    require.config({
        baseUrl: ‘./app/js/‘,
        paths: {
            assets: ‘../../assets/‘,
            css: ‘../../assets/lib/requirejs/css‘,
            text: ‘../../assets/lib/requirejs/text‘,
            views: ‘../views‘,
            config: ‘config/global‘,
            ‘angular-resource‘: ‘../../assets/lib/angularjs/1.3.9/angular-resource‘
        },
        shim: {},
        urlArgs: ‘v=201502100127&r=‘+Math.random()
    });

    //init main
    require([‘app‘,
        ‘config‘,
        ‘angular-resource‘,
        ‘service/index‘,
        ‘directive/index‘,
        ‘controller/index‘],
        function (app, config) {
            app.init();
        }
    );
}();

在main.js中,我们配置requriejs并且启动主程序,启动时载入了 
app、config、angular-resource、service/index、directive/index、controller/index这六个模块,那我们就看app模块,其它不再分析了,大家自己去看代码吧 
这里加载的app位于app/js/下的app.js文件

define(function (require) {
    ‘use strict‘;

    var app = angular.module(‘chituApp‘, [‘ngResource‘]);

    app.init = function () {
        angular.bootstrap(document, [‘chituApp‘]);
    };

    app.config(function ($controllerProvider, $provide, $compileProvider, $resourceProvider) {

        // Save the older references.
        app._controller = app.controller;
        app._service = app.service;
        app._factory = app.factory;
        app._value = app.value;
        app._directive = app.directive;

        // Provider-based controller.
        app.controller = function (name, constructor) {
            $controllerProvider.register(name, constructor);
            return (this);

        };

        // Provider-based service.
        app.service = function (name, constructor) {
            $provide.service(name, constructor);
            return (this);

        };

        // Provider-based factory.
        app.factory = function (name, factory) {
            $provide.factory(name, factory);
            return (this);

        };

        // Provider-based value.
        app.value = function (name, value) {
            $provide.value(name, value);
            return (this);

        };

        // Provider-based directive.
        app.directive = function (name, factory) {
            $compileProvider.directive(name, factory);
            return (this);

        };

        // $resource settings
        $resourceProvider.defaults.stripTrailingSlashes = false;
    });

    app.run(function (){
        //run some code here ...
    });

    return app;
});

3、与后台restful api交互 
数据服务我准备都放在service文件夹下,比如菜单的数据服务在service/index,目前是静态数据。不过项目所有的ajax访问都是由ngResource实现的,实际对$http的封装,$resource可以方便的与resultful接口接合,我们可以大大简化操作,我是比较推荐它,一个简单示例:

//定义
app.factory(‘api‘, [‘$resource‘, function ($resource) {
    return $resource(url,{query:{..},update:{..},remove:{..},get:{..},insert:{..}});
}]);

app.controller(‘test‘,[‘api‘, function (api) {
    //查询
    api.query(params, function(data){
        var list = data;
    });

    //获取并更新
    api.get({id:1}, function(data){
        data.name = ‘new name‘;
        data.update();
    });

    //新增
    api.insert(data);

    //删除
    api.remove({id:1});
}]);

4、页面组件化 
页面组件化思路基本就是依赖ng的指令,主页面上的各部分基本都是通过指令directive/index去渲染的,包括myHeader、myFooter、myLayout、mySetting、myChat五个指令分别实现各部分。一些通用的控件比如dhtmlxgrid、dhtmlxtoolbar我都写成了指令的方式,觉得以后常用的控件都可以用这种方式实现,方便而且还可以提高代码重用性。

define([‘app‘], function (app) {
    app.directive(‘dhtmlxgrid‘, function ($resource) {
        return {
            restrict: ‘A‘,
            replace: true,
            scope: {
                fields: ‘@‘,
                header1: ‘@‘,
                header2: ‘@‘,
                colwidth: ‘@‘,
                colalign: ‘@‘,
                coltype: ‘@‘,
                colsorting: ‘@‘,
                pagingsetting: ‘@‘,
                autoheight: ‘=‘,
                url: ‘@‘,
                params:‘@‘
            },
            link: function (scope, element, attrs) {
                scope.uid = app.genStr(12);
                element.attr("id", "dhx_grid_" + scope.uid);
                element.css({ "width": "100%", "border-width": "1px 0 0 0"});
                scope.grid = new dhtmlXGridObject(element.attr("id"));
                scope.header1    && scope.grid.setHeader(scope.header1);
                scope.header2    && scope.grid.attachHeader(scope.header2);
                scope.fields     && scope.grid.setFields(scope.fields);
                scope.colwidth   && scope.grid.setInitWidths(scope.colwidth)
                scope.colalign   && scope.grid.setColAlign(scope.colalign)
                scope.coltype    && scope.grid.setColTypes(scope.coltype);
                scope.colsorting && scope.grid.setColSorting(scope.colsorting);

                scope.grid.entBox.onselectstart = function () { return true; };

                if (scope.pagingsetting) {
                    var pagingArr = scope.pagingsetting.split(",");
                    var pageSize = parseInt(pagingArr[0]);
                    var pagesInGrp = parseInt(pagingArr[1]);
                    var pagingArea = document.createElement("div");
                    pagingArea.id = "pagingArea_" + scope.uid;
                    pagingArea.style.borderWidth = "1px 0 0 0";
                    var recinfoArea = document.createElement("div");
                    recinfoArea.id = "recinfoArea_" + scope.uid;
                    element.after(pagingArea);
                    element.after(recinfoArea);
                    scope.grid.enablePaging(true, pageSize, pagesInGrp, pagingArea.id, true, recinfoArea.id);
                    scope.grid.setPagingSkin("toolbar", "dhx_skyblue");
                    scope.grid.i18n.paging = {
                        results: "结果",
                        records: "显示",
                        to: "-",
                        page: "页",
                        perpage: "行每页",
                        first: "首页",
                        previous: "上一页",
                        found: "找到数据",
                        next: "下一页",
                        last: "末页",
                        of: " 的 ",
                        notfound: "查询无数据"
                    };
                }

                scope.grid.setImagePath(app.getProjectRoot("assets/lib/dhtmlx/v403_pro/skins/skyblue/imgs/"));
                scope.grid.init();

                if (scope.autoheight) {
                    var resizeGrid = function () {
                        element.height(element.parent().parent().height() - scope.autoheight);
                        scope.grid.setSizes();
                    };
                    $(window).resize(resizeGrid);
                    resizeGrid();
                }

                //scope.grid.enableSmartRendering(true);

                if (scope.url) {
                    var url = app.getApiUrl(scope.url);
                    var param = scope.$parent[scope.params] || {};
                    var api = $resource(url, {}, { query: { method: ‘GET‘, isArray: false } });
                    scope.grid.setQuery(api.query, param);
                }

              //保存grid到父作用域中
                attrs.dhtmlxgrid && (scope.$parent[attrs.dhtmlxgrid] = scope.grid);
            }
        };
    });

    app.directive(‘dhtmlxtoolbar‘, function () {
        return {
            restrict: ‘A‘,
            replace: false,
            scope: {
                iconspath: ‘@‘,
                items:‘@‘
            },
            link: function (scope, element, attrs) {
                scope.uid = app.genStr(12);
                element.attr("id", "dhx_toolbar_" + scope.uid);
                element.css({ "border-width": "0 0 1px 0" });

                scope.toolbar = new dhtmlXToolbarObject(element.attr("id"));
                scope.toolbar.setIconsPath(app.getProjectRoot(scope.iconspath));
                var items = eval("(" + scope.items + ")");
                //scope.toolbar.loadStruct(items);

                var index = 1;
                var eventmap = {};
                for (var i in items) {
                    var item = items[i];
                    if (item.action)
                        eventmap[item.id] = item.action;

                    if (item.type == ‘button‘) {
                        scope.toolbar.addButton(item.id, index++, item.text, item.img, item.imgdis);
                        item.enabled == false && scope.toolbar.disableItem(item.id);
                    }
                    else if (item.type == ‘separator‘) {
                        scope.toolbar.addSeparator(index++);
                    }
                }

                scope.toolbar.attachEvent("onClick", function (id) {
                    var name = eventmap[id];
                    if (name && scope.$parent[name] && angular.isFunction(scope.$parent[name]))
                        scope.$parent[name].call(this);
                });

                attrs.dhtmlxtoolbar && (scope.$parent[attrs.dhtmlxtoolbar] = scope.toolbar);            }
        }
    });
});

5、前端文件分类(文件夹介绍) 
再来说说js文件的分类,实际上是根据angularjs的特点,把所有的js分为以下几个文件夹,都是angular的概念我可能没法一下子跟大家解释清楚,请大家自己去了解。 
app/js 
│  
├─config        配置 
├─constant    常量  
├─controller   控制器 
├─decorator   修饰器 
├─directive    指令 
├─factory      工厂 
├─filter          过滤器 
├─provider     提供商 
├─route         路由 
├─service      服务 
└─value        值 
其中config、constant、value都是固定值,其中config属于自定义的 
controller控制器、directive指令、route路由、filter过滤器大家都很熟悉我就不说了, 
factory工厂、provider提供商、service服务这三者很像的,反正你都可以把它们看作是provider,至于区别看看下面源码自己体会(angularjs1.4.0源码第4027行) 
至于修饰器decorator就是对现有的provider进行加工修饰的

function provider(name, provider_) {
  assertNotHasOwnProperty(name, ‘service‘);
  if (isFunction(provider_) || isArray(provider_)) {
    provider_ = providerInjector.instantiate(provider_);
  }
  if (!provider_.$get) {
    throw $injectorMinErr(‘pget‘, "Provider ‘{0}‘ must define $get factory method.", name);
  }
  return providerCache[name + providerSuffix] = provider_;
}

function enforceReturnValue(name, factory) {
  return function enforcedReturnValue() {
    var result = instanceInjector.invoke(factory, this);
    if (isUndefined(result)) {
      throw $injectorMinErr(‘undef‘, "Provider ‘{0}‘ must return a value from $get factory method.", name);
    }
    return result;
  };
}

function factory(name, factoryFn, enforce) {
  return provider(name, {
    $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
  });
}

function service(name, constructor) {
  return factory(name, [‘$injector‘, function($injector) {
    return $injector.instantiate(constructor);
  }]);
}

6、前端权限控制 
    为什么我没有选择使用route + ngView来构建呢,网上有关angularjs的SPA基本上都是这个方案的,像我这样打开多个TAB页签层的几乎没有。主要是因为我自己一直在做企业应用,同时打开多个页面的tab页签方式会比只能打开一个页面的ng-view方便很多,所以选择自己去实现tab页的方式。但是这么一来权限控制就不能放在路由当中了,需要自己在处理tab页签时控制。 
    前端权限控制的思路是这样的:打开主页面时即加载身份认证数据、菜单数据及相关的其它权限数据,在打开tab页签处理时进行权限验证。 
    数据访问的权限控制可以这样做,登陆时计算得到一个Ticket,每次访问数据时在request header中添加这个Ticket,然后在服务端每次请求时对Ticket进行验证,前端可以在app.js中配置到$httpProvider.defaults.headers.post当中,服务端具体怎么处理介绍到服务端再说。

7、关于UI类库的说明 
    接下来说下UI的东西,适合自己的UI都会有需要改的如easyui我也改了很多,同样dhtmlx这款UI也有很多不合适或有问题的地方,比如不支持字体图标,比如grid更好的数据加载机制等等,我对它的修改集中放在dhtml.custom.js中,把它改到我能用或更好用,花费了我大量的时间,因为需要读懂它的源码后才能修改,具体修改大家可以查看这个文件。

还有大家可能会好奇我为什么在项目中还有引入

<script src="assets/lib/jquery/jquery-ui.js"></script>
<script src="assets/lib/jquery/jquery.cookie.js"></script>
<script src="assets/vendors/jquery-pulsate/jquery.pulsate.custom.js"></script>
<script src="assets/vendors/jquery-slimscroll/jquery.slimscroll.min.js"></script>
<script src="assets/vendors/bootstrap/js/bootstrap.min.js"></script>
<script src="assets/vendors/buttons/js/buttons.js"></script>

其实就是只使用dhtmlx开发出来的页面是很单调的,这些类库我在组件中都有用到,比如myheader中的这些,还有表单控件dhtmlx的form太丑了,所以引入bt3 

8、关于nodejs的工具使用 
这不是我们的重点,你只要知道npm install命令下载node_modules,然后就是运行grunt及httpjs,关于它们的详细介绍及如何配置gruntfile大家自己去了解,最后代码修改完成之后,记得运行grunt重新构建。

四、服务端介绍 
   

服务端大家都很熟悉,没什么说的。 
    关于服务端为什么没分层,这里仅仅是一个数据服务而已,没必要太复杂所以我就没分层了。如果业务比较复杂是可以把控制层Controller、服务层Sevice分开的,同时也衍生出接口层 Interface及数据实体层Entity,关于服务层是不是需要再分或是不同业务要不要分开就要看具体情况了,看过一段时间的DDD,感觉是从技术上把简单的问题复杂化了,觉得实际做项目是没必要,可能是我的理解不够深,我的理念还是能满足需求的情况下能简则简。

1、配置 
在Global.asax.cs中添加FrameworkConfig.Register(),在FrameworkConfig.cs中

Configuration.Instance()    .RegisterComponents()           //注册公共组件
    .RegisterDependencyResolver()   //注册依赖注入
    .RegisterProjectModules()       //注册项目模块
    .RegisterHttpCorsSupport();     //开启CORS支持

2、示例 
然后看一个租户数据示例,这个地址/api/tenant,请求进来之后控制器接收处理TenantController.cs

public class TenantController : ApiController
{
    private ITenantService _tenantService;
    private RequestParameter _requestParameter;

    /// <summary>
    /// 注入参数及服务
    /// </summary>
    /// <param name="tenantService"></param>
    /// <param name="requestParameter"></param>
    public TenantController(ITenantService tenantService, RequestParameter requestParameter)
    {
        _tenantService = tenantService;
        _requestParameter = requestParameter;
    }

    /// <summary>
    /// 查询返回多条租户数据结果集
    /// </summary>
    /// <returns></returns>
    public Paging<bas_tenant> Get()
    {
        var query = _requestParameter.ReadAsQueryEntity();
        var result = _tenantService.GetTenantListWithPaging(query);
        return result;
    }

    /// <summary>
    /// 创建新的租户信息
    /// </summary>
    public string Post([FromBody]bas_tenant tenant)
    {
        return _tenantService.Insert(tenant);
    }

    /// <summary>
    /// 查询返回单个租户信息
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public bas_tenant Get(string id)
    {
        return _tenantService.GetTenant(id);
    }

    /// <summary>
    /// 更新租户信息
    /// </summary>
    /// <param name="id"></param>
    public void Put(string id,[FromBody]bas_tenant tenant)
    {
        _tenantService.Update(id, tenant);
    }

    /// <summary>
    /// 删除租户信息
    /// </summary>
    /// <param name="id"></param>
    public void Delete(string id)
    {
        _tenantService.Delete(id);
    }
}

服务中处理TenantService.cs

public class TenantService : ITenantService
{
    private IUser _user;
    private IDbContext _db;

    //注入用户信息及数据访问上下文
    public TenantService(IUser user,IDbContext db)
    {
        _user = user;
        _db = db;
    }

    //不分页查询
    public List<bas_tenant> GetTenantList()
   {
        var result = _db.Select("*")
            .From("bas_tenant")
            .QueryMany<bas_tenant>();

        return result;
    }

    //分页查询    public Paging<bas_tenant> GetTenantListWithPaging(QueryEntity qe)
   {
        var result = _db.Select("*")
            .From("bas_tenant")
            .Where<_StartWith>("tenant_id,tel", qe).IgnoreEmpty()               //商户编码、手机号     使用startwith查询 忽略空值
            .Where<_Like>("tenant_name,charge_person,addr", qe).IgnoreEmpty()   //商户名、责任人、地址 使用like查询      忽略空值
            .Where<_Eq>("*", qe).IgnoreEmpty()                                  //剩下的其它的字段都   使用equal查询     忽略空值
            .Paging(qe, "tenant_id")                                            //分页参数在qe中 默认按商户编码排序 自动处理页面上的排序、分页请求
            .QueryManyWithPaging<bas_tenant>();

        return result;
    }

    //获取单条记录
    public bas_tenant GetTenant(string id)
   {
        var result = _db.Select("*")
            .From("bas_tenant")
            .Where("tenant_id",id)
            .QuerySingle<bas_tenant>();

        return result;
    }

    //更新
    public int Update(string id, bas_tenant tenant)
   {
        var result = _db.Update("bas_tenant", tenant)
            .AutoMap(x => x.tenant_id)
            .Where("tenant_id", id)
            .Execute();

        return result;
    }

    //添加
    public string Insert(bas_tenant tenant)
   {
        tenant.tenant_id = "T" + (new Random().Next(100) + 500).ToString();

        var result = _db.Insert("bas_tenant", tenant)
            .AutoMap()
            .Execute();

        return tenant.tenant_id;
    }

    //删除
    public int Delete(string id)
   {
        var result = _db.Delete("bas_tenant")
            .Where("tenant_id", id)
            .Execute();

        return result;
    }
}

3、权限认证 
在前端权限控制中我们说了,$http请求时request header中添加了身份认证的Ticket,我们要在每一次请求返回数据前都要验证这个Ticket,当然不能写在每个方法当中了,可以在过滤器中实现:

public class TicketAuthorizeAttribute : AuthorizationFilterAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        bool isAuthorizated = false;
        IPrincipal principal = Thread.CurrentPrincipal;

        var RequestHeader = actionContext.Request.Headers;
        if (RequestHeader.Contains("Ticket"))
        {
            //在这里验证Ticket
            string requestTicket = RequestHeader.GetValues("Ticket").First();
            string serverTicket = EncryptHelper.MD5(...);
            if (requestTicket == serverTicket)
                isAuthorizated = true;
        }

        if (!isAuthorizated)
            actionContext.Response = actionContext.ControllerContext.Request                .CreateErrorResponse(HttpStatusCode.Unauthorized, "已拒绝为此请求授权。");
    }
}

五、源码下载

演示地址:http://zoewin.eicp.net:8081/ (前端页面架设在8081,数据服务架设在8082)不稳定有时不能访问

前端项目 dhtmlx_web:     https://github.com/liuhuisheng/dhtmlx_web 
    后端项目 dhtmlx_webapi: https://github.com/liuhuisheng/dhtmlx_webapi

大家下载代码后直接打开index.html是无效的因为其中有很多的ajax请求必须架设在web服务上。 如果没有nodejs的环境,也可以用VS打开运行或架设到IIS上。 
如果大家有nodejs的环境,可以运行目录下的http.bat实现上是调用nodejs的http简易服务程序,注意端口我写死了是8080,自己去修改。

单独运行前端项目也能打开页面,但是没有动态数据,大家可以先运行服务端程序,运行起来后比如端口为8082,那么其数据服务地址为:http://localhost:8082/api/ 
只要把这个服务端地址复制到前端项目的配置js/config/global.js中再运行前端项目就可以看到数据了。

这个架子折腾了一阵子可能还有一些不完整的地方,共享出来权当给大家一个参考,如果大家有什么意见或建议可以给我留言。 
如果大家感兴趣就在右下角帮我【推荐】一下吧,谢谢大家。

时间: 2024-10-10 15:48:19

http://www.cnblogs.com/xqin/p/4862849.html的相关文章

通用的业务编码规则设计实现[转:http://www.cnblogs.com/xqin/p/3708367.html]

一.背景 每一个企业应用中不可避免的都会涉及到业务编码规则的问题,比如订单管理系统中的订单编号,比如商品管理系统中的商品编码,比如项目管理系统中的项目编码等等,这一系列的编码都需要管理起来,那么它们的应该如何编码的,具体的编码规则我们很多时候都是直接写在程序当中 常用的的编码有: 1.数据库自增长ID或最大值加1 2.GUID 3.时间戳 4.常量+自增长 5.常量+时间戳+自增长 6.根据单据属性编码 比如商品编码:第X是代码商品颜色,第Y位是代码商品产地 7.自定义函数处理返回 8.其它 添

[开源] angularjs + Asp.net 前后端分离解决方案

本文版权归 博客园 萧秦 所有,此处为技术收藏,如有再转,请于篇头明显位置标明原创作者及出处,以示尊重! 作者:萧秦 原文:http://www.cnblogs.com/xqin/p/4862849.html 一.前言 半年前左右折腾了一个前后端分离的架子,这几天才想起来翻出来分享给大家.关于前后端分离这个话题大家也谈了很久了,希望我这个实践能对大家有点点帮助,演示和源码都贴在后面. 二.技术架构 这两年angularjs和reactjs算是比较火的项目了,而我选择angularjs并不是因为它

材料管理框架:一个共通的viewModel搞定所有的分页查询

前言 大家看标题就明白了我想写什么了,在做企业信息化系统中可能大家写的最多的一种页面就是查询页面了.其实每个查询页面,除了条件不太一样,数据不太一样,其它的其实都差不多.所以我就想提取一些共通的东西出来,再写查询时只要引入我共通的东西,再加上极少的代码就能完成.我个人比较崇尚代码简洁干净,有不合理的地方欢迎大家指出. 这篇文章主要介绍两个重点:1.前台viewModel的实现.2.后台服务端如何简洁的处理查询请求. 需求分析 查询页面要有哪些功能呢 1.有条件部输入查询条件(这个不打算做成共通的

2016年4月14日

1.配置返回的json对象为小写开头 在路由配置里面,Register(HttpConfiguration config)中路由配置下边添加: var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First(); jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

一个共通的viewModel搞定所有的分页查询一览及数据导出(easyui + knockoutjs + mvc4.0)

前言 大家看标题就明白了我想写什么了,在做企业信息化系统中可能大家写的最多的一种页面就是查询页面了.其实每个查询页面,除了条件不太一样,数据不太一样,其它的其实都差不多.所以我就想提取一些共通的东西出来,再写查询时只要引入我共通的东西,再加上极少的代码就能完成.我个人比较崇尚代码简洁干净,有不合理的地方欢迎大家指出. 这篇文章主要介绍两个重点:1.前台viewModel的实现.2.后台服务端如何简洁的处理查询请求. 需求分析 查询页面要有哪些功能呢 1.有条件部输入查询条件(这个不打算做成共通的

通用的web系统数据导出功能设计实现(导出excel2003/2007 word pdf zip等)

转自:http://www.cnblogs.com/xqin/p/3165258.html 前言 我们在做web系统中,导出也是很常用的一个功能,如果每一个数据列表都要对应写一个导出的方法不太现实.现在就想设计一个共通的功能来实现这个导出. 需求分析 在开始之前我们先要明白我们要实现怎样一个功能 1.支持导出excel2003/excel2007 word pdf等文件格式 2.支持数据分页,可以导出全部页或指定页 3.支持导出的文档再压缩zip rar 7z 4.支持导出多行的题头 5.支持格

一个共通的viewModel搞定所有的编辑页面-经典ERP录入页面(easyui + knockoutjs + mvc4.0)

http://www.cnblogs.com/xqin/archive/2013/06/06/3120887.html 前言 我写代码喜欢提取一些共通的东西出来,之前的一篇博客中说了如何用一个共通的viewModel和简洁的后台代码做查询页面,所有的查询页面都要对应一个数据录入的编辑及查看明细的页面,那么今天我们就来实现这个页面,同样我们也要使用一个共通的viewModel完成前台UI与JSON数据交互的处理,同样以超简洁的后台代码来处理保存. 需求分析 我们先弄明白我们要做怎么样一个编辑的页面

转-Asp.Net MVC及Web API框架配置会碰到的几个问题及解决方案

前言 刚开始创建MVC与Web API的混合项目时,碰到好多问题,今天拿出来跟大家一起分享下.有朋友私信我问项目的分层及文件夹结构在我的第一篇博客中没说清楚,那么接下来我就准备从这些文件怎么分文件夹说起.问题大概有以下几点: 1.项目层的文件夹结构 2.解决MVC的Controller和Web API的Controller类名不能相同的问题 3.给MVC不同命名空间的Area的注册不同的路由 4.让Web API路由配置也支持命名空间参数 5.MVC及Web API添加身份验证及错误处理的过滤器

分时区查询问题解决

原文地址:http://www.cnblogs.com/xqin/archive/2013/06/03/3114634.html 解决方法: 视图: <div class="grid_1 lbl">查询时间</div> <div class="grid_2 val"><input type="text" data-bind="value:form.deliverytime" class