locked
webapi + CORS: POST works, PUT does not RRS feed

  • Question

  • User-1925828071 posted

    Hi,

    I have a problem accessing a webapi-REST-service with CORS. What I've done so far:

    • installed Microsoft.AspNet.Cors V 5.2.3 via NuGet,
    • called config.EnableCors() in the "Register" method of WebApiConfig,
    • set the attribute [EnableCors("*", "*", "*")] on the controller.

    A CORS-POST call (from an angular2-app) with headers "content-type: application/json" and "accept: application/json" succeeds. The corresponding Fiddler protocol lists the preflight-OPTIONS request from the browser (IE11) followed by the POST request.

    If I issue a PUT request from the very same application, fiddler just lists the OPTIONS request with the servers answer "404 No action was found on the controller ... that matches the request". The PUT request uses the same headers as the POST request.

    When I issue the PUT request from swagger/swashbuckle, everything works fine (just not being a CORS request). The controller is an ODataController.

    Can anyone explain, what I'm doing wrong. If you need further info to solve this problem, please let me know.

    Thanks in advance
    Jürgen

    Thursday, July 7, 2016 12:12 PM

All replies

  • User36583972 posted

    Hi juergen roehr,

    Firstly, you can refer the following links.

    Enabling Cross-Origin Requests in ASP.NET Web API 2:

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

    ASP.NET Web API - CORS Support in ASP.NET Web API 2:

    https://msdn.microsoft.com/en-us/magazine/dn532203.aspx

    Then, debug your code to find which code may cause your error. You could also share us more relevant code/message to help us reproduce the problem.

    Best Regards,

    Yohann Lu

    Friday, July 8, 2016 2:22 AM
  • User-1925828071 posted

    Hi Yohann Lu,

    thank you for your answer. I already read the documents you mentioned and they helped me enabling CORS Access to my service - at least for the GET and POST requests.

    So here is my code - reduced to what I consider relevant:

    public static class WebApiConfig
    {
       public static void Register(HttpConfiguration config)
       {
          config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
          config.Formatters.JsonFormatter.SerializerSettings.Formatting = Formatting.Indented;
          config.Formatters.JsonFormatter.SerializerSettings.ContractResolver =
             new CamelCasePropertyNamesContractResolver();
     
          // Web API routes
          config.EnableCors();
          config.MapHttpAttributeRoutes();
     
          var model = GetEdmModel();
          var batchHandler = new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer);
          config.MapODataServiceRoute("odata""odata", model, batchHandler);
     
          config.EnsureInitialized();
       }
     
       private static IEdmModel GetEdmModel()
       {
          var builder = new ODataConventionModelBuilder();
          builder.EnableLowerCamelCase();
     
          builder.EntitySet<Person>("persons");
          var edmModel = builder.GetEdmModel();
     
          return edmModel;
       }
    }
    

    The controller functions are reduced as the business logic is irrelevant for the Problem.

    [ODataRoutePrefix("persons")]
    [EnableCors("*""*""*")]
    public class PersonsController : ODataController
    {
       [EnableQuery]
       [ResponseType(typeof(IEnumerable<Person>))]
       [ODataRoute("")]
       [HttpGet]
       public IHttpActionResult Get()
       {
          var persons = new List<Person>();
          return this.Ok(persons);
       }
     
       [EnableQuery]
       [ResponseType(typeof(Person))]
       [ODataRoute("({id})")]
       [HttpGet]
       public IHttpActionResult Get([FromODataUriint id)
       {
          var person = new Person();
     
          return this.Ok(person);
       }
     
       [EnableQuery]
       [ResponseType(typeof(Person))]
       [ODataRoute("")]
       [HttpPost]
       public IHttpActionResult Post(Person person)
       {
          return this.Created(person);
       }
     
       [EnableQuery]
       [ResponseType(typeof(Person))]
       [ODataRoute("({id})")]
       [HttpPut]
       public IHttpActionResult Put([FromODataUriint id,
          Person person)
       {
          return this.Ok(person);
       }
    }
    

    Here's the Fiddler protocol of subsequent calls to the Service, which ist published in my local IIS (Win 8.1, IIS 8.5)

    Fiddler protocol

    The first two requests (POST and PUT) are issued via swashbuckle, so they are no CORS requests. These requests are just included to proove, that the WebApi sevrice is working correctly. 

    The marked request is issued via my Angular app, so this one is a CORS preflight-request:

    Request:
    OPTIONS http://192.168.1.79/webtool.webapi/odata/persons HTTP/1.1
    Access-Control-Request-Method: POST
    Access-Control-Request-Headers: content-type, accept
    ...
    
    Response:
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: *
    Access-Control-Allow-Headers: content-type
    ...

    The next request is the POST request, which yields the expected Response (an empty Person record in JSON Format).

    The last request is the PUT preflight-request from my Angular app, which is answered with a 404.

    Request:
    OPTIONS http://192.168.1.79/webtool.webapi/odata/persons(1) HTTP/1.1
    Access-Control-Request-Method: PUT
    Access-Control-Request-Headers: content-type, accept
    ...
    
    Response:
    HTTP/1.1 404 Not Found
    Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
    Content-Length: 301
    {
      "error":{
          "code":"",
          "message":"No HTTP resource was found that matches the request
                     URI 'http://192.168.1.79/webtool.webapi/odata/persons(1)'.","innererror":{
          "message":"No action was found on the controller 'Persons' that matches the request.",
          "type":"",
          "stacktrace":""
        }
      }
    }

    Does this Information help you any further? I can't see, where I should start Debugging.

    Cheers
    Jürgen

    Friday, July 8, 2016 10:16 AM
  • User36583972 posted

    Hi juergen roehr,

    According to your code. I have made a sample on my side. I can call Put, Get, Post methods correctly. You can refer the following Ajax call.

        $.ajax({
                        url: "http://localhost:9601/odata/persons(999)",
                        type: "Put",//Post"
                        headers: { "Accept": "application/json; odata=nometadata" },
                        contentType: "application/json; charset=urf-8",
                        dataType: "json",
                        //data: "{ LName:'slider1',FName:'200',Id:1}",
                        success: function (response) {
                            //alert(JSON.stringify(response))
                            alert(response)
                        }, 
                        error: function (XMLHttpRequest, textStatus, errorThrown) {
                            alert("error-Put:" + textStatus + "  " + errorThrown);
                        }
                    });
    

    Best Regards,

    Yohann Lu

    Saturday, July 9, 2016 8:32 AM
  • User-1925828071 posted

    Hi Yohann Lu,

    again, thank you for your help, but firstly I didn't doubt, that a PUT request works in general (in fact you can see from my post, that it works in my webapi service as well) and secondly I wonder, whether your request really is a CORS request. From the developer tools screenshot, I assume, it is not. Just using different port numbers is - at least in IE - "not enough CO..". That's why I've published the service to my local IIS and called it via the internal ip-address (the angular app runs on localhost:3000 - the default setup for "lite server").

    And even if you can manage to get a CORS result from your PUT request, the question remains: what is the reason for web api not being able to route *my* request. Here is the list of my included libraries

    <?xml version="1.0" encoding="utf-8"?>
    <packages>
      <package id="EntityFramework" version="6.1.3" targetFramework="net46" />
      <package id="Flurl" version="0.1.0" targetFramework="net46" />
      <package id="Microsoft.AspNet.Cors" version="5.2.3" targetFramework="net46" />
      <package id="Microsoft.AspNet.OData" version="5.9.0" targetFramework="net46" />
      <package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net46" />
      <package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net46" />
      <package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net46" />
      <package id="Microsoft.AspNet.WebApi.Cors" version="5.2.3" targetFramework="net46" />
      <package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.3" targetFramework="net46" />
      <package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.1" targetFramework="net46" />
      <package id="Microsoft.Net.Compilers" version="1.2.2" targetFramework="net46" developmentDependency="true" />
      <package id="Microsoft.OData.Core" version="6.15.0" targetFramework="net46" />
      <package id="Microsoft.OData.Edm" version="6.15.0" targetFramework="net46" />
      <package id="Microsoft.Spatial" version="6.15.0" targetFramework="net46" />
      <package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net46" />
      <package id="Newtonsoft.Json" version="8.0.3" targetFramework="net46" />
      <package id="Swashbuckle" version="5.3.2" targetFramework="net46" />
      <package id="Swashbuckle.Core" version="5.3.2" targetFramework="net46" />
      <package id="Swashbuckle.OData" version="2.18.3" targetFramework="net46" />
      <package id="WebActivatorEx" version="2.1.0" targetFramework="net46" />
    </packages>

    These are all current Versions - regarding the web api and odata stuff.

    Any further ideas?

    Cheers
    Jürgen

    Sunday, July 10, 2016 11:34 AM
  • User36583972 posted

    Hi senseiern,

    First of all,  I do not have the same environment like you on my side. So, I suggest that you consider whether there is a problem on IIS settings.

    ASP.NET Web API - PUT & DELETE Verbs Not Allowed - IIS 8:

    http://stackoverflow.com/questions/10906411/asp-net-web-api-put-delete-verbs-not-allowed-iis-8

    If you have some questions about IIS, you van visit the IIS forum for getting better support:

    http://forums.iis.net/

    Best Regards,

    Yohann Lu

    Tuesday, July 12, 2016 9:19 AM
  • User-1925828071 posted

    Hi all,

    I'm afraid, but this is no IIS issue (or IIS express, which yields the same results). All mehtods - including PUT - are served correctly, when called from the same origin; what fails is the OPTIONS method as preflight for the PUT request. I've investigated this a little further:

    I've created an ApiController similar to the OdataController above. The only difference: the urls for "GET specific" and "PUT" look like this ".../persons/1" instead of ".../persons(1)", as the braces lead to an error with the ApiController and not using the braces produces an error with the OdataController. Calling each method on the ApiController succeeds - from same origin and via CORS (I use "127.0.0.1" as the host of the client app, which leads to CORS requests).

    Calling each method on the OdataController from same origin succeeds as well. Calling those methods via CORS succeeds except for the PUT perflight (hence the PUT request itself is never started). Here are the headers of the Odata requests:

    GET all preflight request
    OPTIONS http://localhost:3607/odata/persons HTTP/1.1
    Host: localhost:3607
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: de,en-US;q=0.7,en;q=0.3
    Accept-Encoding: gzip, deflate
    Access-Control-Request-Method: GET
    Access-Control-Request-Headers: content-type
    Origin: http://127.0.0.1:3000
    Connection: keep-alive
    
    GET request
    GET http://localhost:3607/odata/persons HTTP/1.1
    Host: localhost:3607
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0
    Accept: application/json
    Accept-Language: de,en-US;q=0.7,en;q=0.3
    Accept-Encoding: gzip, deflate
    Content-Type: application/json
    Referer: http://127.0.0.1:3000/...
    Origin: http://127.0.0.1:3000
    Connection: keep-alive
    
    GET Response
    HTTP/1.1 200 OK
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Type: application/json; odata.metadata=minimal
    Expires: -1
    Server: Microsoft-IIS/10.0
    Access-Control-Allow-Origin: *
    OData-Version: 4.0
    X-AspNet-Version: 4.0.30319
    X-Powered-By: ASP.NET
    Date: Wed, 20 Jul 2016 11:07:25 GMT
    Content-Length: 93
    { "@odata.context":"http://localhost:3607/odata/$metadata#persons","value":[ ] }
    GET specific preflight request
    OPTIONS http://localhost:3607/odata/persons(1) HTTP/1.1
    Host: localhost:3607
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: de,en-US;q=0.7,en;q=0.3
    Accept-Encoding: gzip, deflate
    Access-Control-Request-Method: GET
    Access-Control-Request-Headers: content-type
    Origin: http://127.0.0.1:3000
    Connection: keep-alive
    
    GET specific request
    GET http://localhost:3607/odata/persons(1) HTTP/1.1
    Host: localhost:3607
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0
    Accept: application/json
    Accept-Language: de,en-US;q=0.7,en;q=0.3
    Accept-Encoding: gzip, deflate
    Content-Type: application/json
    Referer: http://127.0.0.1:3000/...
    Origin: http://127.0.0.1:3000
    Connection: keep-alive
    
    GET response
    HTTP/1.1 200 OK
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Type: application/json; odata.metadata=minimal
    Expires: -1
    Server: Microsoft-IIS/10.0
    Access-Control-Allow-Origin: *
    OData-Version: 4.0
    X-AspNet-Version: 4.0.30319
    X-Powered-By: ASP.NET
    Date: Wed, 20 Jul 2016 11:07:25 GMT
    Content-Length: 128
    {
      "@odata.context":"http://localhost:3607/odata/$metadata#persons/entity", 
      "id":0,
      "firstName":null,
      "lastName":null,
      "age":0
    }
    PUT preflight request
    OPTIONS http://localhost:3607/odata/persons(1) HTTP/1.1
    Host: localhost:3607
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: de,en-US;q=0.7,en;q=0.3
    Accept-Encoding: gzip, deflate
    Access-Control-Request-Method: PUT
    Access-Control-Request-Headers: content-type
    Origin: http://127.0.0.1:3000
    Connection: keep-alive
    
    PUT preflight response
    HTTP/1.1 404 Not Found
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Type: application/json; odata.metadata=minimal; odata.streaming=true
    Expires: -1
    Server: Microsoft-IIS/10.0
    OData-Version: 4.0
    X-AspNet-Version: 4.0.30319
    X-Powered-By: ASP.NET
    Date: Wed, 20 Jul 2016 11:07:25 GMT
    Content-Length: 288
    {
      "error": {
        "code":"",
        "message":"No HTTP resource was found that matches the request URI 'http://localhost:3607/odata/persons(1)'.",
        "innererror": {
          "message":"No action was found on the controller 'Persons' that matches the request.","type":"",
          "stacktrace":""
        }
      }
    }
    PUT preflight request for ApiController
    OPTIONS http://localhost:3607/api/persons/1 HTTP/1.1
    Host: localhost:3607
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: de,en-US;q=0.7,en;q=0.3
    Accept-Encoding: gzip, deflate
    Access-Control-Request-Method: PUT
    Access-Control-Request-Headers: content-type
    Origin: http://127.0.0.1:3000
    Connection: keep-alive
    
    PUT preflight response
    HTTP/1.1 200 OK
    Cache-Control: no-cache
    Pragma: no-cache
    Expires: -1
    Server: Microsoft-IIS/10.0
    Access-Control-Allow-Origin: *
    Access-Control-Allow-Methods: PUT
    Access-Control-Allow-Headers: content-type
    X-AspNet-Version: 4.0.30319
    X-Powered-By: ASP.NET
    Date: Wed, 20 Jul 2016 11:40:44 GMT
    Content-Length: 0

    The only difference I can see between the failing Odata PUT preflight request and the succeeding Api PUT preflight request ist the braced parameter in the url. The only difference between the failing Odata PUT preflight request and the succeeding Odata GET (specific) preflight request is the value of the requested method.

    So again my question: Why is PUT failing? Is this a bug in Asp WebApi Odata or do I have to configure something?

    Cheers
    Jürgen

    Wednesday, July 20, 2016 11:52 AM
  • User36583972 posted

    Hi, juergen roehr,

    This is confusing. I think is there have some wrong with your program settings? We can make it can work well. So, I suggest you re-create your application and debug your code step by step.

    If you have any question, you can post a new thread in relative forums.

    Best Regards,

    Yohann Lu

    Saturday, August 6, 2016 4:23 AM
  • User-474980206 posted
    By default only, get, post and head are supported by CORS. You need to update your CORS attribute.

    [EnableCors("*", "*", "GET,POST,PUT,DELETE")]
    Sunday, August 7, 2016 4:41 PM
  • User-1925828071 posted

    Hi bruce,

    I'm sorry for the late reply, but the E-Mail notification is working as bad as the ODataController. And I'm sorry, but your answer ist not the solution. Of course I've tried replacing the "*" with all verbs explicitly - my fault, that I didn't mention that - and it changed nothing. Secondly - and here I wonder, why I wrote all the stuff above if no one does read it - it's NOT the PUT which gets a 404 reply, it is the OPTIONS request being the preflight to PUT. The OPTIONS being preflight to GET works well. So it cannot be the verb itself - or am I missing something?

    Cheers
    Jürgen

    Friday, August 12, 2016 11:11 AM
  • User-1925828071 posted

    Hi Yohann Lu,

    can you please tell me, how to debug the code which is responsible for those actions where the routing system decides which request to pass through to my controller or not to pass it through.

    Thanks
    Jürgen

    Friday, August 12, 2016 11:13 AM
  • User36583972 posted

    Hi juergen roehr,

    As far as I know, the Web API request processing pipeline includes a few built-in message handlers. These include the following:

    •HttpServer -- this is used to retrieve the request from the host

    •HttpRoutingDispatcher -- this is used to dispatch the request based on the route configured

    •HttpControllerDispatcher -- this is used to send the request to the respective controller

    You can add message handlers to the pipeline to perform one or more of the following operations.

    •Perform authentication and authorization

    •Logging the incoming requests and the outgoing responses

    •Add response headers to the response objects

    •Read or modify the request headers

    You can refer the following tutorial.

    HTTP Message Handlers in ASP.NET Web API:

    http://www.asp.net/web-api/overview/advanced/http-message-handlers

    Best Regards,

    Yohann Lu

    Sunday, August 28, 2016 2:31 AM
  • User-1925828071 posted

    Hi Yohann Lu,

    thank you for the link to the usage of the message handlers. I've used them in my sample service like described and so detected, that when calling base.HandleAsync a HttpResponseException was thrown on the PUT-preflight request (probably leading to the 404 result). Unfortunately further investigation of the exception like described here lead to nowhere since I could not manage to get neither the logger nor the handler to be called. As there are quite a few reports about this phenomenon when using odata ith CORS, I consider this one as buggy enough to cancel my investigations. Maybe it will be working under ASP.NET Core.

    Again, thank your for your time and your help.

    Cheers
    Jürgen

    Tuesday, September 6, 2016 10:53 AM