Introduction to CORS
In my previous article, “Hosting a WCF Service Inside a Windows Service,” I demonstrated how to host a WCF Service inside a Windows Service. Today, I will take it a step further and demonstrate how to enable CORS on a service and how to communicate properly through JSON.
What Is WCF?
Windows Communication Foundation enables you to build service-oriented applications. Service-oriented applications mean that you have a back-end and a front-end communicating successfully with one another. The back-end is the physical service that reads messages, or data from a source which can be another WCF service, a Windows Service, a web site, or even a mobile phone. The messages between the front-end and back-end are sent asynchronously. Data that can be sent from and to the service can be in XML, Raw format, in other words, binary data, and JSON. Here is more information on WCF.
CORS
Cross-origin resource sharing (CORS) allows resources to be requested from a domain which is outside the domain from which the resource comes from. Here is more information regarding CORS.
JSON and JSONP
If you have heard about JSON, you will know that it is an easy way to communicate through the Document Object Model in JavaScript. JSONP (or JSON with Padding), on the other hand, is a technique to overcome the cross-domain restrictions imposed by browsers. You can find more on JSONP here.
If you haven’t read my article “Hosting a WCF Service inside a Windows Service,” I suggest you do this now, because this article builds on that and you will make use of that code and improve on it.
Cross Domain Files
Open Notepad and type the following code:
<?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <site-control permitted-cross-domain-policies="all"/> <allow-access-from domain="*" secure="false"/> <allow-http-request-headers-from domain="*" headers="*"/> </cross-domain-policy>
Save this file as crossdomain.xml
Whilst in Notepad, add the following code and save it as clientaccesspolicy.xml:
<?xml version="1.0" encoding="utf-8"?> <access-policy> <cross-domain-access> <policy> <allow-from http-request-headers="*"> <domain uri="*"/> </allow-from> <grant-to> <resource path="/" include-subpaths="true"/> </grant-to> </policy> </cross-domain-access> </access-policy>
More information on clientaccesspolicy.xml can be found here.
It is very important that these two files must accompany your service, irrespective of what type of service it is and where the service will be running from, and be located at your service’s root folder.
Add a new Service project to your existing project and name it CrossDomainService, for example.
Open CrossDomainService.svc.vb and add the following code into it:
Imports System.IO Imports ystem.Xml Imports System.ServiceModel.Channels Namespace TestService Public Class CrossDomainService Implements ICrossDomainService Public Function ProvidePolicyFile() As _ System.ServiceModel.Channels.Message _ Implements ICrossDomainService.ProvidePolicyFile Dim filestream As FileStream = _ File.Open("ClientAccessPolicy.xml", FileMode.Open) Dim reader As XmlReader = XmlReader.Create(filestream) Dim result As System.ServiceModel.Channels.Message = _ Message.CreateMessage(MessageVersion.None, "", reader) Return result End Function End Class End Namespace
This opens and reads the clientaccespolicy file to ensure that the hosting service has the correct permissions to allow for cross-domain calls.
Add its interface:
Imports System.ServiceModel.Web Imports System.ServiceModel.Channels Namespace TestService <ServiceContract()> _ Public Interface ICrossDomainService <OperationContract(), WebGet(UriTemplate:= _ "/ClientAccessPolicy.xml")> _ Function ProvidePolicyFile() As Message End Interface End Namespace
This determines where the file is located to be read from via the UriTemplate setting. More information on the UriTemplate setting can be found here.
Edit the OnStart event of TestService to include the operations for the CrossDomain service:
Protected Overloads Overrides Sub OnStart(ByVal args As String()) Try If serviceHost IsNot Nothing Then serviceHost.Close() End If If CrossDomainServiceHost IsNot Nothing Then CrossDomainServiceHost.Close() End If serviceHost = New WebServiceHost(GetType(TestService), _ New Uri("http://localhost/TestService")) serviceHost.AddServiceEndpoint(GetType(ITestService), _ New WebHttpBinding(), "http://localhost/TestService") CrossDomainServiceHost = New WebServiceHost(GetType(CrossDomainService), _ New Uri("http://localhost/CrossDomainService")) CrossDomainServiceHost.AddServiceEndpoint(GetType(ICrossDomainService), _ New WebHttpBinding(), "http://localhost/CrossDomainService") serviceHost.Open() CrossDomainServiceHost.Open() Catch ex As Exception System.Diagnostics.EventLog.WriteEntry("Test Service", _ ex.Message, EventLogEntryType.Error) WebOperationContext.Current.OutgoingResponse.StatusCode = 501 serviceHost.Close() CrossDomainServiceHost.Close() End End Try End Sub
Edit the OnStop method to stop the CrossDomain service properly:
' Stop the Windows service. Protected Overloads Overrides Sub OnStop() If serviceHost IsNot Nothing Then serviceHost.Close() CrossDomainServiceHost.Close() serviceHost = Nothing CrossDomainServiceHost = Nothing End If End Sub End Class
Edit your GetCustomers Interface method as well as the Service method to include some parameters.
Interface:
<OperationContract()> _ <WebGet(BodyStyle:=WebMessageBodyStyle.Bare, _ ResponseFormat:=WebMessageFormat.Json, RequestFormat:=WebMessageFormat.Json, UriTemplate:= _ "GetCustomers?strCustomers={strCustomers} _ &callback={CustomerCallback}")> _ Function GetCustomers(ByVal strCustomers As String, _ ByVal CustomerCallback As String) As Stream
Here, you will notice that I have included an UriTemplate as well as two parameters. The one parameter will host the data that will be sent over to a web page, for example. The other parameter is a callback. You will see how these two parameters get used from a Web page a bit later.
Add the service method:
Function GetCustomers(ByVal strCustomers As String, _ ByVal CustomerCallback As String) As Stream _ Implements ITestService.GetCustomers Try Dim Cust As List(Of Customers) = New List(Of Customers) ' Have a DataSet to read the incoming data For i = 0 To CustomerTable.Rows.Count - 1 CustomerRow = CustomerTable.Rows(i) Dim CC As New Customers CC.Name = CustomerRow.Item("Name") CC.Email = CustomerRow.Item("Email") Cust.Add(CC) Next i ' Serialize the results as JSON Dim serializer As DataContractJsonSerializer = New _ DataContractJsonSerializer(Cust.GetType()) Dim Stream As MemoryStream = New MemoryStream serializer.WriteObject(Stream, Cust) ' Return the results serialized as JSON Dim strJson As String = _ Encoding.Default.GetString(Stream.ToArray()) Dim jsCode As String = CustomerCallback & _ "(" & strJson & ")" WebOperationContext.Current.OutgoingResponse.ContentType = _ "application/javascript" Return New MemoryStream(Encoding.UTF8.GetBytes(jsCode)) strCustomers = strJson WebOperationContext.Current.OutgoingResponse.StatusCode = 200 WebOperationContext.Current.OutgoingResponse.StatusDescription _ = "OK" Catch ex As Exception System.Diagnostics.EventLog.WriteEntry("Test Service", _ ex.Message, EventLogEntryType.Error) WebOperationContext.Current.OutgoingResponse.StatusCode = 501 WebOperationContext.Current.OutgoingResponse.StatusDescription _ = ex.Message End Try End Function
This looks worse than what it actually is. Before I explain, add the Customers DataContract by adding a new class and then entering this:
<DataContract()> _ Public Class Customers Dim CN As String Dim E As String <DataMember()> _ Public Property Name() As String Get Return CN End Get Set(value As String) CN = value End Set End Property <DataMember()> _ Public Property Email() As String Get Return E End Get Set(ByVal value As String) E = value End Set End Property End Class
The DataContract assists in transporting the correct information to and from the service.
The GetCustomers method creates a DataSet and then a DataTable that are added to a dynamic list. This list will be exported to the Web page that has called this method and be able to use this data. There is one exception, though. This data can be exported as XML or JSON. Seeing the fact that JSON is a bit more complicated, that is why I decided on this article in the first place.
If you look further into the GetCustomers function, the method creates a DataContractJsonSerializer and a MemoryStream object to help transport the data in the appropriate format. Here is more information on DataContractJsonSerializer and MemoryStream.
With the following code, you can add a simple JQuery function that makes use of this information:
var url = 'http://URL/TestMobileService/GetStock' var customStore = new DevExpress.data.CustomStore({ load: function () { return $.ajax({ type: 'GET', url: url, data: {}, cache: true, dataType: "jsonp", success: function (result) { console.log(JSON.stringify(result)); }, error: function (xhr, status, error) { console.log(JSON.stringify(xhr)); console.log(JSON.stringify(status)); console.log(JSON.stringify(error)); } }); }, totalCount: function (loadOptions) { return 0; }, });
Conclusion
As promised, working with CORS is essentially an easy way to communicate with JSON. Until next time, cheers!