这两天在研究如何通过JSONP去实现跨域调用WCF服务,找到了以下几种解决方式:
解决方案一:
在服务器端直接通过调用DataContractJsonSerializer或JavaScriptSerializer对数据进行JSON序列化后返回Stream或直接调用Response.Write方法把序列化后的数据返回给业务系统:
以下的JsonHelper类实现对于JSON调用和JSONP调用进行分别返回:若是JSONP调用则把JSON数据再根据传入的jsoncallback参数封装。
{
/// 输出JSON,支持JSON和JSONP
/// </summary>
/// <param name="obj"></param>
public static void OutputJSON(this object obj)
{
string result;
JavaScriptSerializer serializer = new JavaScriptSerializer();
result = serializer.Serialize(obj);
string pass=System.Web.HttpContext.Current.Request.QueryString["jsoncallback"];
if (string.IsNullOrEmpty(pass) == false)
{
System.Web.HttpContext.Current.Response.Write(
string.Format("{0}({1})", pass, result));
}
else
{
System.Web.HttpContext.Current.Response.Write(result);
}
}
public static T ParseJSON<T>(this string input)
{
T result;
JavaScriptSerializer serializer = new JavaScriptSerializer();
result = serializer.Deserialize<T>(input);
return result;
}
}
所有服务则定为返回值为void, 代码如下:
[ServiceContract(Namespace ="")]
{
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Xml)] // 注意这里一定要用Xml而不是Json,否则返回的数据会被多封装一个Null值进去。
publicvoid GetPurchaseGroup()
{
JsonMsg msg =new JsonMsg(){Name ="XX", Value ="YY"};
m_JsonMsg.OutputJSON();
}
}
当然也可以把JSonHelper类实现为返回Stream流,每个服务器端方法都返回Stream流。从通用性来说,返回Stream的方式兼容性更强,并且可以自己控制返回数据的Content-Type.
虽然通过解决方案一可以实现WCF支持JSONP方式调用,但是这种解决方案侵入性太强,改变了服务的接口(返回值),因此并不是特别好的解决方案,所以我们继续往下看:
解决方案二:
.NET 4.0的新功能.NET 4.0已经增添了原生支持JSONP跨域调用WCF. 主要涉及两点:crossDomainScriptAccessEnabled 配置项和JavascriptCallbackBehaviorAttribute类下面给出 crossDomainScriptAccessEnabled的使用例子:
<behaviors>
<endpointBehaviors>
<behavior name="CorsairStudio.Core.WebSite.CrossDomainServiceAspNetAjaxBehavior">
<enableWebScript />
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="webHttpBindingJsonP" crossDomainScriptAccessEnabled="true"/>
</webHttpBinding>
</bindings>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
<services>
<service name="CorsairStudio.Core.WebSite.CrossDomainService">
<endpoint address="" behaviorConfiguration="CorsairStudio.Core.WebSite.CrossDomainServiceAspNetAjaxBehavior"
binding="webHttpBinding" bindingConfiguration="webHttpBindingJsonP"
contract="CorsairStudio.Core.WebSite.CrossDomainService" />
</service>
</services>
</system.serviceModel>
通过启用 ,WCF便已经可以支持JSONP跨域调用。而属性JavascriptCallbackBehaviorAttribute则用于定义客户端JS调用时使用的URL中的参数名字:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[JavascriptCallbackBehavior(UrlParameterName="callback")]
public class JSonpService
...
PS: 在.NET4中通过JSON调用WCF会被多封装一个'd', 但如果通过JSONP调用,则不会多封装这个'd'.
参见:http://bendewey.wordpress.com/2009/11/24/using-jsonp-with-wcf-and-jquery/
.NET 4已经给出了完整的解决方案,但目前的多数项目都还是基于.NET 3.5的,因此解决方案二依然是雾中花,需要找更实际的解决方案。
解决方案三:HttpModule
HttpModule可以拦截用户的请求或修改输出返回的内容,利用这个特性,可以尝试扩展一个HttpModule为JSONP调用时修改返回的数据
JSONPModule
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
namespace NIntegrate.Web
{
/// <summary>
/// 用于.NET 3.5的JSONP技术支持HttpModule.(.NET 4.0使用crossDomainScriptAccessEnabled=true配置项进行支持)
/// </summary>
public class JSonpilizeModule : IHttpModule
{
private const string RESPONSE_CONTENT_TYPE = "application/json";
internal const string REQUEST_QUERY_KEY = "callback";
#region IHttpModule Members
public void Dispose()
{
}
public void Init(HttpApplication app)
{
app.ReleaseRequestState += new EventHandler(app_ReleaseRequestState);
}
void app_ReleaseRequestState(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
HttpResponse response = app.Response;
HttpRequest requset = app.Request;
// 若返回对象是Json对象,则进行处理
if (response.ContentType.ToLowerInvariant().Contains(RESPONSE_CONTENT_TYPE))
{
response.Filter = new JSonpilizingResponseFilter(response, requset);
}
}
#endregion
}
public class JSonpilizingResponseFilter : Stream
{
private Stream _Sink;
private long _Position;
private string _Key;
private HttpResponse _Response;
private HttpRequest _Requset;
public override bool CanRead { get { return false; } }
public override bool CanSeek { get { return false; } }
public override bool CanWrite { get { return true; } }
public override void Flush() { this._Sink.Flush(); }
public override long Length { get { return 0; } }
public override long Position { get { return _Position; } set { _Position = value; } }
public JSonpilizingResponseFilter(HttpResponse response, HttpRequest request)
{
this._Requset = request;
this._Response = response;
this._Sink = response.Filter;
this._Key = request[JSonpilizeModule.REQUEST_QUERY_KEY];
}
public override int Read(byte[] buffer, int offset, int count)
{
return _Sink.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
return 0;
}
public override void Close()
{
this._Sink.Close();
}
public override void SetLength(long value)
{
this._Sink.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
byte[] pageBuffer;
string oringalValue = Encoding.Default.GetString(buffer);
//首先判断有没有系统错误
if (HttpContext.Current.Error == null)
{
_Response.ClearHeaders();
// 若Request["callback"]没有值则是JSonp调用(为保持和4.0实现一致,当非Jsonp调用时,会封装一个d:)
if (string.IsNullOrEmpty(this._Key) == true)
{
// 返回类型为application/json
_Response.ContentType = "application/json; charset=utf-8";
oringalValue = string.Format("{0}({1})", this._Key, oringalValue);
}
// 否则是Jsonp调用(保持和4.0实现一致,不封装d:)
else
{
// 返回类型为application/x-javascript
_Response.ContentType = "application/x-javascript; charset=utf-8";
Regex regex = new Regex(@"{""d"":(?<OringalValue>{.+})}");
Match match = regex.Match(oringalValue);
if (match.Success)
{
oringalValue = string.Format("{0}({1})", this._Key, match.Groups["OringalValue"].Value);
}
}
pageBuffer = Encoding.Default.GetBytes(oringalValue);
_Sink.Write(pageBuffer, 0, pageBuffer.Length);
_Response.AppendHeader("Content-Length", pageBuffer.Length.ToString());
}
}
} }
然后修改Web.config文件
...
<httpModules>
...
<add name="JSONPModule" type="NIntegrate.Web.JSONPModule, NIntegrate, Version=X.X.X.X, Culture=neutral, PublicKeyToken=e2b9e2165dbdd5e6"/>
</httpModules>
</system.web>
...
<system.webServer>
...
<modules>
...
<add name="JSONPModule" preCondition="managedHandler" type="NIntegrate.Web.JSONPModule, NIntegrate, Version=X.X.X.X, Culture=neutral, PublicKeyToken=e2b9e2165dbdd5e6"/>
</modules>
...
</system.webServer>
参见:http://blog.csdn.net/teddyma/archive/2010/03/06/5353076.aspx
这是在.NET 3.5SP1下通过的办法,不过.NET4已经提供了内置的实现了。
解决方案四:MSDN上的例子中的JSONPBehaviorAtrribute
参见:http://www.developmentalmadness.com/2009/07/building-single-sign-on-provider-using_06.html