Introduction
According to the "Same origin policy", if the domain name, protocol and port number is different from the source of data retrieval then it is not permitted.
However html <script src=""> is allowed to do content retrieval from the foreign sources. This is the idea behind the JSONP (JSON with padding) origin.
JSONP is widely used to request the data from a server which resides in a different domain. The process by which JSONP does this is very simple.
It dynamically adds script tags to the current document and results will be returned using a callback function.
.Net 4.0 adds support for JSONP in WCF.
WCF support for JSONP
You can make a WCF service ready for cross domain scripting (JSONP) by using
WebHttpBinding
and setting crossDomainScriptAccessEnabled
to "true". But there is one more way too. That is by using standard end points.
WebScriptEndpoint
is a standard end point which adds
the WebScriptEnabling
behavior automatically by using fixed WebHttpBinding
.
Building the service
To demo, I will be building here a WCF service which returns an array of string to load the options for html select control in the UI.
As per the below code, class OptionsService which is a service contract exposes a method called
GetOptions
. Method GetOptions
takes in typeId as input parameter and returns
ResponseOption
object which is a data contract. Attribute [WebGet(ResponseFormat = WebMessageFormat.Json)]
is mainly added for the GetOpions
to make it accessible by REST programming models and also to indicate response data format is JSON.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.ServiceModel.Activation;
namespace OptionsBuilderService
{
[DataContract]
public class ResponseOption
{
[DataMember]
public string[] Options;
}
[ServiceContract(Namespace = "OptionsBuilderService")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class OptionsService
{
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Json)]
public ResponseOption GetOptions(int typeId)
{
List<string> options = new List<string>();
ResponseOption rOptions = new ResponseOption();
switch (typeId)
{
case 1:
options.Add("Hero Honda");
options.Add("Bajaj");
options.Add("Harley Dvson");
options.Add("TVS");
break;
case 2:
options.Add("Toyota");
options.Add("TATA");
options.Add("General Motors");
options.Add("Audi");
options.Add("BMW");
break;
}
rOptions.Options = options.ToArray();
return rOptions;
}
}
}
Below is configuration setting needed to make the above service ready for cross domain scripting (JSONP). I have used
webScriptEndpoint
and set crossDomainScriptAccessEnabled
to true.
I have used serviceActivations
tags as I have not added .svc files to my WCF project. If you have .svc file and if you are using it then in mark up
for "@SeviceHost
" directive set "factory
" attribute to "System.ServiceModel.Activation.WebScriptServiceHostFactory
".
WebScriptServiceHostFactory
adds an ASP.NET AJAX endpoint to the service without requiring configuration. The ASP.NET AJAX endpoint created
by this factory is configured with the WebHttpBinding
and the WebScriptEnablingBehavior
, with all the default settings.
="1.0"
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
<authentication mode="None" />
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true">
<serviceActivations>
<add relativeAddress="OptionsService.svc" service="OptionsBuilderService.OptionsService"
factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" />
</serviceActivations>
</serviceHostingEnvironment>
<standardEndpoints>
<webScriptEndpoint>
<standardEndpoint name="" crossDomainScriptAccessEnabled="true"/>
</webScriptEndpoint>
</standardEndpoints>
</system.serviceModel>
</configuration>
Building the client
The client here is a simple ASP.NET application. It has a page called Default.aspx which uses Master.master.
Default.aspx has two HTML Select controls
SelectVehicleTyps
and SelectVehicles
. Options for
SelectVehicles
are coming from the service call.
Below code dynamically adds script block after the page is rendered. And this script block makes call to the WCF REST service and gets the options to load the
SelectVehicles
control.
When the WCF REST service responds callback function is invoked with the response data and data is used for the further usage. The script tag injection and the callback
function invoke are automatically handled by the ASP.NET AJAX ScriptManager
. Usage here is similar to the
ASP.NET AJAX proxies but with addition one line to enable the JSONP.
Go through the makeMyServiceCall()
function to see the JSONP enabling.
<script type="text/javascript">
function makeMyServiceCall() {
var sel = document.getElementById("SelectVehicleTyps");
var val = sel.options[sel.selectedIndex].value;
var proxy = new OptionsBuilderService.OptionsService();
proxy.set_enableJsonp(true);
proxy.GetOptions(parseInt(val), onSuccess, onFail, null);
}
function onSuccess(result) {
var select = document.getElementById("SelectVehicles");
select.options.length = 0;
for (var i = 0; i < result.Options.length; i++) {
var d = result.Options[i];
select.options.add(new Option(d, i))
}
}
function onFail() {
alert("Error happend!")
}
</script>
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="http://localhost:7838/OptionsService.svc" />
</Services>
</asp:ScriptManager>
<br />
Select the vehicle type:
<select id="SelectVehicleTyps" onchange="makeMyServiceCall();"> <%-- Make the service call --%>
<option value="1">Motor Bike</option>
<option value="2">Car</option>
</select>
<select id="SelectVehicles">
<option></option>
</select>
<br />
<script type="text/javascript" defer="defer">makeMyServiceCall();</script>
<%-- Make the service call --%>
When the selected option of the SelectVehicleTyps is changed new options are loaded to the SelectVehicles.
Points of Interest
* Validating the response.
* Setting X-Content-Type-Options: nosniff .
History
V1.0