19

Working on a new project using a WebAPI backend server and I am having trouble posting to the controller from an actual website, despite Postman having no issues posting to the controller. I get the an error 415, the browser console logging:

HTTP415: UNSUPPORTED MEDIA TYPE - The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method.
(XHR)OPTIONS - http://localhost:5000/api/Users

Whilst the log from Kestrel is

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 OPTIONS http://localhost:5000/api/Users  0
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 OPTIONS http://localhost:5000/api/Users  0
info: Microsoft.AspNetCore.Mvc.StatusCodeResult[1]
      Executing HttpStatusCodeResult, setting HTTP status code 415
Microsoft.AspNetCore.Mvc.StatusCodeResult:Information: Executing HttpStatusCodeResult, setting HTTP status code 415
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
      Executed action SchoolsChat.Controllers.UserContoller.Post (SchoolsChat) in 2.5323ms
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action SchoolsChat.Controllers.UserContoller.Post (SchoolsChat) in 2.5323ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 6.6615ms 415 
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 6.6615ms 415 

I am trying to post the following JSON:

{
    UserDOB: "2016-12-18",
    UserFirstName: "asdf",
    UserLastName: "asasdf",
    UserName: "asdf",
    UserSecret: "asdf"
}

using this TypeScript class

/**
 * JsonPost
 */
class JsonPost {
    private _response: number;
    public get Reponse(): number {
        return this._response;
    }
    constructor(link: string, data: Object) {
        let request = new XMLHttpRequest();
        request.withCredentials = true;

        request.open("POST", APIUri + link, true);
        request.setRequestHeader("content-type", "application/json");
        request.setRequestHeader("cache-control", "no-cache");
        request.onreadystatechange = () => this._response = request.status;
        console.log(request);
        request.send(JSON.stringify(data));
    }
}

The model of User is

public class User
    {
        [KeyAttribute]
        public int UserId { get; set; }
        [RequiredAttribute]
        public int SchoolId { get; set; }
        [RequiredAttribute]
        public string UserName { get; set; }
        [RequiredAttribute]
        [DataTypeAttribute(DataType.Password)]
        public string UserSecret { get; set; } // Unnecessary due to school linking?
        [RequiredAttribute]
        public virtual School UserSchool { get; set; }
    }

Whilst the controller for posting is simple, just outputting the first name

[HttpPost]
    public IActionResult Post([FromBody]User user)
    {
        Console.WriteLine(user.UserName);
        return StatusCode(200);
    }

Edit Whilst the answer from nemec was helpeful in resolving the problem, I found that for the WebAPI specifically the best resolution was to only configure cors using app.UseCors as services.AddCors in many cases didn't actually include the necessary headers in the response.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            app.UseCors(options => options.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials());
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();
            app.UseMvc();
        }
bsizzle
  • 389
  • 1
  • 4
  • 10
  • 4
    See http://stackoverflow.com/questions/8153832/xmlhttprequest-changes-post-to-option for why you are seeing the `OPTIONS` request instead of a `POST`. – Evan Mulawski Dec 19 '16 at 01:58

4 Answers4

57

In my case the problem was that I put a FromBody attribute before my action parameter.

From:

[HttpPost("Contact")]
public async Task<IActionResult> NewContact([FromBody]Contact contact)

To:

[HttpPost("Contact")]
public async Task<IActionResult> NewContact(Contact contact)
mohas
  • 1,911
  • 1
  • 16
  • 20
  • 5
    It fixed the issue for me as well but on closer inspection my web app was posting using a content type of `application/x-www-form-urlencoded`. Once I changed the content type of the `POST` to `application/json` `[FromBody]` was able to do it's thing. I've not dug into whether it's possible to use `[FromBody]` in conjunction with a content type of `application/x-www-form-urlencoded` yet. – AndyM Jul 24 '18 at 22:49
  • 3
    The problem occurs because the ContentType of the [FromBody] must be Json. If its not JSON it will do a 415 error. When you take out it, it accept allot of ContentType, and no ContentType. – Danilo Breda Dec 13 '18 at 12:15
  • I agree with @mohas. For me, common cause of 415 errors with ASP.Net Core is a missing binding attribute or a mismatch between the request and the declared binding ([FromBody], [FromForm], [FromQuery] etc). Older versions of MVC could often work OK without binding attributes. – R.M. Buda Nov 06 '19 at 01:54
  • Had the same error but with a CORS error message on ASP.Net core 3.1. Took me a while to figure this one out since I kept trying cors related fixes. – mababin Feb 09 '22 at 00:42
8

As Evan mentioned in his comment, your POST is turning into an OPTIONS when you make a cross-origin ajax request. Due to browsers' cross-origin security policies, your web api needs to tell the browser/js that your website is allowed to make ajax requests against it.

https://learn.microsoft.com/en-us/aspnet/core/security/cors

To setup CORS for your application add the Microsoft.AspNetCore.Cors package to your project.

Add the CORS services in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors();
}

If you follow the linked instructions, you can even use IApplicationBuilder.UseCors to further customize which sites are allowed.

For example:

app.UseCors(builder =>
    builder.WithOrigins("http://example.com")
           .AllowAnyHeader()
);

Postman is an app and therefore has the ability to exempt itself from cross-origin rules.

nemec
  • 1,044
  • 9
  • 35
3

Don't know why yet, I'm still pretty new to .Net Core Web API's. I removed the controller attribute [ApiController] and everything fell into place.

In my situation, I have a MVC interface & WebApi on the same project. Hope this helps someone.

Talon
  • 3,466
  • 3
  • 32
  • 47
2

We upgraded to .NET core 3.0 and this required to use [FromQuery] when you're building an object from query parameters, e.g. with a GET request.

Example:

[HttpGet("Hierarchy")]
public virtual async Task<IActionResult> GetHierarchy([FromServices] IDeviceHierarchyService service, [FromQuery] HierarchyStructureRequest structureLoad)
Jelle den Burger
  • 1,428
  • 17
  • 31