第二节:ExtJS调用WCF系列-----分页排序列表实现
打开第一节中的那个项目,新建一个Paging.aspx的页面来实现分页列表。
这次我们使用一个测试的数据库CompanyInfoDB,里面有两张表,部门和员工,并外键关联,数据库调用采用Linq的Sqlmetal 命令方式,在Visual Studio 2008的命令提示符中输入以下命令:D:\Program Files\Microsoft Visual Studio 9.0\VC>sqlmetal /conn:server=172.16.1.52;database=CompanyInfoDB;uid=sa;pwd=sa123456 /map:c:\LinqTemp\CompanyInfoDB.map
/code:c:\LinqTemp\CompanyInfoDB.cs /serialization:Unidirectional
然后把生成的CompayInfo.map 文件和CompanyInfo.cs文件加入到项目中,并添加System.Data.Linq的引用,还要修改一下Web.Config 加入数据库链接字符串和XmlMappingSource文件的位置。
<connectionStrings>
<add name="CompanyInfoDBConnectionString" connectionString="Data Source=172.16.1.52;Initial Catalog=CompanyInfoDB;Persist Security Info=True;User ID=sa;Password=sa123456" providerName="System.Data.SqlClient"/>
</connectionStrings>
<appSettings>
<add key="CompanyInfoDBXmlMappingSource" value="E:\ExtJS\ExtJS调用WCF系列博客源文件\ExtJSAndWCFChapter1\ExtJSAndWCFChapter1\DataBase\CompanyInfoDB.map"/>
</appSettings>
如图:
为了层次更清晰一点,我们新建一个EmployeeBL.cs的类文件来处理Employee的业务逻辑,EmpployeeBL.cs的文件代码如下:
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Data.Linq.Mapping;
using System.IO;
using System.Linq.Dynamic;
using System.Runtime.Serialization;
namespace ExtJSAndWCFChapter1
{
public class EmployeeBL
{
CompanyInfoDB ctx;
//构造函数
public EmployeeBL()
{
XmlMappingSource xms = XmlMappingSource.FromXml(File.ReadAllText(ConfigurationManager.AppSettings["CompanyInfoDBXmlMappingSource"]));
ctx = new CompanyInfoDB(ConfigurationManager.ConnectionStrings["CompanyInfoDBConnectionString"].ConnectionString, xms);
//ctx.Log = Console.Out;
}
public string GetEmployeePaging(int start, int limit, string sort, string dir)
{
string strJsonSource = "";
var query = from emp in ctx.Employee
select new
{
EmployeeID = emp.EmployeeID,
CnName = emp.CnName,
Sex = emp.Sex,
Age = emp.Age,
Email = emp.Email,
OnWorkDate = emp.OnWorkDate,
DeptName = emp.Department.CnName
};
query = query.OrderBy(sort + " " + dir);
int TotalCount = query.Count(); //共有记录数
int PageNum = start / limit; //共有页数
int PageSize = limit; //每页记录数
query = query.Skip(PageSize * PageNum).Take(PageSize);
string JsonSource = query.ToJSON(); //当前页记录转成JSON格式
strJsonSource = @"{""TotalCount"":""" + TotalCount + "";
strJsonSource = strJsonSource + @""",""EmployeeList"":" + JsonSource + "}";
return strJsonSource;
}
}
}
这里需要两个类文件:Dynamic.cs 和 JSONHelper.cs 前者是微软提供的Linq动态查询文件,后者是scottgu的JSON序列化文件。
并在EmployeeService.svc.cs 文件中加入获取员工的分页排序方法,代码如下
/// Employee分页排序
/// </summary>
/// <param name="start"></param>
/// <param name="limit"></param>
/// <param name="sort"></param>
/// <param name="dir"></param>
/// <returns></returns>
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, UriTemplate = "/GetEmployeePaging")]
public string GetEmployeePaging(int start, int limit, string sort, string dir)
{
if (start < 0 || sort == "") throw new ArgumentException("参数错误!");
EmployeeBL bl = new EmployeeBL();
return bl.GetEmployeePaging(start, limit, sort, dir);
}
接下来我们编写客户端代码,这次我们新建一个paging.js的文件来存放Paging.Aspx的脚本文件,ExtJS调用WCF的时候有两点须注意:
第一点:关于前一节中
[WebInvoke(BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, UriTemplate = "/Get")]中的BodyStyle = WebMessageBodyStyle.Bare 和
[WebInvoke(BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, UriTemplate = "/GetAll")]中的BodyStyle = WebMessageBodyStyle.Wrapped的区别,看前一节的两个例子,我们不难发现两个返回值的不同之处:
Get:
{"Age":28,"Birthday":"\/Date(286732800000+0800)\/","CnName":"Xiaozhuang","Email":"iamxiaozhuang@163.com","EmployeeID":"b34f963e-8520-44da-be00-bd0a1aeadc78","Sex":true}
GetAll:
{"GetAllResult":[{"Age":28,"Birthday":"\/Date(286732800000+0800)\/","CnName":"CnName","Email":"email@saf.com","EmployeeID":"2835ba7e-5f0c-41ff-8746-d6e959800850","Sex":true},{"Age":28,"Birthday":"\/Date(286732800000+0800)\/","CnName":"CnName1","Email":"email1@saf.com","EmployeeID":"d5b7a088-13a8-4195-8ce9-e0836cd33de4","Sex":false}]}
可以看到GetAll 的返回值给加了一个GetAllResult的根对象,但是这个根对象在ExtJS调用的时候是不需要的,必须去掉这个根对象才行,那么我们为什么不用WebMessageBodyStyle.Bare非要用BodyStyle = WebMessageBodyStyle.Wrapped?那是因为前者只有在WCF方法只有一个输入参数,而且不需要对其进行序列化的时候才可以使用,但是很多方法要求都不止一个参数,所以必须用后者。那我们怎样才能去掉这个根对象呢?在http://erichauser.net/?p=35有个去掉这个根对象的方法,不过他提供的那个文件有问题,不能达到预期的效果,经过调试,我把文件WCFJsonReader.js进行了改进,代码如下:
* @class Ext.data.WCFJsonReader
* @extends Ext.data.JsonReader
*
* Custom reader for reading data from WCF. If you are using WebMessageBodyStyle.Wrapped, then WCF adds a root object to your
* JSON result:
*
* { "MethodResult": { "Count" : 0, "Objects": [ … ] } }
*
* Ext does not expect the result to have a root object before parsing, so this class will remove it.
*/
Ext.data.WCFJsonReader = Ext.extend(Ext.data.JsonReader, {
/* @cfg {Boolean} Sets whether or not the OperationContract has the is using WebMessageBodyStyle.Wrapped */
wrapped: true,
/**
* If wrapped is set to true, we will strip WCF’s wrap object
*/
read : function(response){
var json = response.responseText;
var o = eval("("+json+")");
if(!o) {
throw {message: "JsonReader.read: Json object not found"};
}
if (this.wrapped) {
for (var prop in o) {
if (typeof(prop) == 'string' && prop.substr(prop.length-6,prop.length) == 'Result') {
o = this.convert(o[prop]);
//o = o[prop];
break;
}
}
}
if(o.metaData){
delete this.ef;
this.meta = o.metaData;
this.recordType = Ext.data.Record.create(o.metaData.fields);
this.onMetaChange(this.meta, this.recordType, o);
}
return Ext.data.WCFJsonReader.superclass.readRecords.call(this, o);
},
//private
convert : function(o) {
o = eval("("+o+")");
var newResult = new Object();
for (var rootProp in o) {
newResult[rootProp] = o[rootProp];
}
return newResult;
}
});
在调用WCF的时候,用这个WCFJsonReader.js代替原始的JsonReader就可以了。
第二点:我们在调用WCF的时候要指明Content-type是application/json,而且参数必须要进行JSON序列化才可以,但是在ExtJS提供的分页排序例子中输入参数是直接由GridPanel提供的,所以我们必须截获这些参数,并对它进行JSON序列化,由此我建立了一个WCFHttpProxy.js的文件,这个文件继承自Ext.data.HttpProxy并重载了它的load方法,在这个方法里截获输入参数,并对它们进行JSON序列化。代码如下:
* Ext JS Library 2.0 RC 1
* Copyright(c) 2006-2007, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
/**
* Author by xiaozhuang
*/
Ext.data.WCFHttpProxy = Ext.extend(Ext.data.HttpProxy, {
load : function(params, reader, callback, scope, arg){
if(this.fireEvent("beforeload", this, params) !== false){
Ext.lib.Ajax.defaultPostHeader = 'application/json';
params = Ext.util.JSON.encode(params);
var o = {
params : params || {},
request: {
callback : callback,
scope : scope,
arg : arg
},
reader: reader,
callback : this.loadResponse,
scope: this
};
if(this.useAjax){
Ext.applyIf(o, this.conn);
if(this.activeRequest){
Ext.Ajax.abort(this.activeRequest);
}
this.activeRequest = Ext.Ajax.request(o);
}else{
this.conn.request(o);
}
}else{
callback.call(scope||this, null, arg, false);
}
}
});
其实就是增加了Ext.lib.Ajax.defaultPostHeader = 'application/json';params = Ext.util.JSON.encode(params);两行。
有了这两个文件,我们要改进一下我们的EXTJS,在它的目录下建立WCF的文件夹,并把这两个文件拷贝进去。
现在我们可以编写客户端代码了,Paging.aspx的代码如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
<link rel="stylesheet" type="text/css" href="ExtJS/resources/css/ext-all.css" />
<script type="text/javascript" src="ExtJS/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="ExtJS/ext-all.js"></script>
<!-- ExtJS调用WCF的专用文件 -->
<script type="text/javascript" src="ExtJS/WCF/WCFHttpProxy.js"></script>
<script type="text/javascript" src="ExtJS/WCF/WCFJsonReader.js"></script>
<!-- 简体中文语言包 -->
<script type="text/javascript" src="ExtJS/source/locale/ext-lang-zh_CN.js"></script>
<!-- 分页排序ExtJS代码 -->
<script type="text/javascript" src="paging.js"></script>
<!-- Common Styles for the examples -->
<style type="text/css">
body .x-panel {
margin-bottom:20px;
}
.icon-grid {
background-image:url(ExtJS/icons/fam/grid.png) !important;
}
#button-grid .x-panel-body {
border:1px solid #99bbe8;
border-top:0 none;
}
.add {
background-image:url(ExtJS/icons/fam/add.gif) !important;
}
.option {
background-image:url(ExtJS/icons/fam/plugin.gif) !important;
}
.remove {
background-image:url(ExtJS/icons/fam/delete.gif) !important;
}
.save {
background-image:url(ExtJS/icons/save.gif) !important;
}
</style>
</head>
<body>
<form id="form1" runat="server">
<h1>Paging Grid Example</h1><br /><br />
<div id="topic-grid"></div>
</form>
</body>
</html>
Paging.js的代码如下:
* Author by Xiaozhuang
*
*
*/
Ext.onReady(function(){
// create the Data Store
var store = new Ext.data.Store({
// load using script tags for cross domain, if the data in on the same domain as
// this page, an HttpProxy would be better
proxy: new Ext.data.WCFHttpProxy({
url: '/EmployeeService.svc/GetEmployeePaging'
}),
// create reader that reads the Topic records
reader: new Ext.data.WCFJsonReader({
root: 'EmployeeList',
totalProperty: 'TotalCount',
id: 'EmployeeID',
fields: [
{name: 'EmployeeID', type: 'int'},
{name: 'CnName', type: 'string'},
{name: 'Sex', type: 'string'},
{name: 'Age', type: 'int'},
{name: 'Email', type: 'string'},
{name: 'OnWorkDate',type:'string'},
{name: 'DeptName', type: 'string'}
]
}),
// turn on remote sorting
remoteSort: true
});
store.setDefaultSort('EmployeeID', 'ASC');
// 把true和false转化为男或者女,这个其实可以在服务器端进行转化,写在这里只是为了测试
function renderSex(value, p, record){
return record.data.Sex=="true"?"男":"女";
}
//这个函数演示了怎样把服务器端的DateTime类型转为Javascript的日期
function renderOnWorkDate(value, p, record){
var jsondate = record.data.OnWorkDate;
return eval("new " + jsondate.substr(1,jsondate.length-2)).toLocaleDateString();
}
// the column model has information about grid columns
// dataIndex maps the column to the specific data field in
// the data store
var nm = new Ext.grid.RowNumberer();
var sm = new Ext.grid.CheckboxSelectionModel(); // add checkbox column
var cm = new Ext.grid.ColumnModel([nm,sm,{
header: "员工ID",
dataIndex: 'EmployeeID',
width: 100
// renderer: renderTopic
},{
header: "姓名",
dataIndex: 'CnName',
width: 200
},{
header: "性别",
dataIndex: 'Sex',
width: 70,
renderer: renderSex
},{
header: "年龄",
dataIndex: 'Age',
width: 70
},{
header: "Email",
dataIndex: 'Email',
width: 150
},{
header: "入职时间",
dataIndex: 'OnWorkDate',
width: 150,
renderer: renderOnWorkDate
},{
header: "部门",
dataIndex: 'DeptName',
width: 200
}]);
// by default columns are sortable
cm.defaultSortable = true;
var grid = new Ext.grid.GridPanel({
//el:'topic-grid',
renderTo: document.body,
width:800,
height:500,
title:'分页和排序列表',
store: store,
cm: cm,
trackMouseOver:false,
sm: sm,
loadMask: true,
viewConfig: {
forceFit:true,
enableRowBody:true,
showPreview:true,
getRowClass : function(record, rowIndex, p, store){
return 'x-grid3-row-collapsed';
}
},
// inline toolbars
tbar:[{
text:'添加',
tooltip:'添加一条记录',
iconCls:'add'
}, '-', {
text:'修改',
tooltip:'修改',
iconCls:'option'
},'-',{
text:'删除',
tooltip:'删除记录',
iconCls:'remove',
handler:handleDelete
}],
bbar: new Ext.PagingToolbar({
pageSize: 25,
store: store,
displayInfo: true
})
});
// render it
grid.render();
// trigger the data store load
var request={start:0,limit:25};
store.load({params:request});
function handleDelete(){
var selectedKeys = grid.selModel.selections.keys; //returns array of selected rows ids only
if(selectedKeys.length > 0)
{
Ext.MessageBox.confirm('提示','您确实要删除选定的记录吗?', deleteRecord);
}
else
{
Ext.MessageBox.alert('提示','请至少选择一条记录!');
}//end
}
function deleteRecord(btn){
if(btn=='yes'){
store.reload();
}//end if click 'yes' on button
} // end deleteRecord
});
运行结果:
源代码下载在这里