81

I've set up an ASP.NET web application starting with an MVC 4/Web API template. It seems as though things are working really well - no problems that I'm aware of. I've used Chrome and Firefox to go through the site. I've tested using Fiddler and all of the responses seem to be on the money.

So now I proceed to write a simple Test.aspx to consume this new Web API. The relevant parts of the script:

<script type="text/javascript">
    $(function () {

        $.ajax({
            url: "http://mywebapidomain.com/api/user",
            type: "GET",
            contentType: "json",
            success: function (data) {

                $.each(data, function (index, item) {

                    ....

                    });
                }
                );

            },
            failure: function (result) {
                alert(result.d);
            },

            error: function (XMLHttpRequest, textStatus, errorThrown) {
                alert("An error occurred, please try again. " + textStatus);
            }

        });

    });
</script>

This generates a REQUEST header:

OPTIONS http://host.mywebapidomain.com/api/user HTTP/1.1
Host: host.mywebapidomain.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Origin: http://mywebapidomain.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: content-type
Connection: keep-alive

As is, Web API returns a 405 Method Not Allowed.

HTTP/1.1 405 Method Not Allowed
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Mon, 30 Sep 2013 13:28:12 GMT
Content-Length: 96

<Error><Message>The requested resource does not support http method 'OPTIONS'.</Message></Error>

I understand that the OPTIONS verb is not wired up in Web API controllers by default... So, I placed the following code in my UserController.cs:

// OPTIONS HTTP-verb handler
public HttpResponseMessage OptionsUser()
{
    var response = new HttpResponseMessage();
    response.StatusCode = HttpStatusCode.OK;
    return response;
}

...and this eliminated the 405 Method Not Allowed error, but the response is completely empty - no data is returned:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Mon, 30 Sep 2013 12:56:21 GMT
Content-Length: 0

There must be additional logic... I don't know how to properly code the Options method or if the controller is even the proper place to put the code. Weird (to me) that the Web API site responds properly when viewed from Firefox or Chrome, yet the .ajax call above errors out. How do I handle the "preflight" check in the .ajax code? Maybe I should be addressing this issue on the client side's .ajax logic? Or, if this is an issue on the server side due to not handling the OPTIONS verb.

Can anyone help? This must be a very common issue and I apologize if it's been answered here. I searched but didn't find any answers that helped.

UPDATE IMHO, this is a client-side issue and has to do with the Ajax JQuery code above. I say this because Fiddler doesn't show any 405 error headers when I access mywebapidomain/api/user from a web browser. The only place I can duplicate this problem is from the JQuery .ajax() call. Also, the identical Ajax call above works fine when run on the server (same domain).

I found another post: Prototype AJAX request being sent as OPTIONS rather than GET; results in 501 error that seems to be related, but I've tinkered with their suggestions with no success. Apparently, JQuery is coded so that if an Ajax request is cross-domain (which mine is) it adds a couple of headers that trigger the OPTIONS header somehow.

'X-Requested-With': 'XMLHttpRequest',
'X-Prototype-Version': Prototype.Version,

It just seems that there should be a better solution available than modifying core code in JQuery...

The answer provided below assumes this is a server-side issue. Maybe, I guess, but I lean toward clients, and calling a hosting provider isn't going to help.

sajadre
  • 1,141
  • 2
  • 15
  • 30
rwkiii
  • 5,716
  • 18
  • 65
  • 114
  • what are you going to send down with your options request? – Daniel A. White Sep 30 '13 at 13:44
  • I don't need to send an OPTIONS request at all. For some reason this gets done when an Ajax call is made crossdomain. So, as you can see in the Javascript all I am doing is specifying GET, yet the OPTIONS header is sent due to HTTP protocol. It's a "preflight" check. – rwkiii Sep 30 '13 at 13:50
  • 2
    oh you should enable cors on your iis server. – Daniel A. White Sep 30 '13 at 14:02
  • It's an Arvixe server - Business Class Pro. Both sites are hosted on the same physical server, same hosting account. Just different host names. Is CORS something I can enable without calling Arvixe? – rwkiii Sep 30 '13 at 14:05
  • i would call your hosting provider. – Daniel A. White Sep 30 '13 at 14:09
  • My problem seems like more of a client issue than a server one? It's common for browsers to send the OPTIONS request and from the ones I've tested they do not receive a 405 back from the server. Just wondering, if this is a server issue then calling my hosting provider is ok. If this is an issue people will run into on the client side if they want to consume this web api, then it's not a good option. – rwkiii Sep 30 '13 at 14:24

11 Answers11

96

Mike Goodwin answer is great but it seemed, when I tried it, that it was aimed at MVC5/WebApi 2.1. The dependencies for Microsoft.AspNet.WebApi.Cors didn't play nicely with my MVC4 project.

The simplest way to enable CORS on WebApi with MVC4 was the following.

Note that I have allowed all, I suggest you limit the Origin's to just the clients you want your API to serve. Allowing everything is a security risk.

Web.config:

<system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Methods" value="GET, PUT, POST, DELETE, HEAD" />
        <add name="Access-Control-Allow-Headers" value="Origin, X-Requested-With, Content-Type, Accept" />
      </customHeaders>
    </httpProtocol>
</system.webServer>

BaseApiController.cs:

We do this to allow the OPTIONS http verb

 public class BaseApiController : ApiController
  {
    public HttpResponseMessage Options()
    {
      return new HttpResponseMessage { StatusCode = HttpStatusCode.OK };
    }
  }
Oliver
  • 35,233
  • 12
  • 66
  • 78
  • @Castaldi this is likely because the answer provided was aimed at WebApi 1 which didn't have Attribute Routing. For WebApi 2 I would suggest using Microsoft's CORS nuget package. https://www.nuget.org/packages/Microsoft.AspNet.WebApi.Cors – Oliver May 25 '16 at 06:40
  • You can also use [ApiExplorerSettings(IgnoreApi = true)] to ignore the OPTIONS endpoints on Swagger. – Mário Meyrelles Mar 10 '17 at 20:55
  • This worked for my WebApi 2 App. Especially the Options() method to the relevant/base controller – lazyList Feb 04 '19 at 04:59
  • this worked for my web api in net core 3.1 - thank you! – kifka Jul 13 '21 at 17:11
52

As Daniel A. White said in his comment, the OPTIONS request is most likely created by the client as part of a cross domain JavaScript request. This is done automatically by Cross Origin Resource Sharing (CORS) compliant browsers. The request is a preliminary or pre-flight request, made before the actual AJAX request to determine which request verbs and headers are supported for CORS. The server can elect to support it for none, all or some of the HTTP verbs.

To complete the picture, the AJAX request has an additional "Origin" header, which identified where the original page which is hosting the JavaScript was served from. The server can elect to support request from any origin, or just for a set of known, trusted origins. Allowing any origin is a security risk since is can increase the risk of Cross site Request Forgery (CSRF).

So, you need to enable CORS.

Here is a link that explains how to do this in ASP.Net Web API

http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api#enable-cors

The implementation described there allows you to specify, amongst other things

  • CORS support on a per-action, per-controller or global basis
  • The supported origins
  • When enabling CORS a a controller or global level, the supported HTTP verbs
  • Whether the server supports sending credentials with cross-origin requests

In general, this works fine, but you need to make sure you are aware of the security risks, especially if you allow cross origin requests from any domain. Think very carefully before you allow this.

In terms of which browsers support CORS, Wikipedia says the following engines support it:

  • Gecko 1.9.1 (FireFox 3.5)
  • WebKit (Safari 4, Chrome 3)
  • MSHTML/Trident 6 (IE10) with partial support in IE8 and 9
  • Presto (Opera 12)

http://en.wikipedia.org/wiki/Cross-origin_resource_sharing#Browser_support

Mike Goodwin
  • 8,810
  • 2
  • 35
  • 50
  • Hi Mike. Thank you for the link, here's another one that's good too: http://www.codeguru.com/csharp/.net/net_asp/using-cross-origin-resource-sharing-cors-in-asp.net-web-api.html - though neither of these have fixed the problem for me, yet. I've placed my test pages on the server for now and that helps me for the short term. I tried installing Microsoft.AspNet.WebApi.Cors but got a weird error that my app didn't have any WebApi dependencies and so install rolled back. Thank you for your answer - I know it's right. +1! – rwkiii Oct 01 '13 at 16:59
  • @rwkiii The link is indeed a solution that involves adding dependencies on Web API 5.2.2 but the solution would be more extensible than a hack to force MVC to support the OPTIONS pre-flight request. You may also want to review Dominick's answer as the pre-flight request may be a result of Accept OR Content-Type headers that require such a call from the client – Sudhanshu Mishra Dec 09 '14 at 05:34
  • Just a note but if you set the content-type to: 'application/x-www-form-urlencoded', 'multipart/form-data' or 'text/plain' then the request is deemed 'simple' and wont issue the pre-flight request. – mbx-mbx Mar 09 '15 at 16:41
25

Just add this to your Application_OnBeginRequest method (this will enable CORS support globally for your application) and "handle" preflight requests :

var res = HttpContext.Current.Response;
var req = HttpContext.Current.Request;
res.AppendHeader("Access-Control-Allow-Origin", req.Headers["Origin"]);
res.AppendHeader("Access-Control-Allow-Credentials", "true");
res.AppendHeader("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name");
res.AppendHeader("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");

// ==== Respond to the OPTIONS verb =====
if (req.HttpMethod == "OPTIONS")
{
    res.StatusCode = 200;
    res.End();
}

* security: be aware that this will enable ajax requests from anywhere to your server (you can instead only allow a comma separated list of Origins/urls if you prefer).

I used current client origin instead of * because this will allow credentials => setting Access-Control-Allow-Credentials to true will enable cross browser session managment

also you need to enable delete and put, patch and options verbs in your webconfig section system.webServer, otherwise IIS will block them :

<handlers>
  <remove name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" />
  <remove name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" />
  <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
  <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
  <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE,PATCH,OPTIONS" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>

hope this helps

Chtioui Malek
  • 11,197
  • 1
  • 72
  • 69
  • 3
    Thanks. Only this `Application_OnBeginRequest` helped me. But if you want to be able to get data with authorization too, you should also add `Authorization` to `Access-Control-Allow-Headers` – Ehsan88 Feb 17 '16 at 05:02
18

After encountering the same issue in a Web API 2 project (and being unable to use the standard CORS packages for reasons not worth going into here), I was able to resolve this by implementing a custom DelagatingHandler:

public class AllowOptionsHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var response = await base.SendAsync(request, cancellationToken);

        if (request.Method == HttpMethod.Options &&
            response.StatusCode == HttpStatusCode.MethodNotAllowed)
        {
            response = new HttpResponseMessage(HttpStatusCode.OK);
        }

        return response;
    }
}

For the Web API configuration:

config.MessageHandlers.Add(new AllowOptionsHandler());

Note that I also have the CORS headers enabled in Web.config, similar to some of the other answers posted here:

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
    <remove name="WebDAVModule" />
  </modules>

  <httpProtocol>
    <customHeaders>
      <add name="Access-Control-Allow-Origin" value="*" />
      <add name="Access-Control-Allow-Headers" value="accept, cache-control, content-type, authorization" />
      <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
    </customHeaders>
  </httpProtocol>

  <handlers>
    <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
    <remove name="TRACEVerbHandler" />
    <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
  </handlers>
</system.webServer>

Note that my project does not include MVC, only Web API 2.

defines
  • 10,229
  • 4
  • 40
  • 56
11

I have managed to overcome 405 and 404 errors thrown on pre-flight ajax options requests only by custom code in global.asax

protected void Application_BeginRequest()
    {            
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
        if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
        {
            //These headers are handling the "pre-flight" OPTIONS call sent by the browser
            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
            HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
            HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
            HttpContext.Current.Response.End();
        }
    }

PS: Consider security issues when allowing everything *.

I had to disable CORS since it was returning 'Access-Control-Allow-Origin' header contains multiple values.

Also needed this in web.config:

<handlers>
  <remove name="ExtensionlessUrlHandler-Integrated-4.0"/>
  <remove name="OPTIONSVerbHandler"/>
  <remove name="TRACEVerbHandler"/>
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0"/>
</handlers>

And app.pool needs to be set to Integrated mode.

lukalev
  • 113
  • 1
  • 5
9

I had this same problem. For me the fix was to remove the custom content type from the jQuery AJAX call. Custom content types trigger the pre-flight request. I found this:

The browser can skip the preflight request if the following conditions are true:

The request method is GET, HEAD, or POST, and

The application does not set any request headers other than Accept, Accept-Language, Content-Language, Content-Type, or Last-Event-ID, and

The Content-Type header (if set) is one of the following:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

From this page: http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api (under "Preflight Requests")

Community
  • 1
  • 1
Dominik
  • 1,366
  • 2
  • 18
  • 37
3

In ASP.NET web api 2 , CORS support has been added . Please check the link [ http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api ]

refactor
  • 13,954
  • 24
  • 68
  • 103
2
    protected void Application_EndRequest()
    {
        if (Context.Response.StatusCode == 405 && Context.Request.HttpMethod == "OPTIONS" )
        {
            Response.Clear();
            Response.StatusCode = 200;
            Response.End();
        }
    }
yongfa365
  • 352
  • 3
  • 7
1

I too faced the same issue.

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

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

Then Add the file CorsConfig in the Web API App_Start Folder.

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

namespace WebApiNamespace
{
    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.

Sudhanshu Mishra
  • 6,523
  • 2
  • 59
  • 76
1

I've had same problem, and this is how I fixed it:

Just throw this in your web.config:

<system.webServer>
    <modules>
      <remove name="WebDAVModule" />
    </modules>

    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Expose-Headers " value="WWW-Authenticate"/>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, OPTIONS, PUT, PATCH, DELETE" />
        <add name="Access-Control-Allow-Headers" value="accept, authorization, Content-Type" />
        <remove name="X-Powered-By" />
      </customHeaders>
    </httpProtocol>

    <handlers>
      <remove name="WebDAV" />
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
</system.webServer>
Erti-Chris Eelmaa
  • 25,338
  • 6
  • 61
  • 78
0
//In the Application_OnBeginRequest method in GLOBAL.ASX add the following:-  

var res = HttpContext.Current.Response;  
var req = HttpContext.Current.Request;  
res.AppendHeader("Access-Control-Allow-Origin", "*");  
res.AppendHeader("Access-Control-Allow-Credentials", "true");  
res.AppendHeader("Access-Control-Allow-Headers", "Authorization");  
res.AppendHeader("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");  

    // ==== Respond to the OPTIONS verb =====
    if (req.HttpMethod == "OPTIONS")
    {
        res.StatusCode = 200;
        res.End();
    }

//Remove any entries in the custom headers as this will throw an error that there's to  
//many values in the header.  

<httpProtocol>
    <customHeaders>
    </customHeaders>
</httpProtocol>
Roy Fagon
  • 61
  • 2