Cross Domain RESTful Service in WCF
I had to write a REST API last week, which needed to be accessible from webpages hosted in multiple domains. I thought of writing down one of the technique I followed from the web standard. So, I am going to explain how to create a WCF service, make it RESTful and how to structure it in a way such that you can use/call it from webpage hosted in any domain. Most common examples of this usage is google map, bing map APIs.
Common web standard to make sync/async calls to the service is to use XMLHttpRequest object directly and get the result back in the response text; then use the result accordingly in the page logic. You may use JSON formated request/response in the service to make it easy to use in the web page; specially because almost all the browser has the native support of JSON in JavaScript.
A limitation associated with XMLHttpRequest object is that it will not allow you to access a webpage/service from a different domain other than the host of currently served page. Which means, let's say you have a page http://www.mydomain.com/index.html, and if you try to request a page from www.google.com domain using XMLHttpRequest, you will get a security error because it is against the browser's security model. [Note: With the introduction of latest webstandards like HTML5, you have other ways to achieve the cross domain data exchange. For example window.postMassage, XDomainRequest in IE8+, AccessContol headers in non IE browsers etc. So the future of cross domain access would be very straight forward method using one or a combination of above mentioned technologies. Most of the latest version of browsers already supports this. But for my usage, since majority of my users are still in IE7, I will not be able to use one of the above mentioned techniques]
Approach
To overcome this security model, the approach is as follows.
1. Define a javascript function to handle the response - a callback method.
<script type="text/javascript">
function myCallback(data) {
// Process the data
}
</script>
2. Create a script tag with source pointing to the service with required parameters as query string. Pass the callback method name also as a query string parameter.
<script type="text/javascript" src="http://www.anydomain.com/myservice/method?
callabck=myCallback
¶m1=abc¶m2=efg" ></script>
3. In your service code, wrap the response as a method call to the callback and set the content type as "text/javascript" for the response stream.
e.g. final response as string would be like "myCallback('result string')" . So, when the request is completed, automatically your callback method will get invoked with proper response data.
If you are using standard javascript libraries like jQuery, you don't need to define the callback and javascript tags explicitly, instead you can use the standard APIs like $.ajax(...) to invoke the service directly; all the steps explained above will be wrapped by the APIs internally for you and you can simply use this as a method call.
E.g.
$.ajax({
type: 'GET',
url: "http://mydomain.com/service1.svc/locate?callback=?",
data: null,
success: animate,
dataType: 'json',
beforeSend: function(xhr){
//before send process
}
});
RESTful service in WCF
Now lets introduce a simple service and make in RESTful.
Step 1.Open VS or WebDeveloper express and create a new WCF Service application as follows.
Step 2. By default, this service is not accessible through a get method from browser. So, make following changes in the operation contract.
[OperationContract]
[WebGet(UriTemplate="GetData?callback={callback}")]
Stream GetData(string callback);
We will need the callback while constructing the response, so that we can wrap the response around the callback method. Note two things here, first, the WebGet attribute with a UriTemplate, this enable us to use the service method like a web url and the value of querystring parameter "callback" will be available as a parameter to the GetData method call. And second, the return type of the method is changed to a "Stream"; this is to send the response content type as "text/javascript" when this method invoked from a source property of the "<script />" tag.
Step 3. The method should be accessible through the url : http://localhost:59636/Service1.svc/GetData?callback=myCallbackMethod when it is ready. Now you need to make following changes in your web.config file to define service endpoints to specify accessible through a WebGet.
<system.serviceModel>
<services>
<service behaviorConfiguration="s1" name="WcfService1.Service1">
<endpoint behaviorConfiguration="wh" binding="webHttpBinding"
contract="WcfService1.IService1">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="wh">
<webHttp/>
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="s1">
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
Step 3. Now we need to change the GetData implementation to return the content type as "text/javascript" and to wrap the respoonse as a callback method call.
try
{
// Do some operation and get the result...
string result = "{name:'Hari', address:'1111 Main, Kandakkai'}";
string responseText = result;
if (!String.IsNullOrEmpty(callback))
{
// Wrap the response with callback method
responseText = String.Format("{0}({1})", callback, result);
}
// Set response headers, and content type = "text/javasctipt"
OutgoingWebResponseContext responseContext = WebOperationContext.Current.OutgoingResponse;
responseContext.Headers.Add(HttpResponseHeader.CacheControl, "public");
responseContext.ContentType = "text/javascript; charset=UTF-8";
responseContext.LastModified = DateTime.Now;
responseContext.StatusCode = System.Net.HttpStatusCode.OK;
//Return the stream
return new MemoryStream(ASCIIEncoding.Default.GetBytes(responseText));
}
catch
{
string response = String.Empty;
if (!String.IsNullOrEmpty(callback))
{
response = String.Format("{0}({{}})", callback);
}
return new MemoryStream(ASCIIEncoding.Default.GetBytes(response));
}
Now, access your service using following url : http://localhost:59636/Service1.svc/GetData?callback=myCallbackMethod, you will see the response getting called in your javascript function myCallbackMethod. You may use the url - "http://localhost:59636/Service1.svc/GetData?callback=" in jQuery's $.ajax method to invoke the service directly.
You may contact me if you need more info or to get the source code.
Comments
Post a Comment