62

Using [FromBody] string content on an ApiController in ASP.NET Core 3.0 returns a validation error:

{"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
 "title":"One or more validation errors occurred.",
 "status":400,
 "traceId":"|9dd96d96-4e64bafba4ba0245.",
 "errors":{"$":["The JSON value could not be converted to System.String. Path: $ | LineNumber: 0 | BytePositionInLine: 1."]}}

when the client post data with content-type : application/json

How do I get the raw json data as a string in my api controller in .NET Core 3.0? Without the client having to update its content type?

Tony Ngo
  • 19,166
  • 4
  • 38
  • 60
Poul K. Sørensen
  • 16,950
  • 21
  • 126
  • 283
  • 1
    Does this works in .net core 2.1? Post sample of your json. – cdev Sep 30 '19 at 07:57
  • 1
    I decided to just go with StreamReader and read the Request.Body myself. Its a new project and havent testet on 2.1, but might have bound the body to JToken in the past instead of string. – Poul K. Sørensen Sep 30 '19 at 08:51

11 Answers11

73

Not sure this help but I think they made some change in .net core 3.0 Newtonsoft.JSON package so you can try this

Install Microsoft.AspNetCore.Mvc.NewtonsoftJson package.

In your startup.cs add

services.AddControllers().AddNewtonsoftJson();

Tony Ngo
  • 19,166
  • 4
  • 38
  • 60
42

If you are using asp.net core 3.0 then this has built-in JSON support. I have use the following and it works without setting the custom input handler.

[HttpPost]
public async Task<IActionResult> Index([FromBody] JsonElement body)
{

    string json = System.Text.Json.JsonSerializer.Serialize(body);
    return Ok();

}
ameya
  • 1,448
  • 1
  • 15
  • 31
33

Change [FromBody] string content to [FromBody] object content and then if you want/need to read as string use content.ToString()

Chev Paredes
  • 691
  • 7
  • 5
7

If you change the parameter [FromBody] String value to [FromBody] YourType value it is automatically deserialised for you.

From:

// POST api/<SelectiveCallRulesController>
[HttpPost]
public async Task Post([FromBody] String rule)        
{
...

To:

// POST api/<SelectiveCallRulesController>
[HttpPost]
public async Task Post([FromBody] SelectiveCallRule rule)        
{
...

It had me going around until I realised the error message regarding deserialisation is correct!

CrazeydAVE
  • 91
  • 1
  • 5
  • 4
    The problem is sometimes you can't have the value deserialized, because you don't know exactly what type of object you'll be getting. – Eternal21 May 07 '21 at 13:46
4

The reason for this error is that "System.Text.Json doesn't deserialize non-string values into string properties" (source).

That means if you have a controller with the simple [FromBody] string argument:

[HttpPost("save")]
public async Task Save([FromBody] string content)
{

this request will succeed:

curl -H "Content-Type: application/json" -X POST -d "\"abcdefgh\"" https://localhost:5000/save -v

but this will fail:

curl -H "Content-Type: application/json" -X POST -d "{\"content\":\"abcdefgh\"}" https://localhost:5000/save -v

In fact, a similar error occurs not only for string but for other simple types like int, bool, etc. For example, if you change the argument type to int in the code above, then sending JSON {"content":123} in the body will give JSON value could not be converted to System.Int32 error.

To avoid this error either:

  • fix the request to pass the argument in the body as "some string" (instead of JSON)
  • pass argument in request as [FromQuery] or [FromForm]
  • or move your argument into the property of some class (don't forget the getter and setter for this member because class fields are not deserialized):
public class Content
{
    public string Value { get; set;}
}
...
[HttpPost("save")]
public async Task Save([FromBody] Content content)
{

Tested on ASP.NET Core 7.0

Vlad Rudenko
  • 2,363
  • 1
  • 24
  • 24
1

I had to write a custom IInputFormatter to ensure my body content was always interpreted as a string.

I also was in the situation where updating all of the API clients was infeasible.

The following will ensure that any [FromBody] parameters will be interpreted as strings, even if they are not quote-wrapped by the caller.

public class JsonStringInputFormatter : TextInputFormatter
{
    public JsonStringInputFormatter() : base()
    {
        SupportedEncodings.Add(UTF8EncodingWithoutBOM);
        SupportedEncodings.Add(UTF16EncodingLittleEndian);

        SupportedMediaTypes.Add(MediaTypeNames.Application.Json);
    }

    public override bool CanRead(InputFormatterContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        return context.ModelType == typeof(string);
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(
        InputFormatterContext context, Encoding encoding)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        using (var streamReader = new StreamReader(
            context.HttpContext.Request.Body,
            encoding))
        {
            return await InputFormatterResult.SuccessAsync(
                (await streamReader.ReadToEndAsync()).Trim('"'));
        }
    }
}

Trimming quotes from the body allows this to be forwards-compatible for body content that is correctly formatted and quote-wrapped.

Ensure that it is registered in your startup before the System.Text.Json formatter:

services.AddControllers()
    .AddMvcOptions(options =>
    {
        options.InputFormatters.Insert(
            0,
            new JsonStringInputFormatter());
    });
Benjamin Soddy
  • 557
  • 1
  • 6
  • 22
0

you can create another class contains your json field.

Wafeelka
  • 41
  • 1
  • 6
0

Use JsonElement instead of string or object. {yourcontrollername([FromBody] JsonElement yourJsondata)}

0

In my case I was working with Angular and NET 6.0

So the controller:

    public string? Post([FromBody] string word)
    {
    }

and the call from angular:

using

import { HttpClient, HttpHeaders } from '@angular/common/http';

the code:

const headers = new HttpHeaders({
  'Content-Type': 'application/json'
}); 

will mark the petition as json.

const body = JSON.stringify("myvalue");

  this.http.post(this.baseUrl + 'controller', body, { headers: headers, responseType: 'text', withCredentials: true }).subscribe(result => {
      this.mycontent = result;
    }, error => console.error(error));

In the above example, The responstype is only because the controller is returning also a string.

Leandro Bardelli
  • 10,561
  • 15
  • 79
  • 116
0
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;

     
[HttpPost]
        public IActionResult SaveScreen([FromBody] JObject value)
        {
            JObject result = new JObject();
            _log.LogInformation(JsonConvert.SerializeObject(value,Formatting.Indented));
            return Content(JsonConvert.SerializeObject(result), "application/json; charset=UTF-8");
        }

Not sure if it's what you want. but i use this code and get the result I want. I just want to post json string into controller.

-2

You need to convert the Json Object to string and then send it to server. Like JSON.stringify(jsonObj).

Hamza Ali
  • 380
  • 5
  • 21