Cross Origin Resource Sharing for c# WCF Restful web service hosted as Windows service

12,693

Solution 1

Finally found a solution to my queries.

Its all here. Supporting Cross Origin Resource

Nice step by step explanation. I guess I could have never figured this out on my own.

CODE:

Create 2 classes as follows:

  1. MessageInspector implementing IDispatchMessageInspector.
  2. BehaviorAttribute implementing Attribute, IEndpointBehavior and IOperationBehavior.

With the following details:

//MessageInspector Class
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;
namespace myCorsService
{
  public class MessageInspector  : IDispatchMessageInspector
  {
    private ServiceEndpoint _serviceEndpoint;

    public MessageInspector(ServiceEndpoint serviceEndpoint)
    {
      _serviceEndpoint = serviceEndpoint;
    }

    /// <summary>
    /// Called when an inbound message been received
    /// </summary>
    /// <param name="request">The request message.</param>
    /// <param name="channel">The incoming channel.</param>
    /// <param name="instanceContext">The current service instance.</param>
    /// <returns>
    /// The object used to correlate stateMsg. 
    /// This object is passed back in the method.
    /// </returns>
    public object AfterReceiveRequest(ref Message request, 
                                          IClientChannel channel, 
                                          InstanceContext instanceContext)
    {
      StateMessage stateMsg = null;
      HttpRequestMessageProperty requestProperty = null;
      if (request.Properties.ContainsKey(HttpRequestMessageProperty.Name))
      {
        requestProperty = request.Properties[HttpRequestMessageProperty.Name]
                          as HttpRequestMessageProperty;
      }

      if (requestProperty != null)
      {
        var origin = requestProperty.Headers["Origin"];
        if (!string.IsNullOrEmpty(origin))
        {
          stateMsg = new StateMessage();
          // if a cors options request (preflight) is detected, 
          // we create our own reply message and don't invoke any 
          // operation at all.
          if (requestProperty.Method == "OPTIONS")
          {
            stateMsg.Message = Message.CreateMessage(request.Version, null);
          }
          request.Properties.Add("CrossOriginResourceSharingState", stateMsg);
        }
      }

      return stateMsg;
    }

    /// <summary>
    /// Called after the operation has returned but before the reply message
    /// is sent.
    /// </summary>
    /// <param name="reply">The reply message. This value is null if the 
    /// operation is one way.</param>
    /// <param name="correlationState">The correlation object returned from
    ///  the method.</param>
    public void BeforeSendReply(ref  Message reply, object correlationState)
    {
      var stateMsg = correlationState as StateMessage;

      if (stateMsg != null)
      {
        if (stateMsg.Message != null)
        {
          reply = stateMsg.Message;
        }

        HttpResponseMessageProperty responseProperty = null;

        if (reply.Properties.ContainsKey(HttpResponseMessageProperty.Name))
        {
          responseProperty = reply.Properties[HttpResponseMessageProperty.Name]
                             as HttpResponseMessageProperty;
        }

        if (responseProperty == null)
        {
          responseProperty = new HttpResponseMessageProperty();
          reply.Properties.Add(HttpResponseMessageProperty.Name,
                               responseProperty);
        }

        // Access-Control-Allow-Origin should be added for all cors responses
        responseProperty.Headers.Set("Access-Control-Allow-Origin", "*");

        if (stateMsg.Message != null)
        {
          // the following headers should only be added for OPTIONS requests
          responseProperty.Headers.Set("Access-Control-Allow-Methods",
                                       "POST, OPTIONS, GET");
          responseProperty.Headers.Set("Access-Control-Allow-Headers",
                    "Content-Type, Accept, Authorization, x-requested-with");
        }
      }
    }
  }

  class StateMessage
  {
    public Message Message;
  }
}

//BehaviorAttribute Class
using System;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace OpenBetRetail.NFCReaderService
{
  public class BehaviorAttribute : Attribute, IEndpointBehavior,
                                 IOperationBehavior
  {        
    public void Validate(ServiceEndpoint endpoint) { }

    public void AddBindingParameters(ServiceEndpoint endpoint,
                             BindingParameterCollection bindingParameters) { }

    /// <summary>
    /// This service modify or extend the service across an endpoint.
    /// </summary>
    /// <param name="endpoint">The endpoint that exposes the contract.</param>
    /// <param name="endpointDispatcher">The endpoint dispatcher to be
    /// modified or extended.</param>
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, 
                                      EndpointDispatcher endpointDispatcher)
    {
      // add inspector which detects cross origin requests
      endpointDispatcher.DispatchRuntime.MessageInspectors.Add(
                                             new MessageInspector(endpoint));
    }

   public void ApplyClientBehavior(ServiceEndpoint endpoint,
                                   ClientRuntime clientRuntime) { }

   public void Validate(OperationDescription operationDescription) { }

   public void ApplyDispatchBehavior(OperationDescription operationDescription,
                                     DispatchOperation dispatchOperation) { }

   public void ApplyClientBehavior(OperationDescription operationDescription,
                                   ClientOperation clientOperation) { }

   public void AddBindingParameters(OperationDescription operationDescription,
                             BindingParameterCollection bindingParameters) { }

  }
}

After this all you need to do is add this message inspector to service end point behavior.

ServiceHost host = new ServiceHost(typeof(myService), _baseAddress);
foreach (ServiceEndpoint EP in host.Description.Endpoints)
            EP.Behaviors.Add(new BehaviorAttribute());

Thanks guys for your help.

Solution 2

I believe that the closest thing to Application_BeginRequest in the WCF world are Message Inspectors:

A message inspector is an extensibility object that can be used in the service model's client runtime and dispatch runtime programmatically or through configuration and that can inspect and alter messages after they are received or before they are sent.

In order to use custom message inspectors you'll have to:

  1. Create a class that implements the IDispatchMessageInspector interface (your custom inspector)
  2. Add the custom inspector to your service's DispatchRuntime.MessageInspectors collection

Here you can find more info and some sample code on how to do this.

Share:
12,693
Newbee
Author by

Newbee

Updated on July 24, 2022

Comments

  • Newbee
    Newbee almost 2 years

    I have a WCF Restful service which I am hosting as Windows service. I want to add cross domain support to my service. However, i can do this easily when I use global.asax file. But I want to host my service as a windows service.

    i have created a project which host my service as windows service. Now the problem I am facing is, I am not able to add cross domain support now. I tried all possible solutions I could find through app.config file, but none works. I have tried solutions on these links:

    dotnet tricks

    enable-cors.org

    I tried setting the header in the code using the following function by calling it in each service contract method.

    private static void SetResponseHeader()
    {
    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
    WebOperationContext.Current.OutgoingResponse.Headers.Add("Cache-Control", "no-cache, no-store");
    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Request-Methods", "GET, POST, PUT, DELETE, OPTIONS");
    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Accept");
    }
    

    Interface:

    namespace ReaderService
    {
    [ServiceContract]
    public interface INFCReader
    {
        [OperationContract]
        [WebInvoke(UriTemplate = "GetLogin", Method = "POST")]
        GetLoginResults GetLogin(DisplayRequest dispRequest);
    }
    

    Here DisplayRequest is a class.

    Please help guys. Let me know if anybody want to have look at any other code.

    Thanks a lot.

    EDIT:::::::

    Thanks a lot Thomas for your reply. I created a class MessageInspector which implement IDispactchMessageInspector. I have following code in MessageInspector class.

    public class MessageInspector : IDispatchMessageInspector
    {
        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
            if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
            {
                HttpContext.Current.Response.AddHeader("Cache-Control", "no-cache");
                HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
                HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
                HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
                HttpContext.Current.Response.End();
            }
            return null;
        }
    }
    

    The error that I am getting now is -- 'Object reference not set to an instance of an object.' The error is at this line of the above code

    HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
    

    All I want to do is add CORS support to my web service. Please let me know if I am doing it correctly. OR is there any other way to do the same.

  • Newbee
    Newbee about 11 years
    Thanks a lot Thomas. I implemented Message Inspector. I am getting another error in my service. Please see the edit. Thanks.
  • Ren
    Ren about 11 years
    Note that link-only answers are discouraged, references tend to get stale over time. Please consider adding a stand-alone synopsis here, keeping the link as a reference.
  • Thomas C. G. de Vilhena
    Thomas C. G. de Vilhena about 11 years
    You're welcome. I see that you've already found a solution for your new problem. That was happening because HttpContext is not available from WCF services unless you enable ASP.NET compatibility mode, but I guess that would only work when the WCF service is hosted on IIS.
  • Newbee
    Newbee about 11 years
    Thanks Ren. Will post the actual solution. :)
  • Newbee
    Newbee about 11 years
    Ohh I see. Thanks Thomas for your help. You showed me the correct path. :)
  • Martin Nuc
    Martin Nuc over 10 years
    This really helped me a lot! Thank you very much.
  • Newbee
    Newbee over 10 years
    Glad it helped you. :)
  • HidekiAI
    HidekiAI over 9 years
    A more in-depth look at 'IDispatchMessageInspector' can be found at MSDN blogs Implementing-CORS-support (or implementing-cors-support-in-wcf)
  • Vespucci75fr
    Vespucci75fr about 8 years
    When you add the endpoint programatically, run the foreach loop of the behaviour, after adding your ServiceEndPoint ;-)