Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling CORS Preflight requests to ASP.NET MVC actions

I'm trying to perform a cross-domain POST request to an ASP.NET MVC controller action. This controller action accepts & uses various parameters. The problem is that when the preflight request happens, the controller action actually attempts to execute & because the OPTIONS request doesn't pass any data, the controller action throws out a 500 HTTP error. If I remove the code that uses the parameter, or the parameter itself, the entire request chain is completed successfully.

An example of how this is implemented:

Controller Action

public ActionResult GetData(string data) {     return new JsonResult     {         Data = data.ToUpper(),         JsonRequestBehavior = JsonRequestBehavior.AllowGet     }; } 

Client-side code

<script type="text/javascript">         $(function () {             $("#button-request").click(function () {                 var ajaxConfig = {                     dataType: "json",                     url: "http://localhost:8100/host/getdata",                     contentType: 'application/json',                     data: JSON.stringify({ data: "A string of data" }),                     type: "POST",                     success: function (result) {                         alert(result);                     },                     error: function (jqXHR, textStatus, errorThrown) {                         alert('Error: Status: ' + textStatus + ', Message: ' + errorThrown);                     }                 };                  $.ajax(ajaxConfig);             });         });     </script> 

Now, whenever the preflight request happens, it returns a 500 HTTP code, because the "data" parameter is null, seeing as the OPTIONS request doesn't pass any values.

The server application has been set up in my local IIS on port 8100 & the page running the client-side code is set up on port 8200 to mimic the cross-domain calls.

I have also configured the host (on 8100) with the following headers:

Access-Control-Allow-Headers: Content-Type Access-Control-Allow-Methods: POST, GET Access-Control-Allow-Origin: http://localhost:8200 

One workaround I had found, was to check the HTTP method that executes the action & if it's a OPTIONS request to just return blank content, otherwise execute the action code. Like so:

public ActionResult GetData(string data) {     if (Request.HttpMethod == "OPTIONS") {         return new ContentResult();     } else {         return new JsonResult         {             Data = data.ToUpper(),             JsonRequestBehavior = JsonRequestBehavior.AllowGet         };     } } 

But this approach feels very clunky to me. I considered adding this sort of logic to an Attribute, but even this would mean decorating every action that will get called using CORS with it.

Is there a more elegant solution to getting this functionality to work?

like image 326
Carl Heinrich Hancke Avatar asked Nov 29 '12 11:11

Carl Heinrich Hancke


People also ask

How do you handle CORS preflight request?

Your preflight response needs to acknowledge these headers in order for the actual request to work. Pay special attention to the Access-Control-Allow-Headers response header. The value of this header should be the same headers in the Access-Control-Request-Headers request header, and it can not be '*'.

What triggers a CORS preflight?

A CORS preflight OPTIONS request can be triggered just by adding a Content-Type header to a request — if the value's anything except application/x-www-form-urlencoded , text/plain , or multipart/form-data .

What is CORS in ASP NET MVC?

Cross Origin Resource Sharing (CORS) is a W3C standard that allows a server to relax the same-origin policy. Using CORS, a server can explicitly allow some cross-origin requests while rejecting others. CORS is safer and more flexible than earlier techniques such as JSONP.


2 Answers

So I have found a solution that works. For each request, I check whether it's a CORS request & whether the request is coming in with the OPTIONS verb, indicating that it's the preflight request. If it is, I just send an empty response back (which only contains the headers configured in IIS of course), thus negating the controller action execution.

Then if the client confirms it's allowed to perform the request based on the returned headers from preflight, the actual POST is performed & the controller action is executed. And example of my code:

protected void Application_BeginRequest() {     if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) &&         Request.HttpMethod == "OPTIONS") {         Response.Flush();     } } 

As mentioned, this worked for me, but if anyone knows of a better way, or of any flaws in my current implementation, I would appreciate to hear about them.

like image 52
Carl Heinrich Hancke Avatar answered Sep 25 '22 04:09

Carl Heinrich Hancke


expanding on Carl's answer, i took his code and plugged it into my OWIN pipeline:

app.Use((context, next) => {      if (context.Request.Headers.Any(k => k.Key.Contains("Origin")) && context.Request.Method == "OPTIONS")      {          context.Response.StatusCode = 200;          return context.Response.WriteAsync("handled");      }       return next.Invoke(); }); 

Just add this to the beginning (or anywhere before you register the WebAPI) of your IAppBuilder in Startup.cs

like image 43
Jonesopolis Avatar answered Sep 24 '22 04:09

Jonesopolis