11

I have been working on ASP.NET Core from a few weeks. I was trying to achieve something based on this blog: Microservices

My project.json is as follows:

{
  "version": "1.0.0-*",
  "compilationOptions": {
    "emitEntryPoint": true
  },

  "dependencies": {

    "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final",
    "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
    "Microsoft.AspNet.Diagnostics": "1.0.0-rc1-*",
    "Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
    "EntityFramework.Core": "7.0.0-rc1-final",
    "EntityFramework.Commands": "7.0.0-rc1-final",
    "EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final",
    "EntityFramework.MicrosoftSqlServer.Design": "7.0.0-rc1-final",
    "Microsoft.AspNet.Mvc.Formatters.Json": "6.0.0-rc1-final",
    "Microsoft.AspNet.Mvc.Formatters.Xml": "6.0.0-rc1-final",
    "System.Security.Cryptography.Algorithms": "4.0.0-beta-23516"

  },

  "commands": {
    "web": "Microsoft.AspNet.Server.Kestrel",
    "ef": "EntityFramework.Commands"
  },

  "frameworks": {

    "dnxcore50": {
      "dependencies": {


      }

    }
  },

  "exclude": [
    "wwwroot",
    "node_modules"
  ],
  "publishExclude": [
    "**.user",
    "**.vspscc"
  ]
}

And ConfigureServices method in Startup.cs is as follows:

public void ConfigureServices(IServiceCollection services)
{
    //Registering Authorization Database
    AutorizationAccessRegisteration.RegisterComponents(services, Configuration);

    services.AddMvcCore()
        .AddJsonFormatters(a => a.ContractResolver = new CamelCasePropertyNamesContractResolver());

    //Add cors built in support.
    services.AddCors();

    services.AddMvcCore().AddApiExplorer();

    //Add MVC for supporting WebApi requests
    #region MVC Add

    services.AddMvc();

    services.AddMvc().AddMvcOptions(options =>
    {
        options.RespectBrowserAcceptHeader = true;

        // Input Formatters.
        options.InputFormatters.Clear();

        var jsonInputFormatter = new JsonInputFormatter()
        {
            SerializerSettings = new JsonSerializerSettings()
            {
                ContractResolver = new CamelCasePropertyNamesContractResolver()
                ,
                DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,
                NullValueHandling = NullValueHandling.Ignore
            }
        };


        options.InputFormatters.Add(jsonInputFormatter);

        //Output formater
        //as part of get/post request, set the header Accept = application/json or application/xml
        var jsonOutputFormatter = new JsonOutputFormatter();
        jsonOutputFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
        jsonOutputFormatter.SerializerSettings.DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Ignore;
        options.OutputFormatters.Insert(0, jsonOutputFormatter);

        options.OutputFormatters.Insert(1, new XmlDataContractSerializerOutputFormatter());

    });

    #endregion
}

And here is my Confiure method in Startup.cs:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {

    }
    else if (env.IsStaging())
    {

    }
    else if (env.IsProduction())
    {

    }

    app.UseIISPlatformHandler();

    app.UseCors(builder =>
            builder.WithOrigins("*").AllowAnyHeader().AllowAnyMethod());

    //Middlewares addition to the pipelines:
    /// We add the middlewares in the following fashion:
    /// - Exception Handler
    /// - Logger
    /// - Authorization Handler
    /// There is a big reason of doing that.
    ///
    app.UseExceptionHandler();
    app.UseLoggerHandler();
    app.UseAuthorizationHandler();

    app.UseMvc();
}

An AuthorizationController is as follows:

[Route("api/Authorization")]
public class AuthorizationController : Controller
{
 .
     [HttpPost]
     public void Post([FromBody]string value)
     {
     }
 .
}

The Post method originally had [FromBody]string[] value. I simplified it further by making it a simple string type. I am using Advance Rest Client on Chrome to send an HTTP request. When string[] was the type I was the following values in body:

{

  ["value","sdjklgsdjlg"]

}

After simplifying the parameter, I tried sending request with the following body:

{"sdjklgsdjlg"}

tried this as well:

{"value":"sdjklgsdjlg"}

Am I missing something? I read before, the way old WebApi used to work in relation to the JSON mapping to the complex objects and normal parameters, it works in similar fashion in .NET Core.

Also I should elaborate that breakpoint is hit normally on all the middlewares and controllers. But none of the middleware seems to be able to read Request's stream related things:

context.Request variable problems

context.Request.Body errors

Kindly tell me where I am making problem. Thanks a lot!

Red
  • 2,728
  • 1
  • 20
  • 22
FreshDev
  • 328
  • 1
  • 3
  • 18
  • What does your network sniffer say about it? Can you paste the capture of the http session? Also, can you paste the whole error message? – matcheek Mar 18 '16 at 11:02
  • What do you mean? I am using the Advance Rest Client. It shows correct Request Headers and raw body as I mentioned above. – FreshDev Mar 18 '16 at 11:04
  • have you tried it without [FromBody]? I think it will work without that – Joe Audette Mar 18 '16 at 11:06
  • I did try that before, tried just now again. But don't you think if the value was not showing up in the controller only then this `[FromBody]` might have been doing the problem. But that's not the case. I posted two pictures, which are of my first ExceptionMiddleware and you can see what errors are being showing up in the Body and Form variables of the request. – FreshDev Mar 18 '16 at 11:15
  • You add MVC twice, is that normal ? and MvcCore is added twice too. that looks weird – agua from mars Mar 18 '16 at 11:28

6 Answers6

5

This works for me:

[HttpPost]
public async Task<IActionResult> CreateApp([FromQuery]string userId)
{
    string appDefinition = await new StreamReader(Request.Body).ReadToEndAsync();
    var newAppJson = JObject.Parse(appDefinition);
...
bc3tech
  • 1,228
  • 14
  • 27
  • This would be more accurate: `using var sr = new StreamReader(request.Body); var appDefinition = await sr.ReadToEndAsync();` – Sergey Aug 01 '23 at 23:35
4

[FromBody] uses the registered formatters to decode the entire body of the submitted data into the single parameter it is applied to - by default, the only registered formatter accepts JSON.

In JSON, there is no valid way to represent a string directly - {"sdjklgsdjlg"} is not valid JSON, {"value":"sdjklgsdjlg"} is, but won't deserialize to a simple string parameter. EDIT: See the answer by @tmg, this can be done using the syntax { "": "sdjklgsdjlg" }

Therefore, you'll need some sort of specific model class to represent the input you're trying to get from the body, e.g.:

public class AuthRequest {
    public string Value { get; set; }
}

Then you should be able to successfully do:

[Route("api/Authorization")]
public class AuthorizationController : Controller
{
    [HttpPost]
    public void Post([FromBody]AuthRequest authReq)
    {
        // authReq.Value should have your value in
    }
}

Now if you post { "Value": "some value" } to this, it should do what you expect.

Mark Hughes
  • 7,264
  • 1
  • 33
  • 37
  • In old WebApi 2 controllers I had been working with accepting Strings `FromQuery` but it does not work here as well. Well your DTO trick did allow me to get the values from the request to be mapped to my method. But I am still not able to read the Request.Body and other things involving the context. – FreshDev Mar 18 '16 at 12:07
  • @FreshDev You can still do that using [FromQuery] for query parameters - just not for body binding - if you request `api/Authorization?value=horse` then use `[FromQuery]string value` it should parse that correctly. – Mark Hughes Mar 18 '16 at 12:20
  • thanks for this @mark hughes, I was struggling with this for a while, great explanation – lmiller1990 May 25 '17 at 01:58
4

Mark Hughes answer is corrent until some point. If you post a json of this format { "": "sdjklgsdjlg" } modelbinder should be able to bind it to simple string model, without needing the wrapper model.

tmg
  • 19,895
  • 5
  • 72
  • 76
2

I am late here, but want to share the exact reason so any other user can get accurate info.

You are not able to get values at controller because you are posting data as JSON object:

{"value":"sdjklgsdjlg"} //See the curly braces represent an object.

For resolving this we need another object to bind this data to. Something like this at controller's action:

[HttpPost]
public void Post([FromBody]CustomViewModel data)
{
     ...
    //here you can get value as: data.Value
}

here CustomViewModel is a class:

public CustomViewModel
{
    public string Value { get; set; }
}

If you want to get data as per your current action signature:

[HttpPost]
public void Post([FromBody]string value)
{
     ...
}

Then, you need to pass data as JSON string in request body:

"sdjklgsdjlg" //notice without any curly braces and property name

Similarly for string array action:

[HttpPost]
public void Post([FromBody]IEnumerable<string> values)
{
     ...
}

pass JSON array of string in request body:

["item1", "item2"]
Prateek Pandey
  • 833
  • 6
  • 19
  • When I passes only string in body: "sdjklgsdjlg" then I got this Exception from Web API. "No MediaTypeFormatter is available to read an object of type 'String' from content with media type 'text/plain'." – K T Feb 12 '18 at 04:03
1

You can also do something like this:

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

or user [FromQuery] and pass directly a Querystring value.

Luca Ghersi
  • 3,261
  • 18
  • 32
  • What body are you sending? Anyway, I humbly suggest you to simplify you configuration: you're adding MVC 4 times in a row, I don't know what happen when you do it multiple times. You're also adding Cors twice. – Luca Ghersi Mar 21 '16 at 08:10
  • I have tried various body. But cannot make any of it work. Not even the simplest ones like: `{"value":"someValue"}` I want to read that in raw(bytes) form and then convert it to string. But none of the properties connected to stream are available. As for your saying I am adding MVC multiple times. Then that's not the case. We have to register services in `ConfigureServices` method and then `use` them in `Configure` method. That's exactly what I am doing. – FreshDev Mar 21 '16 at 10:18
  • If you see no content at all my advice for you is to go simple. Remove everthing you don't need, no auth, no cors. Go plain and simple. You can also try creating a demo app with visual studio template, and then check the differences. – Luca Ghersi Mar 21 '16 at 10:55
  • I will surely do that. But its not like I cannot see any Content. The property: context.Request.ContentLength is set correctly(meaning it has different values depending on the payload I am sending). Also my values in the payload does get mapped to the variables in the controller. But I am not able to read the Request stream in the middlewares. – FreshDev Mar 21 '16 at 11:05
  • Sorry, I get you now. You can't read the Request stream twice (web api read it first); to do this, try to reset is position first like this HttpContext.Current.Request.InputStream.Position=0. Check this: https://stackoverflow.com/questions/21971467/why-cant-i-read-http-request-input-stream-twice – Luca Ghersi Mar 21 '16 at 11:07
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/106903/discussion-between-freshdev-and-luca-ghersi). – FreshDev Mar 21 '16 at 11:11
  • I did know about this problem(reading request body multiple times). But if you look at the snapshots I attached to my question, I am encountering some other problem, since the context.Body.Position already shows 0 as value. – FreshDev Mar 21 '16 at 12:11
  • dynamic worked for me. My need was simply to log the request body and used var valueToLog = Newtonsoft.Json.JsonConvert.SerializeObject(value); – Stephen McDowell Sep 02 '17 at 13:00
0

You should use the RC2 version of all your dependencies.
There is https://github.com/aspnet/KestrelHttpServer/issues/915 I have found that version of System.Threading.Tasks.Extensions is 4.0.0 by default. So you should explicitly specify the version of this package in the project.json file:

"System.Threading.Tasks.Extensions": "4.0.0-rc2-24027"