ASP.NET core - how to pass optional [FromBody] parameter? - asp.net-core

How do I pass optional (nullable) [FromBody] parameter in ASP.NET Core (5.0)? If I don't send body in my request I get 415 Unsupported Media Type error. Can this be configured and if so, how to do it on a controller or action, rather than an app level? I presume it has to do something with model validation, but not sure. Thanks.
[HttpGet("[action]")]
public async Task<IActionResult> GetElementsAsync([FromBody] IEnumerable<int> elements = default)
{
var result = await dataService.GetData(elements);
return Ok(result);
}
EDIT: To clarify:
This is typical scenario and it works normally:
But passing empty body is returning 415 right away without even reaching action:

You can find a solution here:
https://github.com/pranavkm/OptionalBodyBinding
From this issue on github:
https://github.com/dotnet/aspnetcore/issues/6878
And from .net Core 5 you can use this one:
public async Task<IActionResult> GetElementsAsync([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] IEnumerable<int> elements = default)
...
Also needed (from Pawel experience):
services.AddControllers(options =>{options.AllowEmptyInputInBodyModelBinding = true;})

Just add content-type in your request header. Without the content-type:application/json will appear 415 when body is empty.
No changes to your controller. Test in my side is ok.
I created a new asp.net core 5 api project and this is my controller:
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
namespace WebApi_net5.Controllers
{
public class HomeController : ControllerBase
{
[HttpGet("[action]")]
public string GetElementsAsync([FromBody] IEnumerable<int> elements = default)
{
return "value";
}
}
}

With ASP.NET Core 3.1, I could allow nullable optional parameters by implementing Nicola's suggestion:
services.AddControllers(options =>{options.AllowEmptyInputInBodyModelBinding = true;})

I will address some points that were not mentioned here.
To get rid of 415 without sending Content-Type you need to create your custom consumer of the Content-Type
https://stackoverflow.com/a/65813534/2531209
But I would say this is an overkill
If you pass Content-Type: application/json in your request header you will get "Body cannot be empty" (Tested on .NET 6) and only then #Nicola answer comes in handy.
From my tests it looks like modifications to the controller are not needed and only FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow) is enough with nullable type as parameter.
Or you can change nothing in your current code and send a header Content-Type: application/json with a body of {}. This will bypasses all of those errors, but this is not the most optimal solutions for public API's

Related

Can't perform HTTP Post Action from Logic App to Asp.net Core Web API

I've built many Logic Apps. I've also integrated with the Logic App API. For some reason, a Post request to an Asp.net Core Web API won't work. It works in Postman, but I can't get Logic Apps to complete the request.
The request arrives at my Web API. I can step through it during a remote debug session. I'm using the [FromBody] decorator on the API method. All the string values in the object are null.
Logic App Headers
Accept = "application/json"
ContentType = "application/json"
ContentLength = "35"
Host = "****.centralus.logic.azure.com"
API method
[HttpPost]
[Route("CreateSomething")]
public async Task<IActionResult> CreateSomething([FromBody] MyObject object)
{
//Create something great
}
I think it might have something to do with the Headers. I noticed that the Postman request won't succeed unless I check the Host and Content-Length box in the Headers section. According to this article, Logic Apps ignores those Headers.
https://learn.microsoft.com/en-us/azure/connectors/connectors-native-http
I've built the HTTP Post Action using the API as well as configured it manually using the Logic App UI in Azure.
By the way, does anyone know the Expression that will automatically calculate the ContentLength?
UPDATE:
I finally figured this out. I had to do some Ninja coding crap to make this work. I'll post my solution tomorrow.
Does anyone know how to make this work? Thanks in advance!
When you use the Logic App API to programmatically create Logic Apps, you have to specify the Body class for when you do something like an HTTP Post. When the Body JSON displayed in the designer, it contained a single object with the objects properties. My API method could not handle this. The key was to simply post the properties in the JSON Body. To make matters worse, I'm doing two HTTP Posts in this particular Logic App. When I tried to add my object properties to the existing Body class, it caused my other HTTP Post to stop working. To overcome this, I had to create a Body2 class with the objects properties. I then had to use the following line of code to replace body2 with body before adding the JSON to the Logic App API call.
This did not work.
body = new Body()
{
object = new Object()
{
//Properties
}
}
This worked.
body2 = new Body2()
{
Type = 0,
Description = "#{items('For_each_2')?['day']?['description']}",
Locations = item.Locations,
Cold = "#{items('For_each_2')?['temperature']?['cold']?['value']}",
Hot = "#{items('For_each_2')?['temperature']?['hot']?['value']}",
Hide = 0
}
Notice I used Replace on body2.
var options = new JsonSerializerOptions { WriteIndented = true, IgnoreNullValues = true};
string jsonString = ReplaceFirst(JsonSerializer.Serialize(myApp, options), "schema", "$schema").Replace("_else", "else").Replace("_foreach", "foreach").Replace("body2", "body");

How does Swagger achieve the same route but different query parameters?

I created an asp.net core web api project, using the .net5 version, and I have a route like this.
[Route("api/detail")]
public IEnumerable<User> Get()
{
//TODO
return users;
}
[Route("api/detail")]
public IEnumerable<User> Get(string name)
{
//TODO
return users;
}
Although my request method is the same and the request parameters are different, the 500 error will be reported in swagger. Is there any way to solve it? Any help is greatly appreciated.
There could be multiple reasons why you're getting a 500 error. When I pasted your code into a new controller the first is error I received was:
Ambiguous HTTP method for action... Actions require an explicit HttpMethod binding for Swagger
It's telling you that you need to decorate each action in the controller with an HttpMethod binding, like [HttpGet]. More on that in a second...
The next issue is that you're using [Route] to bind two different action methods to the exact same route with the same HttpMethod. That's not possible in an API controller.
Conflicting method/path combination... Actions require a unique
method/path combination for Swagger
My preferred method for routing is to use Attribute routing with Http verb attributes.
The first step would be to move the route attribute to the controller. I'm going to assume you've created a DetailsController:
[Route("api/[controller]")]
[ApiController]
public class DetailsController : ControllerBase { }
Now, update your actions. Remove the [Route] attribute, replace with the HttpGet attribute, and add the name parameter to your second endpoint. I also prefer to return an IActionResult:
[HttpGet]
public IActionResult Get()
{
//TODO
return Ok(users);
}
[HttpGet("{name}")]
public IActionResult Get(string name)
{
//TODO
return Ok(users);
}
Note that parameters are identified by using curly braces around the variable {name} in the Http method attribute. Both endpoints work and are accessible through swagger. I urge you to read the linked page above for a better understanding of the possible routing options (linked again).

Can't use Json() in asp.net core web api as it in asp.net core web

In asp.net core web I create a controller and I can use:
return Json(new {status=true});
but in asp.net core web API I can't do it.
In a controller:
[HttpGet("{id}")]
public JsonResult Get(int id)
{
}
I can not return Json()
How to use it?
Asp.Net Core Web API does provide support for wide varieties of response types, with Json being one among them. You can do that like shown below. Make sure you have all your required dependencies. You can learn about the dependencies from the documentation link I attached in this answer.
[HttpGet]
public IActionResult Get()
{
return Json(model);
}
You can also specify strict response formats using the [Produces] Filter on your controller.
Configuring Custom Formatters
You can also configure your own custom formatters in Asp.Net Web API project by calling the .AddFormatterMappings() from ConfigureServices method inside of your Startup.cs. This allows for a greater control on your content negotiation part and lets you achieve strict restrictions.
Please go through this documentation to understand further.
Using Responses with Status Codes
However, when using Web API, I suggest you use the helper methods that are built in so that your response becomes more expressive as it contains both the response content along with the status code. An example of how to do that is below
[HttpGet]
public ActionResult Get()
{
return Ok(_authors.List());
}
For a full list of helper methods available, you can take a look at the Controller.cs and ControllerBase.cs classes.
Asp.net core web api inherit from controllerBase, which doesn't contain a Json(Object) method. You should initialize a new JsonResult yourself in the action.
[HttpGet("{id}")]
public JsonResult Get(int id)
{
return new JsonResult(new { status = true });
}

Swagger showing no parameters to controller actions that have paramters

Swagger not showing parameters in UI and JSON, even tho my method has parameters, This particularly happens when I add the [FromBody] tag
swagger UI no parameters
JSON file no parameters
The action method:
[HttpPost("Action")]
public IActionResult Action([FromBody] string message)
{
return Ok(message);
}
I used fresh Asp.net core 3.1 and 2.2 web app with API template to test this,
I configured it exactly like the documnetaiton
ConfigureServices:
services.AddMvc();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
}
Configure:
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
It does work when I use other attributes like [FromRoute]&[FromHeader] so on... I looked at the examples and swagger does show parameters from a post method
I also tried objects like this :
public class Message
{
public int ID { get; set; }
public string body { get; set; }
}
With this action :
[HttpPost("Action")]
public IActionResult Action([FromBody] Message message)
{
return Ok(message);
}
same result (no parameters) but it does show the schema
So what am I doing wrong? how can I document post parameters like the examples
I had a similar problem on AspNetCore 5.0, where "complex" types would be completely ignored, both as parameters and output types.
The problem was caused by switching input/output formatters to a different JSON serializer (legacy code, due to newtonsoft being slow in certain situations). The new swashbuckle can work either with System.Text.Json or Newtonsoft, but not a 3rd solution.
So check .AddMvcOptions() in ConfigureServices, if there is anything done with InputFormatters or OutputFormatters, that might be the culprit
You should look into this code sample as you are working with OpenAPI 3.0 : http://petstore.swagger.io:8080/
OpenAPI 3.0 provides the requestBody keyword to describe request bodies. It distinguish the payload from parameters (such as query string and PATH). The requestBody is more flexible in that it lets you consume different media types, such as JSON, XML, form data, plain text, and others, and use different schemas for different media types:
https://swagger.io/docs/specification/describing-request-body/
That is default UI and if you click "Try it out" , the sample value will auto fill Request body area to help create a test request body .

Read raw Request.Body in Asp.Net Core MVC Action with Route Parameters

I need to process the raw request body in and MVC core controller that has route parameters
[HttpPut]
[Route("api/foo/{fooId}")]
public async Task Put(string fooId)
{
reader.Read(Request.Body).ToList();
await _store.Add("tm", "test", data);
}
but It seems like the model binder has already consumed the request stream by the time it gets to the controller.
If I remove the route parameters, I can then access the request stream (since the framework will no longer process the stream to look for parameters).
How can I specify both route parameters and be able to access Request Body without having to manually parse request URI etc.?
I have tried decorating my parameters with [FromRoute] but it had no effect.
Please note I cannot bind the request body to an object and have framework handle the binding, as I am expecting an extremely large payload that needs to be processed in chunks in a custom manner.
There are no other controller, no custom middle-ware, filters, serialzier, etc.
I do not need to process the body several times, only once
storing the stream in a temp memory or file stream is not an options, I simply want to process the request body directly.
How can I get the framework to bind paramters from Uri, QueryString, etc. but leave the request body to me?
Define this attribute in your code:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
If you're targeting .Net Core 3 you also need to add
factories.RemoveType<FormFileValueProviderFactory>();
Now decorate your action method with it:
[HttpPut]
[Route("api/foo/{fooId}")]
[DisableFormValueModelBinding]
public async Task Put(string fooId)
{
reader.Read(Request.Body).ToList();
await _store.Add("tm", "test", data);
}
The attribute works by removing Value Providers which will attempt to read the request body, leaving just those which supply values from the route or the query string.
HT #Tseng for the link Uploading large files with streaming which defines this attribute
As I suspected the root cause was MVC inspecting the request body in order to try to bind route parameters. This is how model binding works by default for any routes that are not parameter-less, as per documentation.
The framework however does this only when the request content type is not specified, or when it is form data (multipart or url-encoded I assume).
Changing my request content-type to any thing other than form data (e.g. application/json) I can get the framework to ignore the body unless specifically required (e.g. with a [FromBody] route parameter). This is an acceptable solution for my case since I am only interested accepting JSON payloads with content-type application/json.
Implementation of DisableFormValueModelBindingAttribute in Uploading large files with streaming pointed out by #Tseng seems to be a better approach however, so I will look into using that instead, for complete