81

Similar to this old question about prior ASP.NET versions, I want to get the request body of an HTTP POST to be bound to a string. It seems that the method binds, but that value is null, when ASP.NET invokes my controller method:

namespace Demo.Controllers
{

    [Route("[controller]")]
    public class WebApiDemoController : Controller
    {
    ...

    // POST api/values
    [HttpPost]
    public System.Net.Http.HttpResponseMessage Post([FromBody]string value)
    {
       // expected: value = json string, actual: json = null.
    }

Do I still have to go grab the body from a stream? Or should this just work? When testing the above method, I used the following http headers:

Accept: Application/json
Content-Type: Application/json;charset=UTF-8

I'm passing in the following in the body: { "a": 1 }

I do NOT want to bind to a string variable named a. I want to bind any JSON I get, and then I want to use the JSON content, any arbitrary content at all, from within my method.

If I understood the documentation, the [FromBody] attribute should have done what I wanted, but I'm guessing that the ASP.NET core MVC binding mechanism won't bind a json to a "string value", but perhaps I could do something else that gets me an equivalent level of flexibility.

A similar question here gives me the idea maybe I should have written [FromBody] dynamic data instead of using [FromBody] string value.

Update: There are answers here for .net core 6 and other modern .net core versions.

4
  • Have you checked that anything is actually being passed in the request body? Fiddler? Commented Aug 11, 2015 at 21:13
  • 1
    Just checked in Fiddler and the body content is fine. Commented Aug 11, 2015 at 21:14
  • Your passing a name/pair value a: 1 so the parameter would need to be string a (assuming your expecting to receive 1 Commented Aug 12, 2015 at 0:50
  • 2
    I don't want a concrete model class type, I originally wanted a string but I think maybe what I actually need to do is use dynamic here. stackoverflow.com/questions/23135403/… Commented Aug 12, 2015 at 14:14

11 Answers 11

74

The cleanest option I've found is adding your own simple InputFormatter:

public class RawJsonBodyInputFormatter : InputFormatter
{
    public RawJsonBodyInputFormatter()
    {
        this.SupportedMediaTypes.Add("application/json");
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
    {
        var request = context.HttpContext.Request;
        using (var reader = new StreamReader(request.Body))
        {
            var content = await reader.ReadToEndAsync();
            return await InputFormatterResult.SuccessAsync(content);
        }
    }

    protected override bool CanReadType(Type type)
    {
        return type == typeof(string);
    }
}

And in your Startup.cs inside ConfigureServices:

services
    .AddMvc(options =>
    {
        options.InputFormatters.Insert(0, new RawJsonBodyInputFormatter());
    });

That will let you get at the raw JSON payload in your controllers:

[HttpPost]
public IActionResult Post([FromBody]string value)
{
    // value will be the request json payload
}
Sign up to request clarification or add additional context in comments.

7 Comments

That's beautiful actually. Nice one. Thank you.
This answer is better because it really takes string as it is (in other words it does not parse string to JSON until you decide to do it yourself)
It seems like I did test and that didn’t work either. Now I would like to retest to confirm...
Will this mess with any of the default input formatting? Any tips for how I could add this just to specific methods, e.g. with an attribute? thx.
Changed accepted answer in 2022 in honor of this being much more upvoted than the other clean and safe answer.
|
55

The following works in .net core 1.x, but not in .net core 2.x.

As I commented, the solution is to use [FromBody]dynamic data as my parameter list, using dynamic instead of string, and I will receive a JObject.

Caution: If your architecture calls for a single WebApi server to be equally fluent in producing XML and JSON, depending on content-type header entries, this kind of direct-JSON-consumption strategy can backfire on you. (Supporting both XML and JSON on the same service is possible with sufficient work, but then you're taking stuff that was further UP the MVC asset pipeline and moving it down into your controller methods, which turns out to be against the spirit of MVC, where models come to you as POCOs already parsed.)

Once you convert to a string inside the method, converting the incoming JObject (Newtonsoft.JSON in memory data type for JSON) to a string.

Found at other answer here.

Sample code, thanks to Jeson Martajaya:

With dynamic:

[HttpPost]
public System.Net.Http.HttpResponseMessage Post([FromBody]dynamic value)
{
   //...
}

Sample code with JObject:

[HttpPost]
public System.Net.Http.HttpResponseMessage Post([FromBody]Newtonsoft.Json.Linq.JObject value)
{
   //...
}

7 Comments

This doesn't work in Microsoft.AspNetCore.Mvc.Core 2.0.0 I get a model but I can't seem to get the properties e.g. [FromBody] dynamic data => data.[someproperty] !property not found exception
If you figure it out, feel free to edit my answer and add the equivalent .net core 2 code. Maybe you just need to add some using to bring in some helpers. Maybe the design of dynamic has changed.
Late the party here, but I'm just letting everyone know that the JObject method does work in 2.2.
You can submit an edit. The answer should be evolved to remain correct in 2020.
@WarrenP for .NetCore 3.0+ you will need to explicitly reference Microsoft.AspNetCore.Mvc.NewtonsoftJson package and then activate it in a startup: services.AddMvc().AddNewtonsoftJson()
|
36

Found a solution for ASP.NET Core 3.1 Web API.

Looks like following:

public async Task<IActionResult> PutAsync([FromBody] System.Text.Json.JsonElement entity)
{ 
    // your code here
}

2 Comments

Works for 3.0 as well
The original question was written a long time ago so I updated the title to state that it's for ASP.NET Core 2 that the question was written. I think a different question and fresh answer for .net core 3.1 would make sense.
19

The following two methods works in ASP.NET core 2 to read the raw json string.

1) This one has better performance.

    [HttpPost]
    public async Task<ActionResult<int>> Process()
    {
        string jsonString;
        using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
        {
            jsonString = await reader.ReadToEndAsync();
        }

2)

    [HttpPost]
    public async Task<ActionResult<int>> Process([FromBody]JToken jsonbody)
    {
        var jsonString = jsonBody.ToString();

2 Comments

Second doesn't work in Asp.Core 3.1 . Tried with Newtonsoft.Json.Linq.JToken.
First works in .net 6
7

Alternatively, you could also just accept a JObject and you would be able to use Linq to Json ot even directly ToString() if you really need the string.

7 Comments

I think that's exactly what I said in my answer, using dynamic results in the JObject being bound
Why using dynamic, then ? You'd loose the compile time validations and the intellisense :S
Sometimes you don't want validation, you want a flexible auto-query feature. Also if your destination accepts ANYTHING (you're writing to a NoSQL document database), C# model classes are a limitation not a feature.
@WarrenP i definitely pioneered projects with object databases, c# model classes are absolutely a feature. If you don't have defined models whether it's c# classes, swagger definitions, or whatever, you're going to end up in a world of hurt when you can no longer index your data reliably. object databases are not schemaless, they are loose schema. If you don't have a more rigid structure such as c# classes that goes through versioning properly, you really are facing a pending implosion.
I have started to realize that after doing way too much freeform mess.
|
2

Based on Saeb Amini's excellent answer above, this extends his solution to be for plain-text as well. The only changes here are adding the "text/plain" mime-type, and adding a namespace and required usings.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Formatters;

namespace AspExtensions // or whatever
{
    // see: https://stackoverflow.com/a/47807117/264031
    public class RawStringBodyInputFormatter : InputFormatter
    {
        public RawStringBodyInputFormatter()
        {
            this.SupportedMediaTypes.Add("text/plain");
            this.SupportedMediaTypes.Add("application/json");
        }

        public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
        {
            var request = context.HttpContext.Request;
            using(var reader = new StreamReader(request.Body)) {
                string content = await reader.ReadToEndAsync();
                return await InputFormatterResult.SuccessAsync(content);
            }
        }

        protected override bool CanReadType(Type type)
        {
            return type == typeof(string);
        }
    }
}

2 Comments

Since this answer is 4 years after the original question, what .net core version was this answer for?
I have at least a version of this in production on .NET 6, and I doubt there were any breaking changes since that time. This definitely still works and is still needed
1

If you don't mine forgoing the automagic binding, this can be placed directly in an Http handler on a Controller:

using StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8);
var value = reader.ReadToEndAsync().GetAwaiter().GetResult();

3 Comments

An interesting idea. What .net core version did you try this in? The original question was a long time ago in .net core 2
@WarrenP I don't remember. It was reasonably current when I posted however.
It seems likely to be a fragile way to work as it will probably break when the underlying framework changes.
0

declare content type by attribute.

[HttpPut("{id}"), Consumes("text/plain", "application/text")]
public IActionResult EditScriptContent(string id)
{
    using (StreamReader reader = new(Request.Body, Encoding.UTF8))
    {
        string content = await reader.ReadToEndAsync();
        ...
    }
}

1 Comment

This is roughly a duplicate to the answer from 2021 by BCS and seems like a fragile and unmaintainable pattern. It does contain more detail than BCS's answer but it seems to me wrong to go direct to a StreamReader on the Request.Body.
-1

I see that Sam has already been down voted for saying pretty much the same thing, but in testing using Postman I find that if I set the request body to just a simple double quoted string ASP binds it fine with the default '[FromBody]string value' argument.

"just send your string like this without any curly braces"

Not sure whether application/json is supposed to accept data in this format. Hopefully by posting this someone knowledgeable will pipe up and state whether this is valid or not.

1 Comment

That's no longer JSON. HTTP post bodies can contain anything you like. But if you're writing restful servers the bodies should be content type json and have a body which is json, or content type XML and have a body which is xml. Whatever that is, is not either one.
-3

You need a type to bind the data. Example:

public class Person
{
   public string Name {get; set;}
}

for data { "Name" : "James"}

3 Comments

I can get what I need with dynamic value. It's not a string, it's a JSON dictionary but that's fine.
Sure, that would work but wondering how far can you go with that...if you want to add validations to your models etc. you still need a type though right?
That's the whole point, you pick one option, fixed (not flexible) and validated or flexible (and not validated)
-9

If you want to receive a string you need to pass it as a string. Your JSON should be enclosed in quotes:

'{ "a": 1 }'

2 Comments

Why the downvotes? This is the same answer as the one from @Neutrino which came two years later with 2 upvotes?
I've tried this solution but it didn't work for me but you are right. People had gave @Neutrino upvote but both answers are the same. I think people, who gave downvotes did for the purpose of completing critic badge.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.