none
Support cross-domain requests (specifically multiple methods in WebInvoke) in Rest WCF

    Question

  • Hi,

    I'm currently using WCF to develop a RESTful service. There is no guarantee the WCF services will be on the same server than the WCF services consumers.
    Which leads to Cross-Domain XHR problem on Firefox and Chrome (IE doesn't seem to implement security regarding cross domain XHR calls yet, to my delight).

    First and foremost, despite what this thread: http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/d1a584c1-63aa-4cb9-91d9-142c22e57e98/ says, it is possible to have cross domain ajax calls to wcf service - using WebInvoke Method = "*" and a few additionnal http headers should do the trick (as pointed out in http://forums.iis.net/t/1160649.aspx - last post).

    Back to my requirement.. When Firefox' tries to access a WCF services via an XHR call, it first sends a "preflighted" request (More information on https://developer.mozilla.org/En/HTTP_access_control). Such request uses the "OPTIONS" method whatever the intended method was (GET, POST, PUT, DELETE).
    As I pointed earlier, a workaround to accepting both the intended method and "OPTIONS" is to set WebInvoke Method to "*".
    Of course, it's a very unsatisfying solution to this problem.

    So far, I've been trying to temporarily alter the WebInvoke Method temporarily to "OPTIONS" in the AfterReceiveRequest method of a class derived from IDispatchMessageInspector class considering an instance of this class was earlier binded w/ endpointDispatcher.DispatchRuntime.MessageInspectors.Add in the ApplyDispatchBehavior method of a class derived from WebHttpBehavior.
    It doesn't work.
    Regardless of the temporary change of the WebInvokeMethod of the called service in AfterReceiveRequest, WCF still gives me a "405 Method not allowed" later in BeforeSendReply.


    Of course it is an even dirtier trick than using WebInvoke Method = "*" and it could maybe lead to severe issues in a multi-threaded and concurrently accessed service although it does exactly what I want it to do.


    A neater and simpler solution would be to redefine the WCF behavior using the WebInvoke Method so several methods can be put in the Method property of a WebInvokeAttribute in it (in a format like "POST, OPTIONS", etc.). Can it be done? If it can be done, where should it be done?


    Thanks for the input and have a nice day!
    Tuesday, January 12, 2010 11:50 AM

All replies

  • Bumping this thread as I have the exact same issue

    I created a method whose WebInvoke attribute had Method="OPTIONS" and it works..but then on the client the subsequent thing is to do a POST ..and there is no method to handle the POST

    I made Method="*" and I get a 400 bad request when the OPTIONS call is made......any ideas?
    Friday, January 15, 2010 10:32 PM
  • Wondering if anyone has found a good solution to the "OPTIONS" header issue.
    Tuesday, March 09, 2010 2:01 PM
  • I have implemented a solution for the Mozilla preflight issue. It doesn't completely address the inability to add just POST and OPTIONS to the WebInvoke method property, but it does work and I'll post it here in case anyone else is pulling their hair out trying to work out how to get this working.

    Firstly you must set the Method = "*" property to your WebInvoke attribute:

     

    [WebInvoke(Method = "*", UriTemplate = "DoStuff")]
    void DoStuff();

     

    Secondly you must add the Access-Control-Allow-Origin:* header to the preflighted OPTIONS response to allow cross domain access and get past the 405 errors. I did this by adding a small method that is called by each of my services' POST methods, but I'm sure there's a way to do it universally without having to do this in all your methods.

     

    private bool HandleHttpOptionsRequest()
    {
     if (WebOperationContext.Current.IncomingRequest.Method == "OPTIONS")
     {
      WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");
      return true;
     }
    
     return false;
    }

     

    public void DoStuff()
    {
     if (HandleHttpOptionsRequest())
     {
      return;
     }
     // ... Normal processing goes here
    }

     

    By doing so your service method is called twice, the first time it will return a HTTP 200 success message and will have the Access-Control-Allow-Origin Header property set allowing Mozilla to continue to send the actual POST.

    I'm sure this could be implemented in a RequestInterceptor, but I ran into problems trying to implement this and didn't get time to get it working.

    • Edited by Casey Allen Wednesday, April 21, 2010 3:41 AM fixed bug in code
    Wednesday, April 21, 2010 3:40 AM
  • Nowadays, almost browsers implement CORS (Cross Origin Resource Sharing) witch together with XmlHttpRequest gives you the possibility to do cross domain requests. Unfortunately, IE does not implement it, it has it own object to do Cross-Domain request (XDomainRequest).

    But, regarding this topic, in the realm of mozilla (Gecko), witch has CORS and XmlHttpRequest to do this kind of requests, 1st you need to make your service server aware of OPTIONS request like this: 

     

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
      EnableCrossDomainAjaxCall();
    }
    
    private void EnableCrossDomainAjaxCall()
    {
      HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
    
      if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
      {
       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();
      }
    }

     

    The above code: I have my service in an MVC project and this code is in Global.asax.cs

    The operation Contract you can do it like this:

    [OperationContract]
    [WebInvoke(Method = "POST")]
    string Method  (string param);

    This way, you make your service available for most browsers that send OPTIONS in the header and letting the response of what kind of verbs are permitted and origins and not in every service method. 

     

    Hope this help!

     

    Cheers,

     

    André Pedroso

     

    • Proposed as answer by TheManFran Thursday, December 15, 2011 8:14 AM
    Tuesday, February 08, 2011 5:46 PM
  • Nowadays, almost browsers implement CORS (Cross Origin Resource Sharing) witch together with XmlHttpRequest gives you the possibility to do cross domain requests. Unfortunately, IE does not implement it, it has it own object to do Cross-Domain request (XDomainRequest).

    But, regarding this topic, in the realm of mozilla (Gecko), witch has CORS and XmlHttpRequest to do this kind of requests, 1st you need to make your service server aware of OPTIONS request like this: 

     

    protected void Application_BeginRequest(object sender, EventArgs e)
    
    {
    
     EnableCrossDomainAjaxCall();
    
    }
    
    
    
    private void EnableCrossDomainAjaxCall()
    
    {
    
     HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
    
    
    
     if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
    
     {
    
      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();
    
     }
    
    }

     

    The above code: I have my service in an MVC project and this code is in Global.asax.cs

    The operation Contract you can do it like this:

     

    This way, you make your service available for most browsers that send OPTIONS in the header and letting the response of what kind of verbs are permitted and origins and not in every service method. 

     

    Hope this help!

     

    Cheers,

     

    André Pedroso

     

    [OperationContract]
    
    [WebInvoke(Method = "POST")]
    
    string Method (string param);


    THANK YOU!!! This solved all my problems.  I was going insane.  Everything was working in IE with no problem, but safari and FireFox would not allow this service to work.

     

    Thanks!

    Friday, July 08, 2011 3:12 PM
  • I'm Glad to help!!

    Cheers :)

     

    André Pedroso

    Friday, July 08, 2011 3:37 PM
  • Thank you. This was driving me nuts. Tried a million things, but this is the solution.
    Thursday, December 15, 2011 8:14 AM
  • <system.serviceModel>
    <standardEndpoints>
    <webScriptEndpoint>
    <standardEndpoint name="" crossDomainScriptAccessEnabled="true"/>
    </webScriptEndpoint>
    </standardEndpoints>
    <httpProtocol>
    <customHeaders>
    <add name="Access-Control-Allow-Origin" value="*" />
    <add name="Access-Control-Allow-Headers" value="Content-Type, Accept" />
    </customHeaders>
    </httpProtocol> 
    .
    .
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/> 
    .
    .
    .
    </system.serviceModel>

    Do above setting in the web.config of hosted wcf service. This will works for all Browsers including firefox,safari,chrome and opera.

    For more details go through below link

    http://www.dotnet-tricks.com/Tutorial/wcf/X8QN260412-Calling-Cross-Domain-WCF-Service-using-Jquery.html

    Wednesday, July 11, 2012 5:20 AM
  • Does anybody have a solution to this problem that can be used by a self-hosted WCF RESTful service?

    A .jpg is worth a thousand .doc

    Wednesday, October 03, 2012 5:34 PM
  • The only problem with * is it allows any domain to hit your service, I'd recommend implementing a safe list of domains.

    The code is a REST API approach but it will work in other solutions.

    	private void AllowAccess()
            {
    
                // Get the request headers
                WebHeaderCollection headers = WebOperationContext.Current.IncomingRequest.Headers;
    
                if (IsCORSAllowed(headers["Origin"]))
                {
                    // Allow the cross domain call
                    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", headers["Origin"]);
                    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Methods", "GET");
                    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Methods", "POST");
                    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Accept, appkey-authorization");
    
                    // Stop ajax caching the responses on Safari/iOS
                    WebOperationContext.Current.OutgoingResponse.Headers.Add("Pragma", "no-cache");
                    WebOperationContext.Current.OutgoingResponse.Headers.Add("Cache-Control", "no-cache");
                    WebOperationContext.Current.OutgoingResponse.Headers.Add("Expires", "0");
                }
            }
    
            private bool IsCORSAllowed(string RequestURL)
            {
    
                try
                {
    
                    if (RequestURL != null)
                    {
                        List<string> SafeHosts = new List<string>(ConfigurationManager.AppSettings["CrossDomainRequest"].Split(';'));
                        return SafeHosts.Any(RequestURL.Contains);
                    }
                    else
                    {
                        return false;
                    }
                }


    • Proposed as answer by NMackay Wednesday, November 28, 2012 4:53 PM
    • Edited by NMackay Wednesday, November 28, 2012 4:56 PM confidential setting exposed
    Wednesday, November 28, 2012 4:53 PM
  • I too faced the same issue.

    Follow the below step to solve the issue on (CORS) compliant in browsers.

    Include REDRock in your solution with the Cors reference.
    Include WebActivatorEx reference to webapi solution.

    Then Add the filr CorsConfig in the WebAPI App_Start Folder.

    [assembly: PreApplicationStartMethod(typeof(WebApiNamespace.CorsConfig), "PreStart")]

    namespace WebApi Namespace
    {
        public static class CorsConfig
        {
            public static void PreStart()
            {
                GlobalConfiguration.Configuration.MessageHandlers.Add(new RedRocket.WebApi.Cors.CorsHandler());
            }
        }
    }

    With these changes done i was able to access the webapi in all browsers.
    Monday, May 19, 2014 2:42 PM