快速开发之代码生成器(asp.net mvc4 + easyui + knockoutjs)

一、前言

作为一个码农这么多年,一直在想怎么提高我们的编码效率,关于如何提高编码效率,我自己的几点体会

1、清晰的项目结构,要编写代码的地方集中
2、实现相同功能的代码量少并且清晰易懂
3、重复或有规律的代码应该自动生成

在这里我就讨论下代码生成的问题。

二、关于代码生成器

刚毕业时我也非常迷信代码生成器,喜欢在网上找一些代码生成器及相关的源码,喜欢在和网友讨论哪款生成器最好用,但实际上很少真正用这些东西来开发项目,原因很简单:
1、生成出来的代码不是我们要的代码
2、生成后的代码再修修改改,其实还没有我的ctrl+c和ctrl+v速度快。
3、生成的基本上是实体类及sql拼接代码,现在直接用linq或一些好用的orm多方便,谁还用SQLHelper加sql文拼接?
4、b/s项目中没有一个生成器能很好的能生成UI层代码及前端交互的js代码,即使能生成也是简单的页面。

所以,我劝大家不要迷信代码生成器了。它的确可以提高我们的效率,但是并不是网上你找一个生成器就行的。代码生成器它只是一个模板引擎而已,最重要的不是代码生成器本身,而是对一类功能或一类页面的代码规范,对自己代码的提炼,提炼出一个通用的模板。

比如我们常见的查询页面,录入页面等,我们只要提炼一个标准的查询页面的代码,包括前台html,前台js,后台控制器,后台数据服务。然后把它写成模板,再利用模板引擎就可以生成我们需要的代码了。

代码生成器本身就是模板引擎,所以我觉得最好的代码生成器不是网上流传的那些可以生成三层架构代码的软件,而是微软的razor引擎,非常简洁易懂,而且做过asp.net mvc项目的朋友应该也很熟悉。我个人觉得这是用来做代码生成最好的引擎。

三、页面模板

我们还是会想要快速开发,比如我选择了一些设定之后,就可以直接生成我想要的代码包括html及js,拷贝到项目中就可以直接运行,运行后就看到我想要的页面,基本功能都有。当然这里所说的快速开发是建立在我对页面功能的提炼模板之上的。实际上我提炼了三种模板:
1、查询页面
这个模板可以解决大部分的查询报表业务功能
image

2、编辑页面
这个编辑模板可以解决基本上所有的录入功能,因为包括了主表,及多个从表(1:N或1:1)录入,而且可以一次性在同一事务中保存。并且定义了很多触发前后事件用于写一些业务处理,并且做到差异更新。
image

3、查询编辑页面,可以查询也可以直接在grid中编辑,这个页面用于做一些简单单据或一些基础数据页面。image

四、代码生成原理

把以上页面代码做成razor模板,razor模板 + 设定选项 ==razor引擎==> 页面代码

怎么利用razor引擎,其实有以下几种方法:
1、直接利用mvc的view输出功能,以下为关键代码

var stringWriter = new StringWriter();
var viewContext = new ViewContext(controllerContext, view, viewData, TempData, stringWriter);
view.Render(viewContext, stringWriter);
var result = stringWriter.ToString();

用这种方法的优点在于不需要引入第三方类库,直接调用mvc视图的Render方法生成,而且效率很高,缺点是controllerContext及view对象的构建获取非常复杂。这种方法适用于有洁辟的码农们,我属于这一种。

2、利用第三方类库RazorEngine输出,以下为关键代码

var template = "Hello @Model.Name! Welcome to Razor!";
var viewData = new { Name = "World" });
var result = Razor.Parse(template, viewData);

这代码很清爽,一目了然,只不过要引入RazorEngine类库,而且效率不如前者。

五、代码生成页面的源码

我们模板准备好了,引擎准备好了,那么还需要一个数据输入viewData,我们做用户界面的目的也就是为了更好的定义这个viewData。 
UI展现主要是用了easyui 及jquery插件smartwizard
前端交互主要是采用了knockoutjs
table表格的行拖拉是采用jquery插件tableDnD
后台用webapi来处理请求,代码有点长:

Index.cshtml

@{
    ViewBag.Title = "代码生成";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@section head{
    <link href="~/Content/js/jquery-plugins/smartwizard/smart_wizard.css" rel="stylesheet" />
    <style type="text/css">
        div#navigation{float: left;width: 180px;}
        div#wrapper{float: right;width: 100%;margin-left: -185px;}
        div#wizard{margin-left: 185px;}
        ul.anchor{margin:0 0 10px 0 !important;}
        ul li{margin-left:-16px;}
        .grid .z-txt{margin:0 -3px;width:90%;} 
        .grid input{width:90%;}
        .grid input[type=checkbox]{cursor:default;}
        .grid select{width:80%;padding:0 !important;height:22px;}
        .grid select + a{margin:5px;}
        .tDnD_whileDrag{background-color: #FBEC88 !important;}
    </style>
}

@section scripts{
    <script src="~/Content/js/jquery-plugins/smartwizard/jquery.smartWizard.js"></script>
    <script src="~/Content/js/jquery-extend/jquery.tablednd.js"></script>
    @Scripts.Render("~/Resource/Sys/Generator.js")
    <script type="text/javascript">
        $(function () {
            ko.applyBindings(new viewModel());
        });
    </script>
}

<div id="container">
    <div id="navigation">
        <div class="panel-header" style="width: 168px; border-width: 0; background: #FAFAFA;">
            代码类别 
            <input type="text" class="z-txt" data-bind="easyuiCombobox:codetype" />
            <div style="margin:1px;"></div>
            数据库名 
            <input type="text" class="z-txt" data-bind="easyuiCombobox:database" />

            <div style="margin:5px;"></div>
             <div  data-bind="autoheight:60"  style="width: 172px; border-width: 0;margin:0;padding:0; background: #FAFAFA; overflow:auto;">
                <ul data-bind="easyuiTree:tabletree"></ul>
            </div>
        </div>
    </div>
    <div id="wrapper">
        <div id="wizard" class="swMain" style="width:100%"></div>
    </div>
</div>

<script id="template-searchEdit" type="text/html">
    <ul>
        <li><a href="#step-1">
            <label class="stepNumber">1</label>
            <span class="stepDesc">设置条件部<br />
                <small>定义查询条件</small>
            </span>
        </a></li>
        <li><a href="#step-2">
            <label class="stepNumber">2</label>
            <span class="stepDesc">设置数据列<br />
                <small>定义查询显示的数据字段</small>
            </span>
        </a></li>
        <li><a href="#step-3">
            <label class="stepNumber">3</label>
            <span class="stepDesc">其它设置<br />
                <small>修改其它代码生成设置</small>
            </span>
        </a></li>
    </ul>

    <div id="step-1" class="step">
        <h2 class="StepTitle">第一步 请勾选要查询的字段</h2>
        <div>  
            <div style="width:200px;float:left;overflow:auto;" data-bind="autoheight:172">
                    <ul data-bind="easyuiTree:searchEdit.columntree"></ul>
            </div>  
            <div style="float:left;overflow:auto" data-bind="autoheight:172,autowidth:405">
                <table class="grid">
                    <thead>
                        <tr>
                            <th style="width:50px">字段</th>
                            <th style="width:120px">显示名称</th>
                            <th style="width:120px">控件类型</th>
                            @*<th >参数</th>*@
                            <th style="width:80px">查询逻辑</th>
                        </tr>
                    </thead>
                    <tbody data-bind="foreach:form.conditions">
                        <tr data-bind="attr:{id:$index}">
                            <td data-bind="text:field" style="text-align:left"></td>
                            <td><input type="text" class="z-txt" data-bind="value:title"/></td>
                            <td><select class="z-txt"  data-bind="options:$root.data.input,value:type"></select></td>
                            @*<td><input type="text" class="z-txt" data-bind="value:options"/></td>*@
                            <td><select class="z-txt"  data-bind="options:$root.data.compare,value:cp"></select></td>
                        </tr>

                    </tbody>
                </table>
            </div>  
        </div>
    </div>
    <div id="step-2" class="step">
        <h2 class="StepTitle">第二步 请勾选要显示的数据字段</h2>
        <div style="width:200px;float:left;overflow:auto;" data-bind="autoheight:172">
            <ul data-bind="easyuiTree:searchEdit.columntree2"></ul>
        </div>  
        <div style="float:left;overflow:auto" data-bind="autoheight:172,autowidth:405">
            <table class="grid">
                <thead>
                    <tr>
                        <th style="width:50px">字段</th>
                        <th style="width:100px">题头</th>
                        <th style="width:30px">隐藏</th>
                        <th style="width:30px">排序</th>
                        <th style="width:50px">对齐</th>
                        <th style="width:40px">宽度</th>
                        <th style="width:50px">格式化</th>
                        <th style="width:50px">编辑器</th>
                    </tr>
                </thead>
                <tbody data-bind="foreach:form.columns">
                    <tr data-bind="attr:{id:$index}">
                        <td data-bind="text:field" style="text-align:left"></td>
                        <td><input type="text" class="z-txt" data-bind="value:title" /></td>
                        <td><input type="checkbox" data-bind="checked:hidden"/></td>
                        <td><input type="checkbox" data-bind="checked:sortable"/></td>
                        <td><select class="z-txt"  data-bind="options:$root.data.align,value:align" ></select></td>
                        <td><input type="text" class="z-txt" data-bind="value:width" /></td>
                        <td><select class="z-txt"  data-bind="options:$root.data.formatter,optionsText:'text',optionsValue:'value',value:formatter" ></select></td>
                        <td><select class="z-txt"  data-bind="options:$root.data.editor,optionsText:'text',optionsValue:'value',value:editor" ></select></td>
                    </tr>
                </tbody>
            </table>
        </div>  
    </div>

     <div id="step-3" class="step">
        <h2 class="StepTitle">第三步 其它设置</h2>

        <div class="container_12">
            <div class="grid_1 lbl">业务区域</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.area"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">控制器名称</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.controller"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">生成路径</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.path"/></div>
        </div>
    </div>
</script>

<script id="template-search" type="text/html">
    <ul>
        <li><a href="#step-1">
            <label class="stepNumber">1</label>
            <span class="stepDesc">设置条件部<br />
                <small>定义查询条件</small>
            </span>
        </a></li>
        <li><a href="#step-2">
            <label class="stepNumber">2</label>
            <span class="stepDesc">设置数据列<br />
                <small>定义查询显示的数据字段</small>
            </span>
        </a></li>
        <li><a href="#step-3">
            <label class="stepNumber">3</label>
            <span class="stepDesc">其它设置<br />
                <small>修改其它代码生成设置</small>
            </span>
        </a></li>
    </ul>

    <div id="step-1" class="step">
        <h2 class="StepTitle">第一步 请勾选要查询的字段</h2>
        <div>  
            <div style="width:200px;float:left;overflow:auto;" data-bind="autoheight:172">
                    <ul data-bind="easyuiTree:searchEdit.columntree"></ul>
            </div>  
            <div style="float:left;overflow:auto" data-bind="autoheight:172,autowidth:405">
                <table class="grid">
                    <thead>
                        <tr>
                            <th style="width:50px">字段</th>
                            <th style="width:120px">显示名称</th>
                            <th style="width:120px">控件类型</th>
                            @*<th >参数</th>*@
                            <th style="width:80px">查询逻辑</th>
                        </tr>
                    </thead>
                    <tbody data-bind="foreach:form.conditions">
                        <tr data-bind="attr:{id:$index}">
                            <td data-bind="text:field" style="text-align:left"></td>
                            <td><input type="text" class="z-txt" data-bind="value:title"/></td>
                            <td><select class="z-txt"  data-bind="options:$root.data.input,value:type"></select></td>
                            @*<td><input type="text" class="z-txt" data-bind="value:options"/></td>*@
                            <td><select class="z-txt"  data-bind="options:$root.data.compare,value:cp"></select></td>
                        </tr>

                    </tbody>
                </table>
            </div>  
        </div>
    </div>
    <div id="step-2" class="step">
        <h2 class="StepTitle">第二步 请勾选要显示的数据字段</h2>
        <div style="width:200px;float:left;overflow:auto;" data-bind="autoheight:172">
            <ul data-bind="easyuiTree:searchEdit.columntree2"></ul>
        </div>  
        <div style="float:left;overflow:auto" data-bind="autoheight:172,autowidth:405">
            <table class="grid">
                <thead>
                    <tr>
                        <th style="width:50px">字段</th>
                        <th style="width:100px">题头</th>
                        <th style="width:30px">隐藏</th>
                        <th style="width:30px">排序</th>
                        <th style="width:50px">对齐</th>
                        <th style="width:40px">宽度</th>
                        <th style="width:50px">格式化</th>
                    </tr>
                </thead>
                <tbody data-bind="foreach:form.columns">
                    <tr data-bind="attr:{id:$index}">
                        <td data-bind="text:field" style="text-align:left"></td>
                        <td><input type="text" class="z-txt" data-bind="value:title" /></td>
                        <td><input type="checkbox" data-bind="checked:hidden"/></td>
                        <td><input type="checkbox" data-bind="checked:sortable"/></td>
                        <td><select class="z-txt"  data-bind="options:$root.data.align,value:align" ></select></td>
                        <td><input type="text" class="z-txt" data-bind="value:width" /></td>
                        <td><select class="z-txt"  data-bind="options:$root.data.formatter,optionsText:'text',optionsValue:'value',value:formatter" ></select></td>
                    </tr>
                </tbody>
            </table>
        </div>  
    </div>

     <div id="step-3" class="step">
        <h2 class="StepTitle">第三步 其它设置</h2>

        <div class="container_12">
            <div class="grid_1 lbl">业务区域</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.area"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">控制器名称</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.controller"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">生成路径</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.path"/></div>
        </div>
    </div>
</script>

<script id="template-edit" type="text/html">
    <ul>
        <li><a href="#step-1">
            <label class="stepNumber">1</label>
            <span class="stepDesc">设置主表编辑区<br />
                <small>定义主表编辑字段</small>
            </span>
        </a></li>
        <li><a href="#step-2">
            <label class="stepNumber">2</label>
            <span class="stepDesc">设置明细数据页签<br />
                <small>定义明细表及页签</small>
            </span>
        </a></li>
        <li><a href="#step-3">
            <label class="stepNumber">3</label>
            <span class="stepDesc">其它设置<br />
                <small>修改其它代码生成设置</small>
            </span>
        </a></li>
    </ul>

    <div id="step-1" class="step">
        <h2 class="StepTitle">第一步 请勾选要编辑的字段</h2>
        <div>  
            <div style="width:200px;float:left;overflow:auto;" data-bind="autoheight:172">
                    <ul data-bind="easyuiTree:searchEdit.columntree"></ul>
            </div>  
            <div style="float:left;overflow:auto" data-bind="autoheight:172,autowidth:405">
                <table class="grid">
                    <thead>
                        <tr>
                            <th style="width:20%">字段</th>
                            <th style="width:40%">标签名称</th>
                            <th style="width:30%">控件类型</th>
                            <th style="width:10%">只读</th>
                        </tr>
                    </thead>
                    <tbody data-bind="foreach:form.conditions">
                        <tr data-bind="attr:{id:$index}">
                            <td data-bind="text:field" style="text-align:left"></td>
                            <td><input type="text" class="z-txt" data-bind="value:title"/></td>
                            <td><select class="z-txt"  data-bind="options:$root.data.input,value:type" style="width:60%"></select><a href="#">高级</a></td>
                            <td><input type="checkbox" data-bind="checked:readonly"/></td>
                        </tr>
                    </tbody>
                </table>
            </div>  
        </div>
    </div>
    <div id="step-2" class="step">
        <h2 class="StepTitle">第二步 请设置页面中的tab页签</h2>
         
        <div style="float:left;overflow:auto;width:150px;" data-bind="autoheight:172">
            <a href="#" class="buttonNext" style="float:left;margin:5px 3px 5px 0" data-bind="click:edit.addTab">添加Tab页签</a>
            <table class="grid">
                <thead>
                    <tr>
                        <th style="width:30%">#</th>
                        <th style="width:70%">名称</th>
                    </tr>
                </thead>
                <tbody data-bind="foreach:form.tabs">
                    <tr data-bind="attr:{id:$index}">
                        <td><a href="#" data-bind="click:$parent.edit.removeTab">删除</a></td>
                        <td><input type="text" class="z-txt" data-bind="value:title,click:$parent.edit.clickTab" style="width:90%"/></td>
                    </tr>
                </tbody>
            </table>
        </div>  

         <div id="edit-tab-setting" style="float:left;overflow:auto;" data-bind="autoheight:172,autowidth:355,visible:edit.selectedTitle()!=null">
 
        </div> 
    </div>

     <div id="step-3" class="step">
        <h2 class="StepTitle">第三步 其它设置</h2>

        <div class="container_12">
            <div class="grid_1 lbl">业务区域</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.area"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">控制器名称</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.controller"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">生成路径</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.path"/></div>
        </div>
    </div>
</script>

<script type="text/html" id="template-edit-tab-setting">
     <div style="padding:8px;clear:both">
        <span>页签类型 </span>
        <select class="z-txt" style="padding:0;height:22px;" data-bind="value:edit.selectedTab.type">
            <option value="empty">empty</option>
            <option value="grid">grid</option>
            <option value="form">form</option>
        </select> 

        <span data-bind="visible:edit.selectedTab.type()!='empty'"> 数据表 </span>
        <select class="z-txt" style="padding:0;height:22px;" data-bind="options:data.table,optionsText:'text',optionsValue:'id',value:edit.selectedTab.subtable,visible:edit.selectedTab.type()!='empty'"></select>

        <span data-bind="visible:edit.selectedTab.type()!='empty'">与主表的关联</span>
        <select class="z-txt" style="padding:0;height:22px;" data-bind="options:data.tablekey,value:edit.selectedTab.relationship,visible:edit.selectedTab.type()!='empty'"></select>
    </div>

    <div style="width:180px;float:left;overflow:auto;margin-right:-18px;" data-bind="autoheight:212,visible:edit.selectedTab.type()!='empty'">
        <ul data-bind="easyuiTree:edit.columntree2"></ul>
    </div> 
    
    <div style="float:right;overflow:auto;" data-bind="autoheight:210,autowidth:535,visible:edit.selectedTab.type()!='empty'">
        <table class="grid">
            <thead>
                <tr>
                    <th style="width:50px">字段</th>
                    <th style="width:100px">题头</th>
                    <th style="width:30px" data-bind="visible:edit.selectedTab.type()=='grid'">隐藏</th>
                    <th style="width:30px" data-bind="visible:edit.selectedTab.type()=='grid'">排序</th>
                    <th style="width:50px" data-bind="visible:edit.selectedTab.type()=='grid'">对齐</th>
                    <th style="width:40px" data-bind="visible:edit.selectedTab.type()=='grid'">宽度</th>
                    <th style="width:50px" data-bind="visible:edit.selectedTab.type()=='grid'">格式化</th>
                    <th style="width:50px" data-bind="visible:edit.selectedTab.type()=='grid'">编辑器</th>
                    <th style="width:50px" data-bind="visible:edit.selectedTab.type()=='form'">控件类型</th>
                    <th style="width:10px" data-bind="visible:edit.selectedTab.type()=='form'">只读</th>
                </tr>
            </thead>
            <tbody data-bind="foreach:edit.selectedTab.columns">
                <tr data-bind="attr:{id:$index}">
                    <td data-bind="text:field" style="text-align:left"></td>
                    <td><input type="text" class="z-txt" data-bind="value:title" /></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><input type="checkbox" data-bind="checked:hidden"/></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><input type="checkbox" data-bind="checked:sortable"/></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><select class="z-txt"  data-bind="options:$root.data.align,value:align" ></select></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><input type="text" class="z-txt" data-bind="value:width" /></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><select class="z-txt"  data-bind="options:$root.data.formatter,optionsText:'text',optionsValue:'value',value:formatter" ></select></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><select class="z-txt"  data-bind="options:$root.data.editor,optionsText:'text',optionsValue:'value',value:editor" ></select></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='form'"><select class="z-txt"  data-bind="options:$root.data.input,value:type"></select></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='form'"><input type="checkbox" data-bind="checked:readonly"/></td>
                </tr>
            </tbody>
        </table>
    </div>
</script>

Generator.js

/**
* 模块名:mms viewModel
* 程序名: Generator.js
* Copyright(c) 2013 liuhuisheng [ liuhuisheng.xm@gmail.com ] 
**/

var viewModel = function () {
    var self = this;

    this.form = {
        type: '',
        database:ko.observable(),
        table: ko.observable(),
        controller: ko.observable(),
        area:ko.observable(),
        conditions: ko.observableArray(),
        columns: ko.observableArray(),
        tabs: ko.observableArray(),
        path: ko.observable("~/Generator/")
    };
 
    this.resetForm = function () {
        self.form.conditions([]);
        self.form.columns([]);
        self.form.tabs([]);
    };

    this.data = {
        codetype: [{ text: 'search', value: 'search' }, { text: 'edit', value: 'edit' }, { text: 'searchEdit', value: 'searchEdit' }],
        database: ko.observableArray(), 
        table: ko.observableArray(),
        column:ko.observableArray(),
        tablekey: ko.observableArray(),
        input: ['text', 'autocomplete', 'combobox', 'lookup','datebox','daterange'],
        compare: ['equal', 'like', 'startwith', 'endwith', 'greater', 'less', 'daterange'],
        align:['left','center','right'],
        formatter: [{text:'',value:''},{ text: '日期', value: 'com.formatDate' }, { text: '时间', value: 'com.formatTime' }, { text: '金额', value: 'com.formatMoney' }, { text: '是否', value: 'com.formatCheckbox' }],
        editor: [{text:'',value:''},{ text: '文本', value: 'text'}, { text: '整数', value: "{type: 'numberbox',options:{min: 0}}" }, { text: '两位小数', value: "{type: 'numberbox',options:{min: 0, precision: 2}}" }, { text: '下拉框', value: "{type:'combobox',options:{}}" }, { text: '弹出框', value: "{type:'lookup',options:{}}" }, { text: '日期', value: 'datebox' }]
    };

    this.initDatabase = function () {
        com.ajax({
            type: 'GET',
            async:false,
            url: '/api/sys/generator/GetConnectionStrings',
            success: function (d) {
                self.data.database(d);
            }
        });
    };

    this.initDatabase();

    this.getTableUrl = function () {
        return '/api/sys/generator/GetTables?database=' + self.form.database();
    };
    this.getColumnUrl = function (table) {
        return '/api/sys/generator/GetColumns?database=' + self.form.database() + "&table=" + table;
    }

    this.codetype = {
        showblank: true,
        width: 110,
        data: self.data.codetype,
        onSelect: function (node) {
            self.form.type = node.value;
            self.initWizard();
        }
    };

    this.database = {
        showblank: true,
        width: 110,
        data: self.data.database,
        onSelect: function (node) {
            self.form.database(node.value)
            self.form.area((node.value.split('.')[1] || node.value).replace(/(^|\s+)\w/g, function (s) { return s.toUpperCase(); }));
        }
    };

    this.tabletree = {
        method: 'GET',
        url: ko.computed(self.getTableUrl),
        loadFilter: function (d) {
            var data = utils.filterProperties(d.rows || d, ['TableName as id', 'TableName as text']);
            self.data.table(data);
            return data;
        },
        onSelect: function (node) {
            self.form.table(node.id);
            self.edit.init();
            self.resetWizard();
            self.form.controller((node.id.split('_')[1] || node.id).replace(/(^|\s+)\w/g, function (s) { return s.toUpperCase(); }));
        }
    };

    this.generator = function () {
        com.ajax({
            type:'POST',
            url: '/api/sys/generator',
            data: ko.toJSON(self.form),
            success: function (d) {
                com.message('success', "代码已生成!");
            }
        });
    };

    this.searchEdit = {};
    this.searchEdit.columntree = {
        method: 'GET',
        url: ko.computed(function () {
            return self.getColumnUrl(self.form.table());
        }),
        checkbox: true,
        loadFilter: function (d) {
            return utils.filterProperties(d.rows || d, ['ColumnName as id', 'ColumnName as text']);
        },
        onSelect: function (node) {
            var handle = node.checked ? 'uncheck' : 'check';
            $(this).tree(handle, node.target);
        },
        onCheck: function (node, checked) {
            if (checked)
                self.form.conditions.push({ field: node.id, title: node.id, type: 'text', options: '', cp: 'equal',readonly:false });
            else
                self.form.conditions.remove(function (item) { return item.field == node.id });
        },
        onLoadSuccess: self.resetForm
    };

    this.searchEdit.columntree2 = {
        method: 'GET',
        url: ko.computed(function () {
            return self.getColumnUrl(self.form.table());
        }),
        checkbox: true,
        loadFilter: function (d) {
            return utils.filterProperties(d.rows || d, ['ColumnName as id', 'ColumnName as text']);
        },
        onSelect: function (node) {
            var handle = node.checked ? 'uncheck' : 'check';
            $(this).tree(handle, node.target);
        },
        onCheck: function (node, checked) {
            var arr = self.form.columns;
            
            if (checked) {
                var item = $.grep(arr(), function (row) {return row.field == node.id;})[0];
                item || arr.push({ field: node.id, title: node.id, hidden: false, sortable: true, align: 'left', width: 80, formatter: '', editor: 'text' });
            } else
                arr.remove(function (item) { return item.field == node.id });
        }
    };

    this.edit = {};
    this.edit.selectedTab = {
        title: ko.observable(),
        type: ko.observable(),
        subtable: ko.observable(),
        relationship: ko.observable(),
        columns: ko.observableArray(),
        primaryKeys:ko.observableArray()
    };
     
    this.edit.columntree2 = {
        method: 'GET',
        url:ko.observable(),
        checkbox: true,
        loadFilter: function (d) {
            self.data.column(d);
            var list = utils.filterProperties(d.rows || d, ['ColumnName as id', 'ColumnName as text']);
            self.edit.setDefaultForm();
            self.edit.resetTableKey();
            var checkedList = [];
            for (var i in self.edit.selectedTab.columns())
                checkedList.push(self.edit.selectedTab.columns()[i].field);
            for (var i in list)
                if ($.inArray(list[i].id, checkedList) > -1) list[i].checked = true;
            
            return list
        },
        onSelect: function (node) {
            var handle = node.checked ? 'uncheck' : 'check';
            $(this).tree(handle, node.target);
        },
        onCheck: function (node, checked) {
            var arr = self.edit.selectedTab.columns;

            if (checked) {
                var item = $.grep(arr(), function (row) { return row.field == node.id; })[0];
                item || arr.push({ field: node.id, title: node.id, hidden: false, sortable: true, align: 'left', width: 80, formatter: '', editor: 'text', type: '', readonly: true });
            } else
                arr.remove(function (item) { return item.field == node.id });
        }
    }
    this.edit.init = function () {
        self.edit.selectedTitle(null);
        self.edit.selectedTab = null;
        $('#edit-tab-setting').empty();
    };
    this.edit.addTab = function () {
        var title = 'tab' + (self.form.tabs().length + 1);
        var newTab = {
            title: ko.observable(title),
            type: ko.observable('empty'),
            subtable: ko.observable(self.form.table()),
            relationship: ko.observable(),
            columns: ko.observableArray(),
            primaryKeys:ko.observableArray()
        };
        newTab.type.subscribe(function (value) {
            if (value == 'grid') {
                var item = $.grep(self.data.table(), function (row) { return row.id == self.form.table() + "Detail" })[0];
                if (item)
                    newTab.subtable(item.id);
            }
            else if (value == 'form') {
                newTab.subtable(self.form.table());
            }
        });
        newTab.columns.subscribe(self.tableDnDUpdate);
        newTab.subtable.subscribe(function (value) {
            self.edit.selectedTab.columns([]);
            self.edit.columntree2.url(self.getColumnUrl(value));
        });

        self.form.tabs.push(newTab);
    };
    
    this.edit.removeTab = function (row,event) {
        self.form.tabs.remove(row);

        if (row.title() == self.edit.selectedTitle())
            self.edit.selectedTitle(null);
    };
    this.edit.selectedTitle = ko.observable();
    this.edit.clickTab = function (row, event) {
        if (row.title() == self.edit.selectedTitle()) return;
 
        self.edit.selectedTitle(row.title());
        self.edit.selectedTab = row;
        self.edit.columntree2.url = ko.observable(self.getColumnUrl(self.edit.selectedTab.subtable()));

        var currentTr = $(event.srcElement).parent("td").parent("tr");
        currentTr.parent().find("tr.tree-node-selected").removeClass("tree-node-selected");
        currentTr.addClass("tree-node-selected");

        var tabTemplate = $('#template-edit-tab-setting').html();
        var wrapper = $('#edit-tab-setting').empty().html(tabTemplate);

        ko.cleanNode(wrapper[0]);
        ko.applyBindings(self, wrapper[0]);
        wrapper.find("table").tableDnD({ onDrop: self.tableDnDSort });
    };
    this.edit.resetTableKey = function () {
        var relationship = self.edit.selectedTab.relationship();
        self.data.tablekey([]);
        var cols = self.data.column();
        for (var i in cols)
            if (cols[i].IsIdentity || cols[i].IsPrimaryKey)
                self.data.tablekey.push(cols[i].ColumnName);

        self.edit.selectedTab.relationship(relationship);
        self.edit.selectedTab.primaryKeys(self.data.tablekey());
    };
    this.edit.setDefaultForm = function () {
        var arr = [
            { field: 'ApproveState', title: '审批状态', type: 'text', readonly: true },
            { field: 'ApproveRemark', title: '审批意见', type: 'text', readonly: true },
            { field: 'ApprovePerson', title: '审批人', type: 'text', readonly: true },
            { field: 'ApproveDate', title: '审批日期', type: 'datebox', readonly: true },
            { field: 'CreatePerson', title: '编制人', type: 'text', readonly: true },
            { field: 'CreateDate', title: '编制日期', type: 'datebox', readonly: true },
            { field: 'UpdatePerson', title: '修改人', type: 'text', readonly: true },
            { field: 'UpdateDate', title: '修改日期', type: 'datebox', readonly: true }
        ];

        var cols = self.data.column();
        var defaults = { field: '', title: '', hidden: false, sortable: true, align: 'left', width: 80, formatter: '', editor: 'text', type: '', readonly: true };
        for (var i in arr) {
            if (!$.grep(cols, function (item) { return item.ColumnName == arr[i].field; }).length)
                return;

            arr[i] = $.extend({}, defaults, arr[i]);
        }

        self.edit.selectedTab.columns(arr);

        var tree = self.edit.columntree2.$element();
        for (var i in arr) {
            var node = tree.tree('find', arr[i].field);
            if (node) tree.tree('check', node.target);
        }
    };

    this.initWizard = function () {
        var stepTemplate = $('#template-' + self.form.type);
        if (!stepTemplate.length) return;

        var wizard = $('#wizard').removeData('smartWizard').empty();
        ko.cleanNode(wizard[0]);
        
        wizard.html(stepTemplate.html());
        wizard.smartWizard({
            labelNext: '下一步',
            labelPrevious: '上一步',
            labelFinish: '生成',
            onFinish: self.generator
        });
        var resizeStep = function () {
            $(".step").height($(window).height() - 145)
                      .width($(window).width() - 205);
            $(".actionBar").width($(window).width() - 195);
            var index = wizard.smartWizard('currentStep');
            wizard.smartWizard('goToStep', index);
        };
        $(window).resize(resizeStep);
        resizeStep();
        ko.applyBindings(self, wizard[0]);
        wizard.find("table").tableDnD({ onDrop: self.tableDnDSort });

        for (var i in self.form) {
            if ($.isFunction(self.form[i]))
                if (self.form[i]() instanceof Array)
                    if (self.form[i].subscribe) 
                        self.form[i].subscribe(self.tableDnDUpdate);
        }
    };
    this.resetWizard = function () {
        var wizard = $("#wizard").smartWizard('goToStep', 1);
        for (var i = 1; i <= wizard.find(">ul>li").length; i++)
            wizard.smartWizard("disableStep", i);
    };

    this.tableDnDUpdate = function () {
        setTimeout('$("table").tableDnDUpdate()', 300);
    };
   
    this.tableDnDSort = function (table, row) {
        var name = $(table).find("tbody").attr("data-bind").replace('foreach:form.','');
        var array = self.form[name], i = 0;

        if (name == 'foreach:edit.selectedTab.columns')
            array = self.edit.selectedTab.columns;

        $("tr[id]", table).each(function () { array()[this.id].id = i++; });
        array.sort(function (left, right) { return left.id == right.id ? 0 : (left.id < right.id ? -1 : 1) });

        //for fix ko bug refresh ui
        var tempArr = array();
        array([]);
        array(tempArr);
    };
};

razor模板
model.cshtml

@using Zephyr.Core.Generator
using System;
using System.Collections.Generic;
using System.Text;
using Zephyr.Core;

namespace Zephyr.Web.@(@Model.Area).Models
{
    [Module("@Model.Database")]
    public class @(Model.TableName)Service : ServiceBase<@Model.TableName>
    {
       
    }

    public class @Model.TableName : ModelBase
    {
@foreach(TableSchema item in Model.Columns)
{
    if (item.IsIdentity)
    {
        @:[Identity]
    }

    if (item.IsPrimaryKey)
    {
        @:[PrimaryKey]   
    }
        @:public @item.TypeName @item.ColumnName { get; set; }
}
    }
}

其它各页面的模板我就不一一贴出来了,大家可以查看我以前的那些关于共通viewModel的博客,查询及编辑页面我都有详细介绍过,大家也可以自己提炼出自己的一套模板,然后也可以用这同一个思路去做一个代码生成器。

六、代码生成用户界面

这个用户界面我们还是要把三种页面的定义分开看: 

1、查询页面生成

第一步,选择代码类别search(查询页面),选择数据库,选择业务主表,再勾选字段即可实现查询条件部的设置,并且实现了拖拉排序功能。大家可以对照查询模板看。 
image

第二步,选择grid中要显示的列,并且设置属性,格式化等 
image

第三步,设置一些全局设定,主要根据这些参数确定命名空间,生成文件名等信息 
image

点击生成按钮,按设定生成代码,生成后弹出文件夹,已分别生成MVC三层代码 
image

mms_receive.cs 
image

Index.cshtml 
image

ReceiveController.cs 
image

把这个代码直接拷贝到项目中直接运行,测试条件过滤都没有问题,grid会自适应高度,grid远程排序,选择分页翻页都没有问题,所有的功能都可用, 
只有lookup控件弹出是空值,因为只把控制设置为了lookup但没有为它设置更详细的选项。autocomplete也是同样 
即代码生成器已经生成了一个大的结构及UI,一些小细节还是要手动修改下,代码生成的UI界面如果把每个控件的选项也做进去会相当的复杂,也没有必要再细化了。 
image

2、编辑页面生成 
第一步,选择主表编辑区的字段及控件类型,控件类型中的高级还未实现,这个编辑的UI也可以参照编辑的模板看 
image

第二步,添加tab页签,选择页签类型(grid,form,empty) grid是指跟主表N:1关系,form是指跟主表1:1关系,empty是空页签,生成后自己可以添加内容 
这里我随便添加三个tab页签tab1 tab2 tab3 
tab1用来放人员变动grid(跟主表关系N:1)image 

tab2就选择form(跟主表关系1:1,也可以是主表本身)image

tab3也随便添些东西 
image

第三步,其它设置 
image

点击生成按钮,生成后自动打开文件夹 
image

把这些代码拷贝到项目中直接运行 
image

tab2 
image

tab3,修改主表数据,tab1,tab2,tab3点保存,能保存成功, 
image

审核按钮也可用,审核后单据不可修改 
image

这个编辑功能基本上可以囊括很多的录入页面了,可以算是比较通用了

3、查询编辑页面(查询编辑在同一个页面内)页面生成

第一步,选择查询条件并设置控件类型image

第二步,设置grid中的数据列,及编辑器 
image

第三步,其它设置 
image

点击生成按钮,生成后自动打开文件夹  
image

把代码直接拷贝到项目中运行,结果如下,经测试除了控件还需要进一步设置,所有按钮功能正常使用 
image

 

七、后述

有了这个代码生成的功能,5分钟做一个基本的页面应该是完全没有问题的。
我这里分享下自己的代码生成的思路,权当抛砖引玉,大家有什么更好的方法欢迎留言。
如果大家感兴趣,就在右下角帮我【推荐】一下吧,谢谢大家了。

转载于:https://www.cnblogs.com/xqin/p/3553597.html

asp.net 代码生成器 【基本说明】 1、能够生成三层模式操作的所有后台代码,简单的SQL Server 2005数据库操作。 2、生成的代码包括了 MODEL、BLL、DAL、DBHelper、Config 生成的代码内有详细注释可提供参考。 3、提供数据库增、删、改、查、分页及其事务,并提供多种重载方式。 4、所有数据表必须有主键且主键是第一列,这个主要是为了保证获取记录和分页获取的统一性,其实可以取消这个规则。 5、建议新建App_Code文件夹将生成的C#代码放里面。见此文件夹直接拷贝到项目下既可以使用。 6、不保证所提供软件或程序的完整性和安全性。 7、请在使用前查毒 (这也是您使用其它网络资源所必须注意的) 。 8、《Coder.NET代码生成器》需要.Net FrameWork2.0运行环境,基于SQL Server 2005使用。 9、如无法运行本软件,请下载并安装由微软公司提供的.Net FrameWork2.0系统. 10、如果您在使用过程中遇到程序问题或建议请于我联系我的Email是 mailto:liangaspx@163.com。 11、如需要源码与我联系 李亮 QQ:542529107 或登陆 http://liliang119007.download.csdn.net/下载更新版本。 【生成单表代码】 输入数据库名(Server)登录名(Name)密码(Pwd),连接后选择库名(Database)表名(Tables), 之后单击'生成单表代码'新建App_Code文件夹将生成的C#代码(ASP.NET后台代码)放里面。 【生成三层工厂模式项目】 (1)B/S架构(ASP·NET): 输入数据库名(Server)登录名(Name)密码(Pwd)连接数据库成功后直接点生成整个项目选择路径确定就好了。 (2)C/S架构(Windows应用程序): 输入数据库名(Server)登录名(Name)密码(Pwd)连接数据库成功后直接点生成整个项目选择路径确定, 生成项目后打开该项目解决方案将表示层删掉, 再单击vs的(文件→添加→新建项目→选择Windows应用程序),这样就生成C/S架构的程序了! 程序员:李亮 更新日期:2010-5-17
您是不是因为ASP.NET开发和维护繁琐而烦恼,微软的开发工具更新速度、开发技巧是成多元化的趋势,ASP.NET以其灵活多变的开发模式,深受广大开发人员的喜爱,灵活多变的开发模式有其利的一面,也有其不好的地方,特别是程序代码的随意性较强,即开发人员可以把代码放到任何一个地方,且变量的使用也是无度的、无规律的,这对目前讲究团队开发的流程管理来讲,简直就是一种灾难,本软件将彻底解决您的后顾之忧,通过本软件的自动生成功能,生成的ASP.NET代码规范、全部开源,不存在用隐含的内容,全部代码逻辑开源的展示给客户,符合团队开发的管理要求。本工作室的开发团队汲取多位资深开发人员多年的项目开发经验开发出本软件, 使用本软件从最基本的VO对象到ASPX页面的新增、删除、修改、查询等功能一起生产,代码功能一一俱全。您只需要稍微做下界面的排版即可使用到实际的项目中了。 1、使用本软件做开发的优势: 如果您是ASP.NET开发人员,一定会为每天开发中大量的重复拷贝、粘贴代码(如分页等功能)而感到烦恼,又或为ASP.NET对模式开发的繁琐关联配置而显得无可奈何时。使用本软件可以自动生成代码、建立页面关联。开发人员只要前期对业务了解清楚,数据库表设计明确,用本软件即可完成程序的编写。 如果您是经常使用NHibernate或其他DLL插件的ASP.NET开发人员,一定会为NHibernate或其他DLL插件的配置部署问题而搞懵,这些类型的插件有个最大的问题是其核心操作均是由该插件的内部完成,对开发人员来说是个黑匣子(一般用户不会去读其开源代码),而且多个项目用同类型的插件部署到一部服务器上的时候,很容易造成版本冲突,且出现莫名其妙的问题。这些插件产生的冗余代码,也让开发人员不舒服。使用本软件生成的代码,全部开源,结构清晰,在您的开发工程中绝对不需要引用任何插件或链接库。 如果您是公司或项目负责人,一定会遇到这样的情况,公司拥有众多的ASP.NET开发高手,而开发习惯也各式各样,因而对项目接手的维护人员的技术要求也相应需要提高,这样项目的投入成本自然增加,而企业的利润也相应减少。没有统一开发的模式,对项目的后期维护是一个相当痛苦的过程,何况IT界人才流动频繁,项目的交接也是常有的问题。使用本软件的自动生成的代码,符合.NET的开发模式结合工厂模式,展示、业务、逻辑、存储的分层实现,代码的编写已分门归类,重要体现了“桥归桥,路归路”的理念,这样对任何需要尽快熟悉项目的人员,一定可以在短时间内理解项目的架构思想,很快上手。 2、本软件自动生成的内容: ★ VO、POJO对象 ★ DAO接口 ★ IMPL接口实现类 ★ DAO工厂 ★ VO、POJO工厂 ★ DBC数据库连接管理类,数据库事务管理机制 ★ ASPX调用页面(增、删、改、查)(含.CS文件),分页功能自动实现 ★ Web.Config配置文件(VS2005工程需要的文件) 3、特色: ☆ 一键生成,简洁使用。 ☆ 生成的代码全部开源,没有任何通过插件或链接库来做的操作。 ☆ 支持多表的多主键处理。 ☆ 支持数据事务的操作。 ☆ 生成内容可以依据客户的需要来选择性的生成。 ☆ 支持自定义查询接口的生成,用户可以定义查询条件。 ☆ 目前支持Oracle、Sqlserver 数据库对象的自动生成代码。 4、联系方式: Email通讯邮箱 : autocode@126.com QQ留言:915842778
ASP.NET MVC5是一种使用Model-View-Controller(MVC)架构设计的Web应用程序开发框架。它结合了ASP.NET技术和MVC模式的优势,可以帮助开发人员更高效地构建企业级应用程序。 EasyUI是一个开源的跨浏览器JavaScript UI框架,它为开发者提供了丰富而易于使用的界面组件和工具。EasyUIASP.NET MVC5结合使用,可以快速构建具备良好用户体验的企业级应用程序。 当使用ASP.NET MVC5与EasyUI进行企业应用开发时,可以充分利用ASP.NET MVC5的优势,如分离开发模式、URL路由、模型绑定等,来实现良好的应用程序架构和代码可维护性。 同时,EasyUI提供了大量丰富的UI组件,如表格、表单、对话框、树形控件等,这些组件在企业应用开发中非常常用。使用EasyUI的这些组件可以帮助开发人员快速搭建用户界面,提供友好的交互效果和丰富的功能。 在使用EasyUI的过程中,开发人员可以根据应用的需求定制化各种UI组件的样式和行为,使得应用程序更符合企业的视觉设计和用户体验要求。 此外,ASP.NET MVC5与EasyUI都是开源的框架,拥有强大的社区支持,可以通过不断积累丰富的资源来解决开发过程中的问题。 综上所述,ASP.NET MVC5与EasyUI的结合是一种优秀的企业应用开发框架,可以帮助开发人员快速构建功能丰富、用户友好的应用程序,并且具有良好的可扩展性和可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值