ExtJs与WCF之间的跨域访问
在上一篇文章中用ExtJs与Wcf交互实现了分页Grid,回复中心有灵犀同学希望能采用跨域访问的方式,这个问题其实也困扰了我很久,本来ExtJS用ScriptTagProxy支持跨域访问的,如果服务端是.aspx的页面文件,也非常好实现,但换作WCF,问题就复杂起来。本文尝试解决这个问题,方案不是很巧妙,但是我对多种方案实验中第一个且是唯一有效的办法。
在上一篇文章<<ExtJs+WCF+LINQ实现分页Grid>>中用ExtJs与Wcf交互实现了分页Grid,回复中心有灵犀同学希望能采用跨域访问的方式,这个问题其实也困扰了我很久,本来ExtJS用ScriptTagProxy支持跨域访问的,如果服务端是.aspx的页面文件,也非常好实现,但换作WCF,问题就复杂起来。本文尝试解决这个问题,方案不是很巧妙,但是我对多种方案实验中第一个且是唯一有效的办法。
首先看一下如何用ExtJs中的ScriptTagProxy跨域访问服务器.aspx页面,不是重点,但与为何此种方法不适用WCF相关,所以也赘述下,项目是上文中的项目,下面是实现步骤:
第一步:还是向网站中添加Service.aspx页面,然后将其代码更改如下:
Service.aspx代码
using System;
using System.Collections;
using System.Configuration;
using System.Data;
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;
namespace ExtJs_Wcf_Linq_PageGrid
{
public partial class Service : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
int start = Convert.ToInt32(Request.QueryString["start"]);
int limit = Convert.ToInt32(Request.QueryString["limit"]);
string callback = Request.QueryString["callback"];
ProductsDataContext productDbContext = new ProductsDataContext();
IQueryable<Product> res = productDbContext.Product.Select(product => product);
PageData<Product[]> returnData = new PageData<Product[]>();
returnData.TotolRecord = res.ToArray<Product>().Length;
res = res.Skip<Product>(start);
res = res.Take<Product>(limit);
returnData.Data = res.ToArray<Product>();
System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(PageData<Product[]>));
using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
{
serializer.WriteObject(ms, returnData);
ms.Position = 0;
System.IO.StreamReader sr = new System.IO.StreamReader(ms);
Response.Write(callback+"("+sr.ReadToEnd()+")");
}
}
}
}
第二步:创建一个htm页面PageGridCrossDomain.htm,然后将其代码更改为:
PageGridCrossDomain.htm
<!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>
<title>ExtJs+WCF+LINQ打造分页Grid</title>
<link rel="stylesheet" type="text/css" href="resources/css/ext-all.css" />
<script type="text/javascript" src="adapter/ext/ext-base.js" charset="gb2312"></script>
<script type="text/javascript" src="ext-all-debug.js" charset="gb2312"></script>
<link rel="stylesheet" type="text/css" href="shared/examples.css" />
<script type="text/javascript" src="shared/examples.js" charset="gb2312"></script>
<script src="ScriptTagReader.js" type="text/javascript"></script>
<script type="text/javascript" src="PageCrossDomain.js" charset="gb2312"></script>
</head>
<body>
<h1>
ExtJs+WCF+LINQ打造分页跨域Grid</h1>
<div id="page-grid">
</div>
</body>
</html>
第三步:添加实现跨域分页的脚本文件
PageCrossDomain.js
/**//*
* Ext JS Library 2.1
* Copyright(c) 2006-2008, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
Ext.onReady(function(){
//创建一个用于访问WCF服务的HttpProxy,且访问方法为GET
var proxy = new Ext.data.ScriptTagProxy({
url:'http://127.0.0.1:50643/Service.aspx'
});
var reader = new Ext.data.JsonReader(
{root:'Data',totalProperty :'TotolRecord'},
[
{name: 'ProductID'},
{name: 'Name'},
{name: 'ProductNumber'},
{name: 'MakeFlag'},
{name: 'FinishedGoodsFlag'},
{name: 'Color'},
{name: 'SafetyStockLevel'},
{name: 'ReorderPoint'},
{name: 'StandardCost'},
{name: 'ListPrice'},
{name: 'Size'},
{name: 'SizeUnitMeasureCode'},
{name: 'Weight'},
{name: 'DaysToManufacture'},
{name: 'ProductLine'},
{name: 'Class'},
{name: 'Style'},
{name: 'Weight'},
{name: 'ProductSubcategoryID'},
{name: 'ProductModelID'},
{name: 'SellStartDate'},
{name: 'SellEndDate'},
{name: 'DiscontinuedDate'},
{name: 'rowguid'},
{name: 'ModifiedDate'}
]
);
var store = new Ext.data.Store(
{proxy:proxy,reader:reader}
);
// create the Grid
var grid = new Ext.grid.GridPanel({
store: store,
columns: [
{id:'ProductID',header: "编号",width:30, sortable: true, dataIndex: 'ProductID'},
{header: "名称", width:75, sortable: true, dataIndex: 'Name'},
{header: "产品编码", width:75, sortable: true, dataIndex: 'ProductNumber'},
{header: "是否标记", width:75, sortable: true, dataIndex: 'MakeFlag'},
{header: "颜色", width:50, sortable: true,dataIndex:'Color'},
{header: "数量", width:50, sortable: true,dataIndex:'ReorderPoint'},
{header: "单价", width:50, sortable: true,renderer:'userMoney',dataIndex: 'StandardCost'}
],
stripeRows: true,
autoExpandColumn: 'ProductID',
height:400,
width:600,
title:'产品信息',
viewConfig:
{
columnsText:'列',
sortAscText:'升序',
sortDescText:'降序'
},
bbar: new Ext.PagingToolbar({
pageSize: 25,
store: store,
displayInfo: true,
displayMsg: '总记录数 {0} - {1} of {2}',
emptyMsg: "没有记录"
})
});
grid.render('page-grid');
//载入
store.load({params:{start:0,limit:25}});
grid.getSelectionModel().selectFirstRow();
});
浏览PageGridCrossDomain.htm,效果图如下:
好,到此用ExtJs跨域调用.aspx的示例已经完成,过程相当简单,效果也比较完美。
但我写此系列的文章,主要目的在于探讨ExtJs与Wcf交互,经过前面几篇的学习,已经对WCF的RestFul和ExtJs的使用有了些皮毛认识,深切体会到ExtJs+Wcf是一种比较不错的编程模型,它与传统的B/S,C/S都不太一样,相比WebForm,它更像是web开发。而且服务的概念也会得以深刻体现,可谓一箭双雕。书写本文之前,我尝试了很多种办法用于ExtJs跨域访问Wcf实现分页Grid,但都没有成功,查阅了ScriptTagProxy的源码方才发现一些端倪,原来ScriptTagProxy是通过动态创建<scipt>的方式实现跨域的,原理可见阿布兄 的一篇文章:利用script标签实现的跨域名AJAX请求(ExtJS),我也不再赘述,下面给出ScriptTagProxy源码中的一个片段:
这段代码中的 url便是要跨域访问的地址,由此可见url返回的信息必须是完全符合<script></script>格式要求的代码,否则将出现javascript错误,我在前期用ScriptTagProxy调用WCF,总是出现莫名错误,便是缘于此。WCF能返回JSON格式的数据,但这样的数据是不符合变量声明要求的,而察看extjs示例中跨域的相应格式为: strCallback1001({..json数据…}),这样一种格式是声明了一个对象变量strCallback1001,而再看看我们可爱的WCF呢,当我们将其ResponseFormat = WebMessageFormat.Json,而返回PageData<Product[]>,他返回的标准的json数据,不符合要求,而返回string,去掉ResponseFormat = WebMessageFormat.Json,然后在服务方法体中用类似Service.aspx得到json数据的办法得到json数据,然后再通过字符串拼接成js变量声明符合格式要求的数据的时候,再看返回,这次可好,前面无缘无故的给WCF加上了<xml …. Type="string">,原来WCF只支持良种编码格式:XML,Json,这时候我就晕了,你说WCF要是支持个ClearText编码格式,那问题不就解决了嘛,唉,没办法,看来想用ScriptTagProxy是不行了。这个问题困扰了我几天。其实此处我觉得WCF是应该有返回ClearText的选项的,不管怎么样,那样更灵活。可没办法,我查了个遍,认识的几个大牛,也问了,都没有确定答案。
上面一段是一些牢骚,既然是牢骚,语法与思路自然有些乱了,但基本上应该说明了ExtJs与WCF跨域交互的障碍了。
下面是我扫除这一障碍的一个笨得不能再笨的方法
第一步:在项目中创建一个页面Proxy.aspx,作为ExtJs与Wcf跨域操作的代理页面。这个页面和ExtJs位于一个位置,并且去除页面中的HTML代码:
后台代码为:
Proxy.aspx.cs
using System;
using System.Collections;
using System.Configuration;
using System.Data;
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.Collections.Specialized;
namespace ExtJs_Wcf_Linq_PageGrid
{
public partial class Proxy : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string remoteUrl = Request.QueryString["remote"];
using(System.Net.WebClient wc = new System.Net.WebClient())
{
NameValueCollection nvc = Request.QueryString;
foreach (string key in nvc.Keys)
{
if (key != "remote")
{
if (remoteUrl.IndexOf("?") == -1)
{
remoteUrl = remoteUrl + "?"+key+"="+ nvc[key];
continue;
}
remoteUrl = remoteUrl + "&" + key + "=" + nvc[key];
}
}
string response = wc.DownloadString(remoteUrl);
Response.Write(response);
Response.End();
}
}
}
}
第二步:创建一个ExtJs通过服务代理与Wcf实现跨域交互的htm页面 :PageGridCrossDomainWcf.htm
PageGridCrossDomainWcf.htm
<!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>
<title>ExtJs+WCF+LINQ打造分页Grid</title>
<link rel="stylesheet" type="text/css" href="resources/css/ext-all.css" />
<script type="text/javascript" src="adapter/ext/ext-base.js" charset="gb2312"></script>
<script type="text/javascript" src="ext-all-debug.js" charset="gb2312"></script>
<link rel="stylesheet" type="text/css" href="shared/examples.css" />
<script type="text/javascript" src="shared/examples.js" charset="gb2312"></script>
<script src="ScriptTagReader.js" type="text/javascript"></script>
<script type="text/javascript" src="PageGridCrossDoaminWcf.js" charset="gb2312"></script>
</head>
<body>
<h1>
ExtJs+WCF+LINQ打造分页跨域Grid</h1>
<div id="page-grid">
</div>
</body>
</html>
第三步:创建用于ExtJs与Wcf跨域交互的脚本文件:PageGridCrossDoaminWcf.js
PageGridCrossDoaminWcf.js
/**//*
* Ext JS Library 2.1
* Copyright(c) 2006-2008, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
Ext.onReady(function(){
//创建一个用于访问WCF服务的HttpProxy,且访问方法为GET
var proxy = new Ext.data.HttpProxy({
url:'Proxy.aspx?remote=http://127.0.0.1:50643/PageGridService.svc/GetProductsByPage',
method:'GET'
});
var reader = new Ext.data.JsonReader(
{root:'Data',totalProperty :'TotolRecord'},
[
{name: 'ProductID'},
{name: 'Name'},
{name: 'ProductNumber'},
{name: 'MakeFlag'},
{name: 'FinishedGoodsFlag'},
{name: 'Color'},
{name: 'SafetyStockLevel'},
{name: 'ReorderPoint'},
{name: 'StandardCost'},
{name: 'ListPrice'},
{name: 'Size'},
{name: 'SizeUnitMeasureCode'},
{name: 'Weight'},
{name: 'DaysToManufacture'},
{name: 'ProductLine'},
{name: 'Class'},
{name: 'Style'},
{name: 'Weight'},
{name: 'ProductSubcategoryID'},
{name: 'ProductModelID'},
{name: 'SellStartDate'},
{name: 'SellEndDate'},
{name: 'DiscontinuedDate'},
{name: 'rowguid'},
{name: 'ModifiedDate'}
]
);
var store = new Ext.data.Store(
{proxy:proxy,reader:reader}
);
// create the Grid
var grid = new Ext.grid.GridPanel({
store: store,
columns: [
{id:'ProductID',header: "编号",width:30, sortable: true, dataIndex: 'ProductID'},
{header: "名称", width:75, sortable: true, dataIndex: 'Name'},
{header: "产品编码", width:75, sortable: true, dataIndex: 'ProductNumber'},
{header: "是否标记", width:75, sortable: true, dataIndex: 'MakeFlag'},
{header: "颜色", width:50, sortable: true,dataIndex:'Color'},
{header: "数量", width:50, sortable: true,dataIndex:'ReorderPoint'},
{header: "单价", width:50, sortable: true,renderer:'userMoney',dataIndex: 'StandardCost'}
],
stripeRows: true,
autoExpandColumn: 'ProductID',
height:400,
width:600,
title:'产品信息',
viewConfig:
{
columnsText:'列',
sortAscText:'升序',
sortDescText:'降序'
},
bbar: new Ext.PagingToolbar({
pageSize: 25,
store: store,
displayInfo: true,
displayMsg: '总记录数 {0} - {1} of {2}',
emptyMsg: "没有记录"
})
});
grid.render('page-grid');
//载入
store.load({params:{start:0,limit:25}});
grid.getSelectionModel().selectFirstRow();
});
好了,现在浏览PageGridCrossDomainWcf.htm,效果图如下:
最后,国际惯例,代码示例: