28
votes

I have a .NET Core Web API that is returning a 415 Unsupported Media Error when I try to post some data to it that includes some json. Here's part of what is returned in the Chrome Debugger:

Request URL:http://localhost:51608/api/trackAllInOne/set
Request Method:POST
Status Code:415 Unsupported Media Type
Accept:text/javascript, text/html, application/xml, text/xml, */*
Content-Type:application/x-www-form-urlencoded

action:finish
currentSco:CSharp-SSLA:__How_It_Works_SCO
data:{"status":"incomplete","score":""}
activityId:13
studentId:1
timestamp:1519864867900

I think this has to do with my controller not accepting application/x-www-form-urlencoded data - but I'm not sure. I've tried decorating my controler with Consumes but that does not seem to work.

[HttpPost]
[Route("api/trackAllInOne/set")]
[Consumes("application/x-www-form-urlencoded")]
public IActionResult Post([FromBody] PlayerPackage playerPackage)
{ etc..}

Any help greatly appreciated.

The following code worked fine in .NET 4.6.1 and I am able to capture and process the posts shown above.

[ResponseType(typeof(PlayerPackage))]
public async Task<IHttpActionResult> PostLearningRecord(PlayerPackage playerPackage)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var id = Convert.ToInt32(playerPackage.ActivityId);
    var learningRecord = await _context.LearningRecords.FindAsync(id);
    if (learningRecord == null)
        return NotFound();
etc...
4
What is PlayerPackage ? How did you send the request from Front?Edward
PlayerPackage is information sent from an online course tracking a student's progress in that course. I have no control over how the system makes it posts. The job of my WebApi is t capture these posts and save them in my tracking system.Roddy Balkan
Do you want to accept data with data:{"status":"incomplete","score":""} or you want to log all the body action:finish currentSco:CSharp-SSLA:__How_It_Works_SCO data:{"status":"incomplete","score":""} activityId:13 studentId:1 timestamp:1519864867900Edward
Yes, I do want to accept the json data;{"status: "incomplete" etc.. That's the critical part and is what is stopping it from working. When I remove the json, it seems to work fine - but the json is what I need the most.Roddy Balkan
The following code used to work fine in .net 4.6.1 for the same posts: [ResponseType(typeof(PlayerPackage))] public async Task<IHttpActionResult> PostLearningRecord(PlayerPackage playerPackage) { if (!ModelState.IsValid) { return BadRequest(ModelState); } var id = Convert.ToInt32(playerPackage.ActivityId); var learningRecord = await _context.LearningRecords.FindAsync(id); if (learningRecord == null) return NotFound();Roddy Balkan

4 Answers

48
votes

Try using [FromForm] instead of [FromBody].

public IActionResult Post([FromForm] PlayerPackage playerPackage)
  • FromBody > Bind from JSON

  • FromForm > Bind from Form parameters

You can also remove [FromBody] altogether and trial it then. Because you are expecting form-urlencoded should tell it to bind to object.

41
votes

For PlayerPackage, the request should send a PlayerPackage Json Object, based on your description, you could not control the request which is posted from other place.

For the request, its type is application/x-www-form-urlencoded, it will send data with {"status":"incomplete","score":""} in string Format instead of Json object. If you want to accept {"status":"incomplete","score":""}, I suggest you change the method like below, and then conver the string to Object by Newtonsoft.Json

[HttpPost]
[Route("~/api/trackAllInOne/set")]
[Consumes("application/x-www-form-urlencoded")]
public IActionResult Post([FromForm] string data)
{
    PlayerPackage playerPackage = JsonConvert.DeserializeObject<PlayerPackage>(data);
    return Json(data);
}
13
votes

This did the trick for me:

[HttpPost]
[Consumes("application/x-www-form-urlencoded")]
public IActionResult Post([FromForm]IFormCollection value)
0
votes

I had the same problem. FormDataCollection has no default constructors which is required by Formatters. Use IFormCollection instead.