【開源】分享一個先後端分離方案-前端angularjs+requirejs+dhtmlx 後端asp.net webapi

1、前言

    半年前左右折騰了一個先後端分離的架子,這幾天纔想起來翻出來分享給你們。關於先後端分離這個話題你們也談了好久了,但願我這個實踐能對你們有點點幫助,演示和源碼都貼在後面。css

2、技術架構

    這兩年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
    引入的一些其它類庫或插件就不列出來了,太多了 html

服務端主要是提供restful數據服務,因此.net下毫無疑問選擇asp.net webapi來實現了。 後端項目dhtmlx_webapi: 
    開發工具 VS2012
    數據服務 Asp.net WebApi
    跨域實現 CORS
    依賴注入 Autofac
    日誌組件 Log4net
    數據庫已改成MS Access (爲了方便你們能夠直接運行) 前端

3、前端介紹

    一、基本說明  
    image 
    項目主要分了三個文件夾assets存放引用類庫及插件,app中則是項目文件,build中存放構建後的文件,先讓你們看幾個實現的頁面,再介紹代碼吧 

這是查詢頁面,查詢條件、分頁、排序均可用
imagehtml5

這是虛擬分頁的實現,也實現了過濾行,我本身也是挺喜歡這種風格的。
imagenode

編輯頁面
image react

    二、程序入口
以上的幾個頁面都比較典型,若是你們右鍵查看源碼的話只能看到: jquery

<!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替代 git

<!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.jsangularjs

!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文件 github

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;
});

   
    三、與後臺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});
}]);


    四、頁面組件化
頁面組件化思路基本就是依賴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);            }
        }
    });
});


    五、前端文件分類(文件夾介紹)
再來講說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);
  }]);
}


    六、前端權限控制
    爲何我沒有選擇使用route + ngView來構建呢,網上有關angularjs的SPA基本上都是這個方案的,像我這樣打開多個TAB頁籤層的幾乎沒有。主要是由於我本身一直在作企業應用,同時打開多個頁面的tab頁籤方式會比只能打開一個頁面的ng-view方便不少,因此選擇本身去實現tab頁的方式。可是這麼一來權限控制就不能放在路由當中了,須要本身在處理tab頁籤時控制。
    前端權限控制的思路是這樣的:打開主頁面時即加載身份認證數據、菜單數據及相關的其它權限數據,在打開tab頁籤處理時進行權限驗證。
    數據訪問的權限控制能夠這樣作,登錄時計算獲得一個Ticket,每次訪問數據時在request header中添加這個Ticket,而後在服務端每次請求時對Ticket進行驗證,前端能夠在app.js中配置到$httpProvider.defaults.headers.post當中,服務端具體怎麼處理介紹到服務端再說。

    七、關於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
imageimageimage

    八、關於nodejs的工具使用
這不是咱們的重點,你只要知道npm install命令下載node_modules,而後就是運行grunt及httpjs,關於它們的詳細介紹及如何配置gruntfile你們本身去了解,最後代碼修改完成以後,記得運行grunt從新構建。

4、服務端介紹
   image

    服務端你們都很熟悉,沒什麼說的。
    關於服務端爲何沒分層,這裏僅僅是一個數據服務而已,不必太複雜因此我就沒分層了。若是業務比較複雜是能夠把控制層Controller、服務層Sevice分開的,同時也衍生出接口層 Interface及數據實體層Entity,關於服務層是否是須要再分或是不一樣業務要不要分開就要看具體狀況了,看過一段時間的DDD,感受是從技術上把簡單的問題複雜化了,以爲實際作項目是不必,多是個人理解不夠深,個人理念仍是能知足需求的狀況下能簡則簡。

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

Configuration.Instance()
.RegisterComponents()
//註冊公共組件 .RegisterDependencyResolver() //註冊依賴注入 .RegisterProjectModules() //註冊項目模塊 .RegisterHttpCorsSupport(); //開啓CORS支持


二、示例
而後看一個租戶數據示例,這個地址/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; } }

三、權限認證
在前端權限控制中咱們說了,$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, "已拒絕爲此請求受權。"); } }


5、源碼下載

    演示地址: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中再運行前端項目就能夠看到數據了。

這個架子折騰了一陣子可能還有一些不完整的地方,共享出來權當給你們一個參考,若是你們有什麼意見或建議能夠給我留言。
若是你們感興趣就在右下角幫我【推薦】一下吧,謝謝你們。

相關文章
相關標籤/搜索