zoukankan      html  css  js  c++  java
  • 反射实体自动生成EasyUi DataGrid模板 第二版附项目源码

    之前写过一篇文章,地址  http://www.cnblogs.com/Bond/p/3469798.html   大概说了下怎么通过反射来自动生成对应EasyUi datagrid的模板,然后贴了很多代码,看起来很乱,当时没用过easyui,没啥经验。 这次经过了项目的实际考验,我把它做了一些改动,在此分享下,并且附上源码,源码需要用vs2012打开,打开即可运行不要做任何设置。源码地址在 https://github.com/LittleBearBond/GenerateEasyUiDataGridTemplate 。都说现在程序员要玩GitHub,我一直都想用用,不过没时间,经过N久的加班,最近终于有时间学习了下,把源码也放到上面。

    我为什么要去折腾写这个东西,之前博客也大概提了下,现在请看下图,如果我们用easyui的datagrid来展示这样的数据

    页面必然对应这样一个datagrid模板

    <table id="dt" class="easyui-datagrid" data-options="title:'商品列表'">
      <thead>
        <tr>       <th data-options="field:'Name',align:'center',formatter:format.formatVal"> 名称</th>
          <
    th data-options="field:'Category',align:'center',formatter:format.formatVal"> 类别</th>
          <
    th data-options="field:'Price',align:'center',sortable:true,formatter:format.formatVal"> 价格</th>
          <
    th data-options="field:'CreateTime',align:'center',formatter:format.formatTime" sortable="true"> 创建时间</th>     </tr>
      </
    thead>
    </
    table>

    其实这个模板他要展示的数据对象必然对应一个后台的类,比如这个模板对应的后台类是Product

    public class Product
        {
            /// <summary>
            /// 
            /// </summary>
            public int Id { get; set; }
    
            /// <summary>
            /// 名称
            /// </summary>public string Name { get; set; }
    
            /// <summary>
            /// 
            /// </summary>public string Category { get; set; }
    
            /// <summary>
            /// 价格
            /// </summary>public decimal Price { get; set; }
    
            /// <summary>
            /// 创建时间
            /// </summary>public DateTime CreateTime { get; set; }
      }

    而从后台返回的数据是这样的

    通过以上观察我们会发现一些问题,datagrid要展示的数据后台必然是返回一个json对象,而单个对象里面的 Id 、CreateTime 、Name、 Price 其实是对应着Product实体对象的字段名,而datagrid模板里面要设置哪行显示哪个字段,比如Name 在datagrid模板里面对应着一行th,并设置Name这行的相关属性。

    datagrid模板里面每个字段都是和后台的Product实体对象的字段名称一一对应的,而我们每次在设置模板的时候都要去拷贝字段名称,如果不小心弄错了数据就不会显示出来。如果我们能通过后台的实体对象Product来直接生成这个模板那就不会出错了。这个功能我已经做好了,用到的知识点很少,只是自定义属性加反射再加点字符串拼接就搞定啦。不过生成模板也是有很多问题需要考虑,大致有以下一些问题需要解决。

    1:Product对象里面不是每个字段都要显示出来,比如Id不需要显示,我们希望能够动态设置需要显示的字段。

    2:在设置单行th的时候fileid是Name,但是在thead表头上需要显示 “名称” 两个汉字而不是英文的Name

    3:我们可以动态设置这个字段的data-options属性  以及这行的其他属性 如style  class之类的

    4:显示有先后顺序,Name显示在前面还是Price显示在前面我们可以做动态设置

    5:可以额外添加和Product无关的字段,然后显示到页面上

    上面说在很抽象来看具体事例:

    现在我的类是这样的,标记了display属性,和DataOptions自定属性

     [DataOptions(Options = "title:'商品列表'")]
        public class Product
        {
            /// <summary>
            /// 
            /// </summary>
            public int Id { get; set; }
    
            /// <summary>
            /// 名称
            /// </summary>
            [Display(Name = "名称")]
            [DataOptions(Order = 1)]
            public string Name { get; set; }
    
            /// <summary>
            /// 
            /// </summary>
            [Display(Name = "类别")]
            [DataOptions(Order = 2)]
            public string Category { get; set; }
    
            /// <summary>
            /// 价格
            /// </summary>
            [Display(Name = "价格")]
            [DataOptions(Order = 3, Options = "sortable:true")]
            public decimal Price { get; set; }
    
            /// <summary>
            /// 创建时间
            /// </summary>
            [Display(Name = "创建时间")]
            [DataOptions(Order = 4, Property = "sortable=true")]
            public DateTime CreateTime { get; set; }

    现在我的cshtml页面代码是这样的

    @using GenerateDataGridDemo.EasyUi
    @using GenerateDataGridDemo.Extends
    @using GenerateDataGridDemo.Models
    @{
        ViewBag.Title = "Test1";
        Layout = "~/Views/Shared/_EasyUiLayout.cshtml";
        var loadUrl = Url.Action("LoadTest1", "Home");
    }
    @section searchs
    {
        <div>
            @EasyUiPageControls.SearchTimeInput()
            @EasyUiPageControls.SearchKeyWordInput("产品名称:", "KeyWord", "init=\"请输入产品名称\"")
            @EasyUiPageControls.SearchButton()
        </div>
    }
    @Html.CreateDataGridTemplate(typeof(Product))
    @section scripts{
        <script type="text/javascript">
            $(function () {
                easyui.dg.LoadData('@loadUrl');
                WebJs.SearchCreateTime();
                $('#searchLoadList').click(function () {
                    easyui.dg.Search('@loadUrl');
                });
            });
        </script>
    }

    在页面上得到的效果就是以下这样的,经过简单封装cshtml的页面代码基本上就只有几行,就搞定了数据显示、加载、搜索。

    有时我们页面不会这么简单,我们的页面可能是这样的,前面有多复选框和后面的操作列。

    此时我还是用的以前的Product,只是页面代拿略有改变,如下

    @using GenerateDataGridDemo.EasyUi
    @using GenerateDataGridDemo.Extends
    @using GenerateDataGridDemo.Models
    @{
        ViewBag.Title = "Test2";
        Layout = "~/Views/Shared/_EasyUiLayout.cshtml";
        var loadUrl = Url.Action("LoadTest1", "Home");
    }
    @section searchs
    {
        <div>
            @EasyUiPageControls.SearchTimeInput()
            @EasyUiPageControls.SearchKeyWordInput("产品名称:", "KeyWord", "init=\"请输入产品名称\"")
            @EasyUiPageControls.SearchButton()
            <a class="easyui-linkbutton" id="GetAll">获取选择的ID</a>
        </div>
    }
    @{
        var end = EasyUiPageHtml.FormateOperate();
        var start = EasyUiPageHtml.FirstCheckBox();
    }
    @Html.CreateDataGridTemplate(typeof(Product), end, start)
    @section scripts{
        <script type="text/javascript">
            function formatOperate(val, row) {
                return '<a href="###" onclick="Modify(' + row.Id + ',\'' + row.Name + '\')">编辑</a>  ';
            }
            function Modify(id, name) {
                WebJs.Dialog.Alert(utils.str.formatString('Id:{0},Name{1}', id, name));
            }
            $(function () {
                easyui.dg.LoadData('@loadUrl');
                WebJs.SearchCreateTime();
                $('#searchLoadList').click(function () {
                    easyui.dg.Search('@loadUrl');
                });
                $('#GetAll').on('click', function () {
                    var ids = easyui.dg.GetSelectionIds();
                    if (utils.str.isNullOrWhiteSpace(ids)) {
                        WebJs.Dialog.Tip('请先选择!');
                        return;
                    }
                    WebJs.Dialog.Content(ids);
                });
            });
        </script>
    }

    大家会发现其实就多了end和start,其他其实是没变动的,只是曾加了一个GetALL按钮。

    真正用于生成面datagrid模板的代码只有@Html.CreateDataGridTemplate(typeof(Product))这一行,然后加上Product上面标记的一些属性,就完成了datagrid模板的自动生成,具体实现上篇博客有说明,这次把代码抽取出来然后奉献给大家做个参考。

    demo代码是这样的,一切从简,只有一个程序集,页面只有两个页面,但是功能还是比较齐全的。

     

    点击左边导航,就会添加一个页面,如果已经存在就刷新存在的页面,页面有右键菜单。

    扩展datagrid的view 在没有数据的时候显示提示信息

    对Jq这个日期控件做了点改动,起始日期级联验证

    页面搜索和加载数据做了相应封装,调用的时候只需一句话,在项目中做了很多公共方法的提取和封装,这里提出来的是部分,多了影响大家看这个代码

     支持排序,只有价格和创建时间支持排序,支持单个字段排序,多个字段也是可以的,后台ExtendClass.cs有相关代码只是我没具体做这个功能。

    最后贴一下整个核心的代码,代码就一百多行,在项目源码中可以去看看,然后根据自己的需求去扩展和改进。

    public class GenerateDataGrid
        {
            public static IList<PropertyInfo> GetAllPropertyInfoList(Type entity)
            {
                return entity.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            }
    public static string GetDataGridTemplate(Type entity, string appendEnd, string appendStart) { var sb = new StringBuilder(); //先获取类的Attribute var entityCustomAttr = entity.GetCustomAttributes(typeof(DataOptionsAttribute), false).FirstOrDefault() as DataOptionsAttribute; #region 对实体的Attibute属性进行处理 //是否显示没有 标记dataoptions的字段 var isShowNotAttr = false;//默认不显示 var options = string.Empty; var tableId = string.Empty; var tableProperty = string.Empty; //并没有处理可能发生的异常情况, 比如在Property 指定了id="xxx" 而又指定了id的值 if (entityCustomAttr != null) { isShowNotAttr = entityCustomAttr.IsShowNotAttr; options = string.IsNullOrWhiteSpace(entityCustomAttr.Options) ? string.Empty : entityCustomAttr.Options; //默认ID为dt , 假设在Property 中没有设置了Id,如果设置了这里没做处理 tableId = string.IsNullOrWhiteSpace(entityCustomAttr.Id) ? "dt" : entityCustomAttr.Id; tableProperty = string.IsNullOrWhiteSpace(entityCustomAttr.Property) ? string.Empty : entityCustomAttr.Property; } #endregion //获取所有的Property var properties = GetAllPropertyInfoList(entity); //如果设置有不显示没有dataoptions标记的,值取出标记有dataoptions的字段 if (!isShowNotAttr) { properties = properties.Where(n => n.CustomAttributes.Any(a => a.AttributeType == typeof(DataOptionsAttribute))).ToList(); } //没有打标记的也要取出来, 这里得到以字段name为key List<Attribute>为值的集合对象 Dictionary<string, List<Attribute>> colDicOpts = properties.ToDictionary( property => property.Name, property => { var list = new List<Attribute> { property.GetCustomAttributes(typeof (DataOptionsAttribute), false).FirstOrDefault() as DataOptionsAttribute, property.GetCustomAttributes(typeof (DisplayAttribute), false).FirstOrDefault() as DisplayAttribute }; return list; }); //在table上拼接 id data-options 和 Property sb.AppendLine(string.Format("<table id=\"{0}\" class=\"easyui-datagrid\" data-options=\"{1}\" {2} > <thead> <tr>", tableId, options, tableProperty)); //没有直接遍历加入数据 这里先取得所有数据,然后进行排序,得到th 列表 var listThs = (from pro in properties let custAttrs = colDicOpts.SingleOrDefault(n => n.Key == pro.Name) select AppenedTemplate(Template.DataGridTh, custAttrs, pro)).ToList(); //1、添加到开始部分的 add start if (!string.IsNullOrWhiteSpace(appendStart)) { sb.AppendLine(appendStart); } //2、添加中间部分,先排序,得到显示顺序 add center listThs = listThs.OrderBy(n => n.Key).Select(n => n.Value).ToList(); sb.AppendLine(string.Join("", listThs)); //3、追加后面的字符串 add end if (!string.IsNullOrWhiteSpace(appendEnd)) { sb.AppendLine(appendEnd); } sb.AppendLine(@"</tr></thead></table>"); return sb.ToString(); } //dynamic 可用 KeyValuePair private static dynamic AppenedTemplate(string template, KeyValuePair<string, List<Attribute>> attributes, PropertyInfo proinfo = null) { var displayName = attributes.Value.SingleOrDefault(n => n is DisplayAttribute) as DisplayAttribute; //设置字段显示的名称,直接设置 DisplayAttribute,这个大家肯定很熟悉的属性 var str = Template.RegV.Replace(template, displayName != null ? displayName.Name : attributes.Key); //设置显示的字段field ,即是当前th显示哪个字段,例如field:'Id' str = Template.RegF.Replace(str, attributes.Key); //从该字段的CustomAttributes中取得DataOptionsAttribute var dataOptions = attributes.Value.SingleOrDefault(n => n is DataOptionsAttribute) as DataOptionsAttribute; //设置Property, 如果property和data-options有设置相同的对象 这里没做异常处理 str = Template.RegP.Replace(str, dataOptions == null ? string.Empty : dataOptions.Property ?? ""); //没有设置排序的这里默认设置一个值 var order = dataOptions == null ? 100 : dataOptions.Order; //由于我自己的需要,我要对DateTime类型进行特殊处理 if (proinfo != null && proinfo.PropertyType == typeof(DateTime)) { //没有自定义属性的值 if (dataOptions == null) { //WebJs.Format.formatTime 自己的js时间格式化函数 这个一定程度上导致前后台耦合了 str = Template.RegD.Replace(str, "formatter:format.formatTime");//默认时间格式 } else { str = dataOptions.Options != null && dataOptions.Options.IndexOf("formatter", StringComparison.CurrentCultureIgnoreCase) >= 0 ? //已经设置formatter Template.RegD.Replace(str, dataOptions.Options) : //默认设置formatter Template.RegD.Replace(str, ((dataOptions.Options ?? "").TrimEnd(',') + ",formatter:format.formatTime").TrimStart(',')); } } else { //替换data-option 的值, 如果为空就直接替换为空 if (dataOptions == null) { str = Template.RegDi.Replace(str, string.Empty); } else { var opt = (dataOptions.Options ?? ""); //默认设置起格式化 var replaceStr = opt.IndexOf("formatter", StringComparison.CurrentCultureIgnoreCase) >= 0 ? opt : opt.TrimEnd(',') + ",formatter:format.formatVal"; str = Template.RegD.Replace(str, replaceStr.TrimStart(',')); } } return new { Value = str, Key = order }; } }

     PS:为了追逐爱情,准备离开成都,辞职北漂,忘大神收留。

  • 相关阅读:
    数组实现队列
    qsort用法 (转)
    枚举法
    HDU 2293
    bfs 街道赛跑
    漫谈二分查找Binary Search (转)
    qsort 结构体二级排序实例
    优化枚举法
    10项比较重要的.NET技术
    ADO.NET SQL Provider 和 ADO.NET OLE DB Provider 和 ADO.NET ODBC Provider 性能对比。
  • 原文地址:https://www.cnblogs.com/Bond/p/3583526.html
Copyright © 2011-2022 走看看