-1

Like many others (e.g. the 36 answers here) when I post a string to ASP.Net Core the value is always null. I tried [FromBody] but that results in my calls receiving a 400 error. Eventually I found this 'solution', which works:

[Route("api/[controller]")]
[ApiController]
public class ScoresController : ControllerBase
{
    [HttpPost]
    public void Post(string value)
    {
        # 'value' is null, so ...
        Request.EnableRewind();
        var body = "";
        using (var reader = new StreamReader(Request.Body))
        {
            Request.Body.Seek(0, SeekOrigin.Begin);
            body = reader.ReadToEnd();
        }
        value = body;
        # Now 'value' is correct, use it ...
    }
}

Here is an example of the value I post from the client (Python, using 'requests'):

'{"Username": "TestUserNumber5", "Email": "testuser5@nowhere.com", "PasswordHash": 5}'

and once I have retrieved it from the body in ASP.Net Core it looks like this:

"{\"Username\": \"TestUserNumber5\", \"Email\": \"testuser5@nowhere.com\", \"PasswordHash\": 5}"

I do not explicitly set the content-type, here's the Python code I use to post:

def add_score(score):
    score_json = json.dumps(score)
    response = requests.post(url+"scores/", data=score_json, verify=False)
    return response.ok

Reading the body a second time seems very hacky. Why do I need it?

dumbledad
  • 16,305
  • 23
  • 120
  • 273
  • 1
    What are setting your content-type as on the post request? – Molik Miah Mar 22 '19 at 12:28
  • It's strange to me that the whole thing is being sent as a string in the first place, instead of an object with three string properties. Is the former intended for any particular reason, or would the latter be preferred? – David Mar 22 '19 at 12:30
  • @David, it is strange, but when debugging null values I find it useful to make those values as simple as I can, e.g. a string not an object. When I am confident that values get through I may swap back, away from passing strings. – dumbledad Mar 22 '19 at 12:34

1 Answers1

0

There's some critical information that has been excluded: namely, whether or not this is an API-style controller, whether or not you're using the [ApiController] attribute, and how you're making the post (x-www-form-urlencoded, application/json, text/plain, etc. However, I'll make some assumptions based on the behavior you're experiencing.

If I had to guess, I'd say this is an API-style controller and you have the [ApiController] attribute applied. This will by default switch the binding to [FromBody], but only on class types, not primitive types such as string. As such, by default, your string param is mostly ignored. When you explicitly apply the [FromBody] attribute to the param, you're then forcing the request body to be bound to it, and what you're sending cannot be (hence the 400 Bad Request).

So that brings us to what you're posting. More likely than not, you're sending application/json as the content type, but your request body is just my string value. That's not valid JSON, and you get a 400. If you want to send just a string as JSON, then it needs to be a JSON string, which basically just means it needs to be wrapped in quotes: "my string value". You'll still need the [FromBody] attribute applied explicitly to the param, but if you add the quotes, it will come through.

UPDATE

And that would be why code is so important for a question. It looks like you're sending it from the client as a string and then you're binding it to a string in your ASP.NET Core app. However, the actual data itself is JSON.

The problem is that for a primitive type like string, the default binding is going to be FromForm. However, you're not sending a x-www-form-urlencoded request. Adding [FromBody] didn't work either because even though it looks like JSON, it's not being sent with a JSON content type. In fact, your content type is likely text/plain, and ASP.NET Core does not have a text/plain input formatter applied by default. You can change that, but that's really not ideal.

Plain simple, you've got an object that you want to transfer over HTTP to an API. That means you should be using an object-friendly format such as JSON or XML. On the Python side, send your request as actual JSON, not a string containing a serialized JSON object. On the ASP.NET Core side, create a class that has properties for the members of that JSON object and use that as your param. Then, you will have no issues.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • I've added controller info to the code listing. My understanding of [requests.post](http://docs.python-requests.org/en/master/user/quickstart/#more-complicated-post-requests) is that it swaps to `application/json` if you use the `json` parameter, so mine would not be. – dumbledad Mar 22 '19 at 14:57
  • Sadly when I swap back to accepting an object instead of a string the controller method is never called and the client receives a "bad request". That's hard to debug which is why I swapped to string, at least that got me to a breakpoint in the controller's method. – dumbledad Mar 24 '19 at 11:21
  • @dumbledad what is your code for accepting an object? Try to add ` services.Configure(options => { options.SuppressModelStateInvalidFilter = true; }); ` to `Startup.cs` to see the model error. – Edward Mar 25 '19 at 05:38