OSharp是什么?
OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现。与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现。依赖注入、ORM、对象映射、日志、缓存等等功能,都只定义了一套最基础最通用的抽象封装,提供了一套统一的API、约定与规则,并定义了部分执行流程,主要是让项目在一定的规范下进行开发。所有的功能实现端,都是通过现有的成熟的第三方组件来实现的,除了EntityFramework之外,所有的第三方实现都可以轻松的替换成另一种第三方实现,OSharp框架正是要起隔离作用,保证这种变更不会对业务代码造成影响,使用统一的API来进行业务实现,解除与第三方实现的耦合,保持业务代码的规范与稳定。
本文已同步到系列目录:OSharp快速开发框架解说系列
前言
要了解一个东西长什么样,至少得让我们能看到,才能提出针对性的见解。所以,为了言之有物,而不是凭空漫谈,我们先从UI说起,后台管理页面的UI我们将使用应用比较普遍的easyui框架。
以前在用easyui的时候,每个页面都得从0做起,或者不厌其烦地由以前的页面通过“复制-粘贴”的方式来修改,久页久之,就会造成页面庞大且难以维护。其实,前端的html,javascript代码与后端的代码是一样的,通过一定的组织,把重复的代码抽离出来,同样也通过达到很好的复用率。而MVC的天生的Layout布局与分布视图(Partial View),就是对重复代码抽离的需求有很好的支持。
EasyUI-Layout布局
_Layout.cshtml
MVC的布局,最先当然是作为根视图存在的_Layout.cshtml了,_Layout.cshtml很简单,只是负责一些样式文件和公共脚本的引用。开发阶段,先使用绝对地址进行引用,发布的时候再进行压缩代码的考虑。
在_Layout.cshtml中,除了必需的 @RenderBody() ,还定义了两个Section,分别为负责引用子级视图样式的 @RenderSection("header", false) 和负责引用子级视图脚本的 @RenderSection("footer", false)
1 @{
2 Layout = null;
3 }
4 <!DOCTYPE html>
5 <html>
6 <head>
7 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
8 <meta name="viewport" content="width=device-width, initial-scale=1.0">
9 <title>@ViewBag.Title - OSharp管理系统</title>
10 <link href="/Content/themes/gray/easyui.css" rel="stylesheet" />
11 <link href="/Content/themes/icon.css" rel="stylesheet" />
12 <link href="/Content/osharp-icons.css" rel="stylesheet" />
13 <link href="/Content/osharp-admin.css" rel="stylesheet"/>
14 @RenderSection("header", false)
15 </head>
16 <body>
17 @RenderBody()
18 <script src="/Scripts/jquery-1.11.1.js" type="text/javascript"></script>
19 <script src="/Scripts/jquery.easyui-1.4.1.js" type="text/javascript"></script>
20 <script src="/Scripts/locale/easyui-lang-zh_CN.js" type="text/javascript"></script>
21 <script src="/Scripts/json2.js" type="text/javascript"></script>
22 <script src="/Scripts/osharp.global.js" type="text/javascript"></script>
23 <script src="/Scripts/osharp.easyui.js" type="text/javascript"></script>
24 <script src="/Scripts/osharp.data.js" type="text/javascript"></script>
25 @RenderSection("footer", false)
26 </body>
27 </html>
后台的EasyUI-Layout布局
一般来说,后台管理页面都是这样一个布局方式:
- 上边一个顶栏
- 左边一个手风琴或树形的导航栏
- 中间是一个由iframe加载具体内容页的多选项卡tab页面
这样,就要用到easyui的easyui-layout来做整体布局,左边的导航栏使用easyui-accordion,右边加载内容页的多选项卡使用easyui-tabs。easyui的布局在网上也很普遍,具体的就不说了,完整代码如下:

1 @{
2 ViewBag.Title = "OSharp后台管理";
3 Layout = "~/Areas/Admin/Views/Shared/_Layout.cshtml";
4 string navDataUrl = Url.Action("GetNavData");
5 }
6 <div class="easyui-layout" data-options="fit:true">
7 <div data-options="region:'north', height:50" style="padding: 10px;">
8 <span style="font-size: 18px;">OSharp后台管理系统</span>
9 <a href="/" target="_blank">返回首页</a>
10 </div>
11 <div data-options="region:'west', split:true, minWidth:100, width:150, title:'导航菜单'">
12 <div id="main-nav" class="easyui-accordion" data-options="fit:true, border:false, selected:true">
13
14 </div>
15 </div>
16 <div data-options="region:'center'">
17 <div id="main-tab" class="easyui-tabs" data-options="fit:true, border:false">
18 <div title="我的主页" iconcls="pic_209" style="padding: 5px;">
19 <iframe width="100%" height="100%" frameborder="0" src="@Url.Action("Welcome")" marginheight="0" marginwidth="0"></iframe>
20 </div>
21 </div>
22 </div>
23 <div data-options="region:'south', height:50">
24 <p style="text-align:center; line-height:20px;">Copyright © OSharp @DateTime.Now.Year</p>
25 </div>
26 </div>
27 <div id="tab-menu" class="easyui-menu" style="width: 150px;">
28 <div id="tab-menu-refresh" data-options="iconCls:'icon-reload'">刷新</div>
29 <div id="tab-menu-openFrame" data-options="iconCls:'pic_138'">新窗口打开</div>
30 <div class="menu-sep"></div>
31 <div id="tab-menu-close" data-options="iconCls:'icon-remove'">关闭</div>
32 <div id="tab-menu-closeleft" data-options="iconCls:'icon-undo'">关闭左边</div>
33 <div id="tab-menu-closeright" data-options="iconCls:'icon-redo'">关闭右边</div>
34 <div class="menu-sep"></div>
35 <div id="tab-menu-closeother" data-options="iconCls:'pic_101'">关闭其他</div>
36 <div id="tab-menu-closeall" data-options="iconCls:'pic_283'">关闭所有</div>
37 </div>
38
39 @section footer{
40 <script type="text/javascript">
41 $(function() {
42 $.getJSON("@navDataUrl", function(data) {
43 if (data.length == 0) {
44 return;
45 }
46 //第一层生成手风琴的项
47 $.each(data, function(i, item) {
48 var id = item.Id;
49 $("#main-nav").accordion("add", {
50 title: item.Text,
51 content: "<ul id='tree-" + id + "'></ul>",
52 selected: true,
53 iconCls: item.IconCls
54 });
55 $.parser.parse();
56 //第二层生成树节点
57 if (!item.Children || item.Children.length == 0) {
58 return true;
59 }
60 var treeData = transToTreeData(item.Children);
61 $("#tree-" + id).tree({
62 data: treeData,
63 onClick: function(node) {
64 if (node.attributes) {
65 var tabTitle = node.text;
66 var url = node.attributes.url;
67 var icon = node.iconCls;
68 addTab(tabTitle, url, icon);
69 }
70 }
71 });
72 });
73 });
74
75 $("#main-tab").tabs({
76 onContextMenu: function(e, title) {
77 e.preventDefault();
78 $("#tab-menu").menu("show", { left: e.pageX, top: e.pageY })
79 .data("tabTitle", title); //将点击的Tab标题加到菜单数据中
80 }
81 });
82
83 $("#tab-menu").menu({
84 onClick: function(item) {
85 tabHandle(this, item.id);
86 }
87 });
88 });
89
90 function addTab(title, url, icon) {
91 var $mainTabs = $("#main-tab");
92 if ($mainTabs.tabs("exists", title)) {
93 $mainTabs.tabs("select", title);
94 } else {
95 $mainTabs.tabs("add", {
96 title: title,
97 closable: true,
98 icon: icon,
99 content: createFrame(url)
100 });
101 }
102 }
103
104 function createFrame(url) {
105 var html = '<iframe scrolling="auto" frameborder="0" src="' + url + '" style="width:100%;height:99%;"></iframe>';
106 return html;
107 }
108
109 function tabHandle(menu, type) {
110 var title = $(menu).data("tabTitle");
111 var $tab = $("#main-tab");
112 var tabs = $tab.tabs("tabs");
113 var index = $tab.tabs("getTabIndex", $tab.tabs("getTab", title));
114 var closeTitles = [];
115 switch (type) {
116 case "tab-menu-refresh":
117 var iframe = $(".tabs-panels .panel").eq(index).find("iframe");
118 if (iframe) {
119 var url = iframe.attr("src");
120 iframe.attr("src", url);
121 }
122 break;
123 case "tab-menu-openFrame":
124 var iframe = $(".tabs-panels .panel").eq(index).find("iframe");
125 if (iframe) {
126 window.open(iframe.attr("src"));
127 }
128 break;
129 case "tab-menu-close":
130 closeTitles.push(title);
131 break;
132 case "tab-menu-closeleft":
133 if (index == 0) {
134 $.osharp.easyui.msg.tip("左边没有可关闭标签。");
135 return;
136 }
137 for (var i = 0; i < index; i++) {
138 var opt = $(tabs[i]).panel("options");
139 if (opt.closable) {
140 closeTitles.push(opt.title);
141 }
142 }
143 break;
144 case "tab-menu-closeright":
145 if (index == tabs.length - 1) {
146 $.osharp.easyui.msg.tip("右边没有可关闭标签。");
147 return;
148 }
149 for (var i = index + 1; i < tabs.length; i++) {
150 var opt = $(tabs[i]).panel("options");
151 if (opt.closable) {
152 closeTitles.push(opt.title);
153 }
154 }
155 break;
156 case "tab-menu-closeother":
157 for (var i = 0; i < tabs.length; i++) {
158 if (i == index) {
159 continue;
160 }
161 var opt = $(tabs[i]).panel("options");
162 if (opt.closable) {
163 closeTitles.push(opt.title);
164 }
165 }
166 break;
167 case "tab-menu-closeall":
168 for (var i = 0; i < tabs.length; i++) {
169 var opt = $(tabs[i]).panel("options");
170 if (opt.closable) {
171 closeTitles.push(opt.title);
172 }
173 }
174 break;
175 }
176 for (var i = 0; i < closeTitles.length; i++) {
177 $tab.tabs("close", closeTitles[i]);
178 }
179 }
180
181 function transToTreeData(data) {
182 return $.Enumerable.From(data).Select(function(m) {
183 var obj = {};
184 obj.id = m.Id;
185 obj.text = m.Text;
186 obj.iconCls = m.IconCls;
187 obj.checked = m.Checked;
188 if (m.Url) {
189 obj.attributes = { url: m.Url };
190 }
191 if (m.Children && m.Children.length > 0) {
192 obj.children = transToTreeData(m.Children);
193 }
194 return obj;
195 }).ToArray();
196 }
197 </script>
198 }
效果如下:

左导航数据加载
由上面的代码可知,左边导航菜单,完全是由JS解析后端返回的JSON数据来构建,使用后端来返回数据,而不是在前端构建菜单数据,主要是便于将来进行权限控制,后端可以根据当前用户的权限返回特定的菜单数据。后端代码如下:

1 [AjaxOnly]
2 public ActionResult GetNavData()
3 {
4 List<TreeNode> nodes = new List<TreeNode>()
5 {
6 new TreeNode()
7 {
8 Text = "权限",
9 IconCls = "pic_26",
10 Children = new List<TreeNode>()
11 {
12 new TreeNode() { Text = "用户管理", IconCls = "pic_5", Url = Url.Action("Index", "Users") },
13 new TreeNode() { Text = "角色管理", IconCls = "pic_198", Url = Url.Action("Index", "Roles") },
14 new TreeNode() { Text = "组织机构管理", IconCls = "pic_93", Url = Url.Action("Index", "Organizations") },
15 }
16 },
17 new TreeNode()
18 {
19 Text = "系统",
20 IconCls = "pic_100",
21 Children = new List<TreeNode>()
22 {
23 new TreeNode() { Text = "操作日志", IconCls = "pic_125", Url = Url.Action("Index", "OperateLogs") },
24 new TreeNode() { Text = "系统日志", IconCls = "pic_101", Url = Url.Action("Index", "SystemLogs") },
25 new TreeNode() { Text = "系统设置", IconCls = "pic_89", Url = Url.Action("Index", "SystemSettings") }
26 }
27 }
28 };
29
30 Action<ICollection<TreeNode>> action = list =>
31 {
32 foreach (var node in list)
33 {
34 node.Id = "node" + node.Text;
35 }
36 };
37
38 foreach (var node in nodes)
39 {
40 node.Id = "node" + node.Text;
41 if (node.Children != null && node.Children.Count > 0)
42 {
43 action(node.Children);
44 }
45 }
46
47 return Json(nodes, JsonRequestBehavior.AllowGet);
48 }
上面的代码中,添加了一个 [AjaxOnly],作用标记此方法只允许AJAX的调用方式,拦截非Ajax调用,在数据安全上能起到一定的作用。
1 /// <summary>
2 /// 限制当前功能只允许以Ajax的方式来访问
3 /// </summary>
4 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
5 public class AjaxOnlyAttribute : ActionFilterAttribute
6 {
7 /// <summary>
8 /// Called before an action method executes.
9 /// </summary>
10 /// <param name="filterContext">The filter context.</param>
11 public override void OnActionExecuting(ActionExecutingContext filterContext)
12 {
13 if (!filterContext.HttpContext.Request.IsAjaxRequest())
14 {
15 filterContext.Result = new ContentResult
16 {
17 Content = Resources.Mvc_ActionAttribute_AjaxOnlyMessage
18 };
19 }
20 }
21 }
打上此自定义属性后,如果使用非AJAX的方式来调用上面的GetNavData代码,无法得到返回的JSON数据

正确解析返回数据后,构建导航菜单,点击菜单后打开相应的选项卡

EasyUI-datagrid布局
提取父视图(模板)_DataGridLayout.cshtml
在实践中,我们会发现,大部分 datagrid 的代码组织方式都相似的,不同的只是数据源不同,操作之后提交的URL不同。为了减少重复代码,提高代码的复用率,我们可以把共同的代码提取出来,而MVC的 Layout 又刚好是支持嵌套的,那么,类似于前面的 _Layout.cshtml,我们可以提取一个datagrid的共同父视图 _DataGridLayout.cshtml。
_DataGridLayout.cshtml 的提取原理如下:
- javascript 的变量均是全局变量,并且是有前后顺序的,就可以按需要进行重新赋值
- 在 父视图(_Layout)中初始化 javascript变量,并在适当的位置(变量真正使用之前)向 子视图(Partial View)开放 RenderSection
- 子视图(Partial View)按需要对 父视图(_Layout)中定义的 javascript变量 进行重新赋值
- 正在的运算逻辑运算的时候,使用的就是重新赋值的新值了,以达到复用 Layout 的目的
- 父视图中需要的 C# 变量,通过在子视图中定义 ViewBag 传递过去,比如dom元素的id,数据操作的URL等等
根据 easyui-datagrid 的常用变量及上面的原理,定义的 _DataGridLayout.cshtml 大致结构如下,请结合注释进行理解:
1 @{
2 Layout = "~/Areas/Admin/Views/Shared/_Layout.cshtml";
3 string toolbarItem = ViewBag.ToolbarItem ?? "add,edit,save,cancel,delete";
4 }
5 @section header
6 {
7 <style type="text/css">
8 html { font-family: sans-serif; }
9 .datagrid-header-inner { font-weight: bold; }
10 </style>
11 }
12 @section footer
13 {
14 @*这里进行变量初始化*@
15 <script type="text/javascript">
16 //定义及初始化变量
17 var rownumbers = true, singleSelect = false, ctrlSelect = true, multiSort = false, pageSize = 25;
18 var grid, frozenColumns = [[]], columns = [[]], ...
19
20 //前置逻辑,将在构造datagrid之前执行
21 var startfunction = function() { };
22 //后置逻辑,将在构造datagrid之后执行
23 var endfunction = function() { };
24
25 </script>
26
27 @*开放一个Section,让子视图(Partial View)可以插入代码,对上面定义的变量进行重新赋值。*@
28 @RenderSection("customScript", true)
29
30 @*这里才正在执行业务逻辑*@
31 <script type="text/javascript">
32 $(function () {
33 //执行前置逻辑
34 startfunction();
35
36 //构造 datagrid
37 grid = $("#grid-@ViewBag.GridId").datagrid({
38 title: "@ViewBag.Title",
39 fit: true,
40 frozenColumns: frozenColumns,
41 columns: columns,
42 fitColumns: false,
43 url: "@ViewBag.GridDataUrl",
44 ...
45 });
46
47 //执行后置逻辑
48 endfunction();
49 });
50 </script>
51 }
52 @* 后台还有可能有需要执行的逻辑,开放一个Section *@
53 @RenderSection("endScript", false)
54 }
55 @* datagrid 前面有可能需要插入html,开放一个Section *@
56 @RenderSection("headHtml", false)
57 <div id="grid-@ViewBag.GridId"></div>
58 @* datagrid 后面有可能需要插入html,开放一个Section *@
59 @RenderSection("footHtml", false)
结合实际需求,OSharp中定义的一个可用的 _DataGridLayout.cshtml 如下,可以根据需求进行更改:

1 @{
2 Layout = "~/Areas/Admin/Views/Shared/_Layout.cshtml";
3 string toolbarItem = ViewBag.ToolbarItem ?? "add,edit,save,cancel,delete";
4 }
5 @section header{
6 <style type="text/css">
7 html {
8 font-family: sans-serif;
9 }
10
11 .datagrid-header-inner {
12 font-weight: bold;
13 }
14 </style>
15 }
16 @section footer{
17 <script src="/Scripts/plugins/datagrid-filter.js" type="text/javascript"></script>
18 <script src="/Scripts/plugins/datagrid-detailview.js" type="text/javascript"></script>
19 <script type="text/javascript">
20 var rownumbers = true, singleSelect = false, ctrlSelect = true, multiSort = false, pageSize = 25;
21 var grid, frozenColumns = [[]], columns = [[]], filterData = [], enableFilterData = false, editIndex = undefined, columnMenu = undefined;
22
23 var startfunction = function () {
24 };
25 var endfunction = function () {
26 };
27 var addObject = function () {
28 return {};
29 };
30 var replaceSearchField = function (field) {
31 return field;
32 };
33 </script>
34 @RenderSection("paramInit", false)
35 <script type="text/javascript">
36 function formatBoolean(value) {
37 var icon = value ? 'checkmark' : 'checknomark';
38 return '<span class="tree-file icon-' + icon + '"></span>';
39 }
40
41 var addNewRow = function () {
42 if (!endEditing()) {
43 $.osharp.easyui.msg.tip("请先提交或取消正在编辑的行。");
44 return;
45 }
46 grid.datagrid("appendRow", addObject() || {});
47 editIndex = grid.datagrid("getRows").length - 1;
48 grid.datagrid("selectRow", editIndex)
49 .datagrid("beginEdit", editIndex);
50 };
51
52 var beginEdit = function () {
53 var row = grid.datagrid("getSelected");
54 if (!row) {
55 $.osharp.easyui.msg.tip("请选择要编辑的行。");
56 return;
57 }
58 var index = grid.datagrid("getRowIndex", row);
59 beginEditRow(index);
60 };
61
62 var beginEditRow = function (index) {
63 @if (toolbarItem == null || !toolbarItem.Contains(",save"))
64 {
65 @Html.Raw("return;")
66 }
67
68 if (endEditing()) {
69 grid.datagrid("selectRow", index)
70 .datagrid("beginEdit", index);
71 editIndex = index;
72 } else {
73 grid.datagrid("unselectRow", index)
74 .datagrid("selectRow", editIndex);
75 }
76 };
77
78 var cancelEdit = function () {
79 grid.datagrid("rejectChanges");
80 editIndex = undefined;
81 };
82
83 var saveChanges = function () {
84 if (!endEditing()) {
85 return;
86 }
87 var adds = grid.datagrid("getChanges", "inserted");
88 if (adds && adds.length > 0) {
89 submitAdds(adds);
90 }
91 var edits = grid.datagrid("getChanges", "updated");
92 if (edits && edits.length > 0) {
93 submitEdits(edits);
94 }
95 };
96
97 var deleteRows = function () {
98 var selectRows = grid.datagrid("getSelections");
99 if (selectRows.length == 0) {
100 $.osharp.easyui.msg.tip("请先选中要删除的行。");
101 return;
102 }
103 var ids = $.Enumerable.From(selectRows).Select(function (m) { return m.Id; }).ToArray();
104 $.osharp.easyui.msg.confirm("是否要删除所有选中的行?此操作是不可恢复的。", null, function () {
105 $.post("@ViewBag.DeleteUrl", { ids: JSON.stringify(ids) }, ajaxResultHandler);
106 });
107 };
108
109 function endEditing() {
110 if (editIndex == undefined) {
111 return true;
112 }
113 if (grid.datagrid("validateRow", editIndex)) {
114 grid.datagrid("endEdit", editIndex);
115 editIndex = undefined;
116 return true;
117 } else {
118 return false;
119 }
120 }
121
122 function submitAdds(objs) {
123 $.post("@ViewBag.AddUrl", { dtos: JSON.stringify(objs) }, ajaxResultHandler);
124 }
125
126 function submitEdits(objs) {
127 $.post("@ViewBag.EditUrl", { dtos: JSON.stringify(objs) }, ajaxResultHandler);
128 }
129
130 function ajaxResultHandler(data) {
131 if (data.Type == "Success") {
132 grid.datagrid("reload");
133 }
134 if (data.Type == "Error") {
135 $.osharp.easyui.msg.error(data.Content);
136 } else {
137 $.osharp.easyui.msg.tip(data.Content);
138 }
139 }
140
141 var toolbarData = [
142 @if (toolbarItem.Contains("add"))
143 {
144 @:{ text: "增加", iconCls: "icon-add", handler: addNewRow },
145 }
146 @if (toolbarItem.Contains("edit"))
147 {
148 <text>
149 { text: "编辑", iconCls: "icon-edit", handler: beginEdit },
150 "-",
151 </text>
152 }
153 @if (toolbarItem.Contains("save"))
154 {
155 @:{ text: "保存", iconCls: "icon-save", handler: saveChanges },
156 }
157 @if (toolbarItem.Contains("cancel"))
158 {
159 <text>
160 { text: "取消", iconCls: "icon-undo", handler: cancelEdit },
161 "-",
162 </text>
163 }
164 @if (toolbarItem.Contains("delete"))
165 {
166 @:{ text: "删除", iconCls: "icon-remove", handler: deleteRows },
167 }
168 ];
169 </script>
170 @RenderSection("customScript", true)
171 <script type="text/javascript">
172 $(function () {
173 startfunction();
174
175 grid = $("#grid-@ViewBag.GridId").datagrid({
176 title: "@ViewBag.Title",
177 fit: true,
178 frozenColumns: frozenColumns,
179 columns: columns,
180 fitColumns: false,
181 url: "@ViewBag.GridDataUrl",
182 loadMsg: "正在加载数据,请稍候",
183 toolbar: toolbarData,
184 rownumbers: rownumbers,
185 singleSelect: singleSelect,
186 ctrlSelect: ctrlSelect,
187 multiSort: multiSort,
188 pagination: true,
189 pageSize: pageSize,
190 pageList: [10, 25, 50, 100, 200],
191 remoteFilter: true,
192 onBeforeLoad: beforeLoad,
193 loadFilter: loadFilter,
194 onLoadError: loadError,
195 onDblClickRow: beginEditRow,
196 onHeaderContextMenu: headerContextMenu,
197 showFooter: true
198 });
199 if (enableFilterData) {
200 grid.datagrid("enableFilter", filterData);
201 }
202
203 endfunction();
204 });
205
206 //Header右键
207 function headerContextMenu(e) {
208 e.preventDefault();
209 if (!columnMenu) {
210 createColumnMenu();
211 }
212 columnMenu.menu("show", { left: e.pageX, top: e.pageY });
213 }
214
215 function createColumnMenu() {
216 columnMenu = $("<div/>").appendTo("body");
217 columnMenu.menu({
218 onClick: function (item) {
219 if (item.iconCls == "icon-checkmark") {
220 grid.datagrid("hideColumn", item.name);
221 columnMenu.menu("setIcon", { target: item.target, iconCls: "icon-checknomark" });
222 } else {
223 grid.datagrid("showColumn", item.name);
224 columnMenu.menu("setIcon", { target: item.target, iconCls: "icon-checkmark" });
225 }
226 }
227 });
228 var fields = grid.datagrid("getColumnFields");
229 for (var i = 0; i < fields.length; i++) {
230 var field = fields[i];
231 var col = grid.datagrid("getColumnOption", field);
232 columnMenu.menu("appendItem", { text: col.title, name: field, iconCls: col.hidden ? "icon-checknomark" : "icon-checkmark" });
233 }
234 }
235
236 //param的部分属性与后台要求不符,重置属性并删除原有属性
237 function beforeLoad(param) {
238 if (param.page) {
239 param.pageIndex = param.page;
240 delete param.page;
241 }
242 if (param.rows) {
243 param.pageSize = param.rows;
244 delete param.rows;
245 }
246 if (param.sort) {
247 var array = param.sort.split(',');
248 for (var i = 0; i < array.length; i++) {
249 var field = array[i];
250 array[i] = replaceSearchField(field);
251 }
252 param.sort = $.osharp.tools.array.expandAndToString(array, ",");
253 param.sortField = param.sort;
254 delete param.sort;
255 }
256 if (param.order) {
257 param.sortOrder = param.order;
258 delete param.order;
259 }
260 if (param.filterRules) {
261 if (param.filterRules != "[]") {
262 param.where = getFilterGroup(param.filterRules);
263 }
264 delete param.filterRules;
265 }
266 }
267
268 function getFilterGroup(filterRules) {
269 var group = new $.osharp.filter.group();
270 var rules = eval(filterRules);
271 for (var i = 0; i < rules.length; i++) {
272 var rule = rules[i];
273 rule.field = replaceSearchField(rule.field);
274 rule.op = rule.op == "beginwith" ? "startswith" : rule.op == "endwith" ? "endswith" : rule.op;
275
276 group.Rules.push(new $.osharp.filter.rule(rule.field, rule.value, rule.op));
277 }
278 return JSON.stringify(group);
279 }
280
281 function loadFilter(data) {
282 if (data.Type != undefined && data.Type == "Error") {
283 $.osharp.easyui.msg.error(data.Content);
284 data.rows = [];
285 data.total = 0;
286 return data;
287 }
288 if (data.Rows != undefined && data.Total != undefined) {
289 data.rows = data.Rows;
290 data.total = data.Total;
291 delete data.Rows;
292 delete data.Total;
293 }
294 return data;
295 }
296
297 function loadError() {
298 $.osharp.easyui.msg.error("远程数据载入失败,请重试或检查参数。");
299 }
300
301 </script>
302 @RenderSection("endScript", false)
303 }
304 @RenderBody()
305 @RenderSection("headHtml", false)
306 <div id="grid-@ViewBag.GridId"></div>
307 @RenderSection("footHtml", false)
实例应用
OSharp.Web组件中,定义了一个专用于返回表格数据的类,表格只需要行数据与总行数
1 /// <summary>
2 /// 列表数据,封装列表的行数据与总记录数
3 /// </summary>
4 /// <typeparam name="T"></typeparam>
5 public class GridData<T>
6 {
7 public GridData()
8 : this(new List<T>(), 0)
9 { }
10
11 public GridData(IEnumerable<T> rows, int total)
12 {
13 Rows = rows;
14 Total = total;
15 }
16
17 /// <summary>
18 /// 获取或设置 行数据
19 /// </summary>
20 public IEnumerable<T> Rows { get; set; }
21
22 /// <summary>
23 /// 获取或设置 数据行数
24 /// </summary>
25 public int Total { get; set; }
26 }
通过这个类,就可以向easyui返回数据了,如下:
1 [AjaxOnly]
2 public ActionResult GridData()
3 {
4 List<object>data =new List<object>();
5 for (int i = 1; i <= 20; i++)
6 {
7 var item = new { Id = i, Name = "UserName" + i, NickName = "用户" + i, IsDeleted = false, CreatedTime = DateTime.Now.AddMinutes(i) };
8 data.Add(item);
9 }
10 return Json(new GridData<object>(data, data.Count), JsonRequestBehavior.AllowGet);
11 }
有了前面定义的 datagrid 父视图 _DataGridLayout.cshtml,用户列表(Views\Users\Index.cshtml)的代码就是如此的简单,仅仅需要把columns重新赋值而已
1 @{
2 ViewBag.Title = "用户信息列表";
3 Layout = "~/Areas/Admin/Views/Shared/_DataGridLayout.cshtml";
4
5 ViewBag.GridId = "users";
6 ViewBag.GridDataUrl = Url.Action("GridData");
7 }
8 @section customScript
9 {
10 <script type="text/javascript">
11 columns = [[
12 { field: "Id", title: "编号", width: 40, halign: "center", align: "right", sortable: true },
13 { field: "Name", title: "用户名", width: 150, sortable: true },
14 { field: "NickName", title: "用户昵称", width: 150, sortable: true },
15 { field: "IsDeleted", title: "已删除", width: 80, sortable: true, align: "center", formatter: formatBoolean },
16 { field: "CreatedTime", title: "创建时间", width: 150, halign: "center", align: "center", sortable: true, formatter: function (value) { return $.osharp.tools.formatDate(value); } }
17 ]];
18 </script>
19 }
这样,便可以运行出用户列表的结果,如下

比如添加一个角色信息列表,视图(Views\Roles\Index.cshtml)也同用户列表一样,简单到极致:
1 @{
2 ViewBag.Title = "角色信息列表";
3 Layout = "~/Areas/Admin/Views/Shared/_DataGridLayout.cshtml";
4
5 ViewBag.GridId = "roles";
6 ViewBag.GridDataUrl = Url.Action("GridData");
7 }
8 @section customScript
9 {
10 <script type="text/javascript">
11 columns = [[
12 { field: "Id", title: "编号", width: 40, halign: "center", align: "right", sortable: true },
13 { field: "Name", title: "角色名", width: 150, sortable: true },
14 { field: "Remark", title: "角色描述", width: 150, sortable: true },
15 { field: "CreatedTime", title: "创建时间", width: 150, halign: "center", align: "center", sortable: true, formatter: function (value) { return $.osharp.tools.formatDate(value); } }
16 ]];
17 </script>
18 }
运行效果:

就是这样,多动脑,多总结,前端的代码也同样能像后台C#代码一样重构,重构到极致。
未完待续。。。
开源说明
github.com
OSharp项目已在github.com上开源,地址为:https://github.com/i66soft/osharp,欢迎阅读代码,欢迎 Fork,如果您认同 OSharp 项目的思想,欢迎参与 OSharp 项目的开发。
在Visual Studio 2013中,可直接获取 OSharp 的最新源代码,获取方式如下,地址为:https://github.com/i66soft/osharp.git

nuget
OSharp的相关类库已经发布到nuget上,欢迎试用,直接在nuget上搜索 “osharp” 关键字即可找到

系列导航
本文已同步到系列目录:OSharp快速开发框架解说系列
来源:https://www.cnblogs.com/guomingfeng/p/osharp-easyui-view.html
