‘s’ is an invalid start of a value. Path: $ in ASP.NET Core

By FoxLearn 1/14/2025 6:57:36 AM   72
When working with ASP.NET Core API controllers, you might want to create an endpoint that can accept any string.

However, if you attempt to directly accept a string as an input in a POST request, you may encounter an issue where the request fails and you get an error like:

1
‘s’ is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.

This happens because ASP.NET Core automatically expects the request body to be formatted as JSON. If you send a plain string (without the proper JSON structure), the API cannot deserialize it properly.

In your API, you might write a controller like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using Microsoft.AspNetCore.Mvc;
using Serilog;
 
namespace Demo.Api.Controllers
{
    [ApiController]
    [Route("/api/[controller]")]
    [Produces("application/json")]
    public class TestController : ControllerBase
    {
        [HttpPost()]
        [ProducesResponseType(200)]
        [ProducesResponseType(404)]
        public string PostTest([FromBody] string text)
        {
            Log.Information(text);
            return text;
        }
    }
}

The endpoint above accepts a POST request with a body, expecting a string to be sent.

However, if you post a raw string (e.g., "hello world" without quotes), it will fail with the error mentioned earlier. This is because the endpoint expects the body to be formatted as JSON (with double quotes around the string), like this:

1
"hello world"

To clarify, the issue arises because ASP.NET Core automatically treats the request body as JSON, and raw strings aren't valid JSON unless they are wrapped in quotes.

Solution 1: Accepting a JSON Object Instead of a String

One possible solution is to change the input type to object, which allows the API to accept any JSON object (including a string wrapped in quotes). However, this still doesn't allow for plain string inputs without wrapping them in quotes, so it might not fully solve the problem if you specifically need raw strings.

1
2
3
4
5
6
7
8
[HttpPost()]
[ProducesResponseType(200)]
[ProducesResponseType(404)]
public string PostTest([FromBody] object text)
{
    Log.Information(text.ToString());
    return text.ToString();
}

With this approach, you can send any valid JSON object (e.g., { "a": "b" }), but you still cannot send an unquoted string without wrapping it in JSON format.

Solution 2: Reading the Request Body Directly

If you need to accept raw strings (such as plain text input), the best approach is to read the request body directly as a raw stream and then process the string accordingly.

1
2
3
4
5
6
7
8
9
10
11
[HttpPost()]
[ProducesResponseType(201)]
[ProducesResponseType(400)]
public async Task<IActionResult> PostTest()
{
    using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
    {
        string message = await reader.ReadToEndAsync();
        return Ok(message); // Returns the raw string
    }
}

In this solution, the StreamReader reads the raw request body as a string, allowing you to handle it without the need for it to be formatted as JSON. This approach will accept any string posted to the endpoint, whether it’s a single word, a sentence, or even more complex data.

The downside of this method is that it doesn't integrate well with Swagger or OpenAPI for testing. Swagger UI requires a structured request body, and this raw approach doesn't provide a place to input the body in the Swagger interface. To test it, you will need to use external tools like Postman or cURL.

Example request in Postman:

  • Method: POST
  • Body: hello world (as plain text)

In ASP.NET Core, when you attempt to accept a raw string in a POST request, the default behavior expects the body to be formatted as JSON. If you want to accept any string (including raw strings), you need to either:

  1. Accept the input as an object and handle it as JSON, but this still requires valid JSON formatting (i.e., wrapped in quotes).
  2. Read the request body directly as raw content using a StreamReader, which allows you to handle any string without the need for JSON formatting.

While the second option works best for truly raw strings, remember that it may require tools like Postman or cURL to test the endpoint due to the lack of integration with Swagger UI.