Cross-Domain AJAX Enabled WCF Service

Background

For the basic of how to create an AJAX enabled WCF service, please refer to MSDN: http://msdn.microsoft.com/en-us/library/bb924552.aspx.

For the basic of JSONP, please refer to: http://en.wikipedia.org/wiki/JSON#JSONP.

This article introduce how to make AJAX enabled WCF service support cross-domain request.

In the WCF & WF samples provided by Microsoft, there is already a “custom binding extension” implementation for JSONP support. But there is some limitation, the biggest limitation is it could not support both JSON & JSONP protocol for one AJAX enabled WCF service URL, and it requires each endpoint you want to support JSONP protocol be configured with the specific custom JSONP binding extension which increased configuration complexity & cost.

Here I introduce a more general “custom Http Module” implementation for cross-domain calling AJAX enabled WCF services.

At the beginning, firstly, please realize WCF does not support custom Http Modules by default, which means, by default, a WCF service request, even a webHttpBinding AJAX enabled WCF service request, doesn’t go through any custom Http Modules. To enable it, you need to set the aspNetCompatibilityEnabled property of serviceHostingEnvironment element to true like below:

  < system.serviceModel >
   
< serviceHostingEnvironment  aspNetCompatibilityEnabled ="true"   />
   ...
</ system.serviceModel >

And to enable a WCF service support custom Http Module, you also need to mark the AspNetCompatibilityRequirementsAttribute on the service contract like below:

[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode
= AspNetCompatibilityRequirementsMode.Allowed)]
public   class  TestAjaxClientService
{
       ...
}

JSONPModule

Since with the aspNetCompatibilityEnabled property set to true, an Http WCF service request goes through custom Http Modules, it is possible to write a custom Http Module to automatically convert any JSON response to JSONP response. And actually, since it will be so general, it could not only give JSONP support to WCF, but also give JSONP support to any existing JSON response services, such as Web services and Http handler services. The JSONPModule class below is part of NIntegrate framework (BSD license), but it has no dependency. So you could use it freely and independently.

JSONPModule.cs
using  System;
using  System.Collections.Generic;
using  System.IO;
using  System.Linq;
using  System.Text;
using  System.Web;

namespace  NIntegrate.Web
{
    
public   class  JSONPModule : IHttpModule
    {
        
private   const   string  JSON_CONTENT_TYPE  =   " application/json " ;
        
private   const   string  JS_CONTENT_TYPE  =   " text/javascript " ;

        
#region  IHttpModule Members

        
public   void  Dispose()
        {
        }

        
public   void  Init(HttpApplication app)
        {
            app.ReleaseRequestState 
+=  OnReleaseRequestState;
        }

        
#endregion

        
public   void  OnReleaseRequestState( object  sender, EventArgs e)
        {
            HttpApplication app 
=  (HttpApplication)sender;
            HttpResponse response 
=  app.Response;
            
if  (response.ContentType.ToLowerInvariant().Contains(JSON_CONTENT_TYPE)
                
&&   ! string .IsNullOrEmpty(app.Request.Params[ " jsoncallback " ]))
            {
                response.ContentType 
=  JS_CONTENT_TYPE;
                response.Filter 
=   new  JsonResponseFilter(response.Filter);
            }
        }
    }

    
public   class  JsonResponseFilter : Stream
    {
        
private   readonly  Stream _responseStream;
        
private   long  _position;

        
private   bool  _isContinueBuffer;

        
public  JsonResponseFilter(Stream responseStream)
        {
            _responseStream 
=  responseStream;
        }

        
public   override   bool  CanRead {  get  {  return   true ; } }

        
public   override   bool  CanSeek {  get  {  return   true ; } }

        
public   override   bool  CanWrite {  get  {  return   true ; } }

        
public   override   long  Length {  get  {  return   0 ; } }

        
public   override   long  Position {  get  {  return  _position; }  set  { _position  =  value; } }

        
public   override   void  Write( byte [] buffer,  int  offset,  int  count)
        {
            
string  strBuffer  =  Encoding.UTF8.GetString(buffer, offset, count);
            strBuffer 
=  AppendJsonpCallback(strBuffer, HttpContext.Current.Request);
            
byte [] data  =  Encoding.UTF8.GetBytes(strBuffer);
            _responseStream.Write(data, 
0 , data.Length);
        }

        
private   string  AppendJsonpCallback( string  strBuffer, HttpRequest request)
        {
            
string  prefix  =   string .Empty;

            
if  ( ! _isContinueBuffer)
            {
                prefix 
=  request.Params[ " jsoncallback " +   " ( " ;
                _isContinueBuffer 
=   true ;
            }

            
return  prefix  +  strBuffer;
        }

        
public   override   void  Close()
        {
            
string  suffix  =   " ); " ;
            
byte [] data  =  Encoding.UTF8.GetBytes(suffix);
            _responseStream.Write(data, 
0 , data.Length);

            _responseStream.Close();
        }

        
public   override   void  Flush()
        {
            _responseStream.Flush();
        }

        
public   override   long  Seek( long  offset, SeekOrigin origin)
        {
            
return  _responseStream.Seek(offset, origin);
        }

        
public   override   void  SetLength( long  length)
        {
            _responseStream.SetLength(length);
        }

        
public   override   int  Read( byte [] buffer,  int  offset,  int  count)
        {
            
return  _responseStream.Read(buffer, offset, count);
        }
    }
}

Sample

The sample in this chapter is a unit test of NIntegrate for the JSONPModule. Imagine we have an AJAX enabled WCF service – TestAjaxClientService.svc, which could be called by jQuery like below:

       jQuery.getJSON( ' http://localhost:2166/TestAjaxClientService.svc/Hello ' function (data) { alert( ' inner-domain called by jQuery through normal JSON protocol:  '   +  data.d); });

With the JSONPModule configured like below:

  < 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 >

We can cross-domain call the TestAjaxClientService.svc like:

       jQuery.getJSON( ' http://127.0.0.1:2166/TestAjaxClientService.svc/Hello?jsoncallback=? ' function (data) { alert( ' cross-domain called by jQuery through JSONP protocol (no cache):  '   +  data.d); });
      
function  jsonpCallback(data) {
           alert(
' cross-domain called by jQuery through JSONP protocol (cached):  '   +  data.d);
       }
       jQuery.ajaxSetup({ cache: 
true  });
       jQuery.getScript(
' http://127.0.0.1:2166/TestAjaxClientService.svc/Hello?jsoncallback=jsonpCallback ' );
       jQuery.ajaxSetup({ cache: 
false  });

你可能感兴趣的:(service)