使WCF支持JSONP调用

这两天在研究如何通过JSONP去实现跨域调用WCF服务,找到了以下几种解决方式:

解决方案一: 

在服务器端直接通过调用DataContractJsonSerializer或JavaScriptSerializer对数据进行JSON序列化后返回Stream或直接调用Response.Write方法把序列化后的数据返回给业务系统: 

以下的JsonHelper类实现对于JSON调用和JSONP调用进行分别返回:若是JSONP调用则把JSON数据再根据传入的jsoncallback参数封装。

    public static class JSonHelper

    {

        /// <summary>
        
/// 输出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, 代码如下:     

    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)] 

    [ServiceContract(Namespace ="")]

    public class JSonpService
    {
        [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的使用例子:

代码
<system.serviceModel>
    
<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中的参数名字:

代码
    [ServiceContract(Namespace = "")]
    [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;
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文件

Web.Config
<system.web>
   ...
   
<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

该方案利用的是MSDN的一个例子中做的扩展
参见:http://www.developmentalmadness.com/2009/07/building-single-sign-on-provider-using_06.html
原文地址:https://www.cnblogs.com/Xrinehart/p/1741588.html