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
});