ASP.NET Core 3.0 [FromBody] string content returns "The JSON value could not be converted to System.String."
Asked Answered
T

11

69

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?

Tread answered 28/9, 2019 at 22:3 Comment(2)
Does this works in .net core 2.1? Post sample of your json.Conney
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.Charged
L
74

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();

Larrabee answered 29/9, 2019 at 12:56 Comment(4)
My [FromBody] JSON parameter was returning null after the 3.0 upgrade. This fixed it. Thanks!Turtle
Tried with asp .net core 3.1. Face error as Package Microsoft.AspNetCore.Mvc.NewtonsoftJson 5.0.0 is not compatible with netcoreapp3.1Communicable
@Vikram, that's because you're installing version 5.0.0 of the NuGet package. Before installation, select the version to be 3.1.Crandell
doesn't work for NET6Adjunction
S
48

If you are using ASP.NET Core 3.0+ then this has built-in JSON support with System.Text.Json. I have used 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();

}
Sharpfreeze answered 24/11, 2019 at 3:23 Comment(4)
Well don't forget to import/using System.Text.Json; so JsonElement is recognizedAndrey
@BenyaminLimanto System.Text.Json should be automatically added if you use it, if not then Visual Studio should recommend it as an addition automatically when it marks the line as an error.Bulldoze
@Bulldoze Not everyone is using Visual Studio.Bathtub
@Bathtub They should be, especially if they are writing ASP.NET code. It's even got a free version. That aside, "include the namespace that provides the functionality you want to use" should go without saying, anyway.Bulldoze
S
33

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

Sterilize answered 8/12, 2019 at 23:2 Comment(0)
A
8

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!

Archducal answered 28/8, 2020 at 17:34 Comment(1)
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.Number
R
5

The reason for this error is:

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

Revocable answered 13/12, 2022 at 14:52 Comment(0)
C
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());
    });
Changchangaris answered 16/10, 2019 at 21:21 Comment(0)
P
0

you can create another class contains your json field.

Partridgeberry answered 19/11, 2021 at 9:50 Comment(1)
Could you please provide an example? How is this posted in the body of the request.Been
M
0

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

Mistrustful answered 5/4, 2022 at 7:7 Comment(0)
A
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.

Adjunction answered 19/7, 2022 at 14:34 Comment(0)
B
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.

Banksia answered 29/8, 2022 at 2:33 Comment(0)
G
-2

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

Gimbals answered 29/9, 2019 at 13:15 Comment(1)
I cant change clients.Charged

© 2022 - 2024 — McMap. All rights reserved.