打开第一节中的那个项目,新建一个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; 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 文件中加入获取员工的分页排序方法,代码如下
/**/ /// <summary> /// 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的代码如下:
<% @ Page Language = " C# " AutoEventWireup = " true " CodeBehind = " Paging.aspx.cs " Inherits = " ExtJSAndWCFChapter1.Paging " %> <! 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 });
运行结果: 源代码下载在这里