Enabling Cross-Origin Resource Sharing (CORS) on a Service

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!

Hannes DuPreez
Hannes DuPreez
Ockert J. du Preez is a passionate coder and always willing to learn. He has written hundreds of developer articles over the years detailing his programming quests and adventures. He has written the following books: Visual Studio 2019 In-Depth (BpB Publications) JavaScript for Gurus (BpB Publications) He was the Technical Editor for Professional C++, 5th Edition (Wiley) He was a Microsoft Most Valuable Professional for .NET (2008–2017).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read