Complete examples using numerous methods for POSTing and retrieving data via special attributes set on .NET Core WebAPI methods ([FromQuery], [FromForm], [FromBody], [FromHeader].
GitHub - raddevus/BindApi: Sample code for article for using auto-bind in dotnet webapi and JavaScript Fetch[^]
Introduction
This is a fast article with multiple examples of how to leverage the user of .NET Core WebAPI Auto-Binding feature. I know this is often called Model Binding, but my examples cover the case where you are just passing in one parameter value to a WebAPI method.
I also wrote this up at my blog in a slightly less refined article so you can check there for more details & to post comments if you like: JavaScript Fetch With .NET Core WebAPI: Fetch Method Examples For Auto Binding – Build In Public Developer[^]
How This Article Will Work
I will provide two things for every example so you can see exactly how the Auto-Binding feature works in .NET Core WebAPI.
- WebAPI method definitions (sample code via minimal API)
- JavaScript Fetch examples you can use to POST to the WebAPI & see the result
WebAPI Parameter Attributes
.NET Core WebAPI methods allow you to mark parameters with a numnber of different attributes:
[FromQuery]
[FromForm]
[FromBody]
[FromHeader]
[FromServices]
* [FromKeyedServices]
*
*The last two are brand new to .NET Core 8.x & I will not cover them here. However, I will cover the first four Attributes with full examples.
Background
While writing another article where every user gets their own database (coming soon) I hit an issue with Auto-Binding parameters in .NET Core WebAPIs.
I have written a number of .NET Core WebAPIs but I've noticed that at times it has been easier than others. I generally like my WebAPIs to use [FromBody]
to get the data from the posted body.
However, I discovered the exact reason why this would fail for me in certain situations.
Using FromQuery On the WebAPI Parameter
Let's start out with what I consider the simplest method of posting data, using the [FromQuery] attribute on the WebAPI method.
Source Code
- I'll add the shortest amount of code here that makes it possible to create a quick discussion but if you want to see the source code you can download it from the top of this article or get it at my GitHub repo.
- I'll create very small project of minimal WebAPIs and the methods will be named the same in almost every case, except they will include a number for each example so you can fired up the WebAPI locally and try the examples yourself if you like.
Start WebAPI: Use Browser To Test
Once you start the WebAPI (from the downloaded code) you can load the URL and use your browser console to use JavaScript Fetch API to POST to the WebAPI. Here's snapshot of what that looks like:
[FromQuery] Sample Code
[HttpPost] public ActionResult RegisterUser1([FromQuery] string uuid)
In our Minimal API you will see this method defined in the following way:
app.MapPost("/RegisterUser1", IResult ([FromQuery] string uuid) => {
return Results.Ok(new { Message = $"QUERY - You sent in uuid: {uuid}" });
});
JavaScript Fetch For [FromQuery]
fetch(`http://localhost:5247/RegisterUser1?uuid=987-fake-uuid-1234`,
{method: 'POST'})
.then(response => response.json())
.then(data => console.log(data));
That one is easy enough. If you provide the queryString item (?uuid
) in the URL then the item will be auto-bound to the uuid
string variable and you'll get a valid result back. However, if you don't provide the queryString value, then an error will occur in the WebAPI when it attempts to auto-bind.
Error
An unhandled exception has occurred while executing the request.
Microsoft.AspNetCore.Http.BadHttpRequestException:
Required parameter "string uuid" was not provided from query string.
Using FromForm On the WebAPI Parameter
Let's define our second WebAPI method using the [FromForm]
attribute.
app.MapPost("/RegisterUser2", IResult ([FromForm] string uuid) => {
return Results.Ok(new { Message = $"FORM - You sent in uuid: {uuid}" });
})
NOTE - AntiForgery
As soon as I started running Fetch against the command above, I started getting an odd error message on the WebAPI side which looked like:
Unhandled exception. System.InvalidOperationException: Unable to find the required
services. Please add all the required services by calling
'IServiceCollection.AddAntiforgery' in the application startup code.
at Microsoft.AspNetCore.Builder
.AntiforgeryApplicationBuilderExtensions
.VerifyAntiforgeryServicesAreRegistered(IApplicationBuilder builder)
Breaking Change To .NET Core Minimal WebAPIs
Luckily I was able to search and discover what the problem is and how to fix it. Sheesh!
Breaking change: IFormFile parameters require anti-forgery checks - .NET | Microsoft Learn[^]
Slightly Changed WebAPI for FromForm
app.MapPost("/RegisterUser2", IResult ([FromForm] string uuid) => {
return Results.Ok(new { Message = $"FORM - You sent in uuid: {uuid}" });
})
.DisableAntiforgery()
Sheesh! It's always something!
JS Fetch Call For FromForm
There's some setup to pass our data on a web form. First we have to create the FormData
object and add our name/value pairs. After that we can post the data.
var fd = new FormData();
fd.append("uuid", "123-test2-2345-uuid-551");
fetch(`http://localhost:5247/RegisterUser2`,{
method:'POST',
body:fd,
})
.then(response => response.json())
.then(data => console.log(data));
Now that we've used two different attributes that have worked well. Let's delve into using [FromBody]
attribute which will present great difficulty.
Using FromBody On the WebAPI Parameter
app.MapPost("/RegisterUser3", IResult ([FromBody] string uuid) => {
return Results.Ok(new { Message = $"FORM - You sent in uuid: {uuid}" });
})
JavaScript Fetch Call for FromBody Is Problematic
At first look, this one should be easy, because you may think you should just be able to pass in a string value on the body. That's what I thought, anyways.
Doesn't Work
fetch(`http://localhost:5247/RegisterUser3`,{
method:'POST',
body:"yaka-yaka",
})
.then(response => response.json())
.then(data => console.log(data));
However, that won't even make it past your browser, because it expects you to define an object (between two { } curly braces) for the body.
Next, you may believe you can just create an object which includes the name of the target param (uuid) and pass that, something like the following:
Doesn't Work 2
var data = {"uuid":"yaka-yaka"};
fetch(`http://localhost:5247/RegisterUser3`,{
method:'POST',
body:data,
})
.then(response => response.json())
.then(data => console.log(data));
That doesn't work and won't get past your browser either. Again, it believes the body object is constructed improperly.
Doesn't Work 3
fetch(`http://localhost:5247/RegisterUser3`,{
method:'POST',
body:{"uuid":"this-is-uuid-123"},
})
.then(response => response.json())
.then(data => console.log(data));
Still doesn't work. you will get a 415 Unsupported Media type
. So you need to add the Content-Type object and set it to JSON so the Fetch call knows you intend to make the call with JSON. That has happened because you added the curly brackets to the body.
Doesn't Work 4: But Does Hit WebAPI
This one does actually hit the WebAPI.
fetch(`http://localhost:5247/RegisterUser3`,{
method:'POST',
body:{"uuid":"this-is-uuid-123"},
headers: {
'Content-type':'application/json; charset=UTF-8',
},
})
.then(response => response.json())
.then(data => console.log(data));
However, now you get the error from the Server which states:
Auto-Bind Error: WebAPI Couldn't Bind to Variable
Microsoft.AspNetCore.Http.BadHttpRequestException: Failed to read parameter
"string uuid" from the request body as JSON.
This is an auto-bind error, because the WebAPI didn't see the uuid value, even though we did pass it in with our body object.
At this point I was flabbergasted.
What's The Issue: How Do You Solve It?
Well, you can't solve it on the JavaScript side. Come to find out, you cannot pass a string value directly to the WebAPI as a parameter. Instead you have to create a server-side object
that matches the client-side object
.
Creating The Server-Side Object
Here's the new class we'll add to our Program.cs (just for sample purposes):
record UuidHolder{
public string uuid{get;set;}
}
Define RegisterUser4 For Working Example
We will use the following WebAPI for our working example:
app.MapPost("/RegisterUser4", IResult ([FromBody] UuidHolder uuid) => {
return Results.Ok(new { Message = $"BODY - You sent in uuid: {uuid.uuid}" });
})
This will keep the RegisterUser3
WebAPI method so you can see that it is impossible to get it to bind.
Now, we have to slightly alter our JavaScript Fetch call to also use JSON.stringify() and then everything will work.
JS Fetch For FromBody & Using JSON.Stringify
fetch(`http://localhost:5247/RegisterUser4`,{
method:'POST',
body:JSON.stringify({"uuid":"this-is-uuid-123"}),
headers: {
'Content-type':'application/json; charset=UTF-8',
},
})
.then(response => response.json())
.then(data => console.log(data));
It works!
I thought that sending the data in on the body would've been the easiest way, but it is the most difficult. Let's cover the last one (FromHeader) and wrap this up.
Using FromHeader On the WebAPI Parameter
Here's the last one which allows us to put our data in the header and post it.
app.MapPost("/RegisterUser5", IResult ([FromHeader] string uuid) => {
return Results.Ok(new { Message = $"HEADER - You sent in uuid: {uuid}" });
})
JavaScript Fetch For [FromHeader]
This one is very easy to do, but I'm not entirely sure why we'd post data using headers.
However, it does help us auto-bind data to some header value we may have.
fetch(`http:
method:'POST',
headers: {
'Content-type':'application/json; charset=UTF-8',
"uuid":"123-456-789"
},
})
.then(response => response.json())
.then(data => console.log(data));
Now, you can auto-bind in .NET Core WebAPI methods using any of the basic attributes and your data will get through. When it doesn't, you'll understand where to look.
Bonus Material OpenApi Documentation
Wow! I just discovered (after publishing my article) that I actually got some free documentation of my APIs via OpenApi.
The dotnet project template added a few lines in the Program.cs
file which handles generating documentation.
app.UseSwagger();
app.UseSwaggerUI();
Then, on each post method I had added the following call:
.WithOpenApi();
How Do You View OpenApi Documentation?
I searched all over the web and scanned this lengthy document that explains OpenApi documentation but it never showed me how to view the docs. It's crazy! I finally found out how to view the autogenerated docs here.
To examine the documents start up the webapi and go to : http://localhost:5247/swagger
You will see the following:
You Can Also Try Out The Api Via Curl
The document is active and you can now try out the API via the UI. It uses curl in the background to post to the webapi and you're going to see that curl can post properly to the body method. However, it fails on the FromForm one. Really interesting.
History
June 9, 2024 : First publication