反射實體自動生成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:為了追逐愛情,准備離開成都,辭職北漂,忘大神收留。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM