DEV Community

Cover image for .NET Core WebAPI (Auto)Binding Parameters When Calling Via JavaScript Fetch
raddevus
raddevus

Posted on

.NET Core WebAPI (Auto)Binding Parameters When Calling Via JavaScript Fetch

Get the Source Code For This Article

All of the source code for this article is located at my github repo. It's all C# .NET Core 8.x and JavaScript code.

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)

  1. JavaScript Fetch examples you can use to POST to the WebAPI & see the result WebAPI Parameter Attributes
  2. .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:

Web API - Fetch JS from browser console

[FromQuery] Sample Code

C#
[HttpPost] public ActionResult RegisterUser1([FromQuery] string uuid)

In our Minimal API you will see this method defined in the following way:

C#

app.MapPost("/RegisterUser1", IResult ([FromQuery] string uuid) =>   {
    return Results.Ok(new { Message = $"QUERY - You sent in uuid: {uuid}" });
});
Enter fullscreen mode Exit fullscreen mode

JavaScript Fetch For [FromQuery]

JavaScript

fetch(`http://localhost:5247/RegisterUser1?uuid=987-fake-uuid-1234`,
      {method: 'POST'})
        .then(response => response.json())
        .then(data => console.log(data));
Enter fullscreen mode Exit fullscreen mode

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.

C#

app.MapPost("/RegisterUser2", IResult ([FromForm] string uuid) =>   {
    return Results.Ok(new { Message = $"FORM - You sent in uuid: {uuid}" });
})
Enter fullscreen mode Exit fullscreen mode

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

C#

app.MapPost("/RegisterUser2", IResult ([FromForm] string uuid) =>   {
    return Results.Ok(new { Message = $"FORM - You sent in uuid: {uuid}" });
})
.DisableAntiforgery()
Enter fullscreen mode Exit fullscreen mode

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.

JavaScript

// 1. Create the FormData object
var fd = new FormData();
// 2. Append the name/value pair(s) for values you want to send
fd.append("uuid", "123-test2-2345-uuid-551");
fetch(`http://localhost:5247/RegisterUser2`,{
      method:'POST',
     body:fd,   // add the FormData object to the body data which will be posted
})
.then(response => response.json())
.then(data => console.log(data));
Enter fullscreen mode Exit fullscreen mode

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

C#

app.MapPost("/RegisterUser3", IResult ([FromBody] string uuid) =>   {
    return Results.Ok(new { Message = $"FORM - You sent in uuid: {uuid}" });
})
Enter fullscreen mode Exit fullscreen mode

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

JavaScript

fetch(`http://localhost:5247/RegisterUser3`,{
      method:'POST',
     body:"yaka-yaka",   
})
.then(response => response.json())
.then(data => console.log(data));
Enter fullscreen mode Exit fullscreen mode

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

JavaScript

var data = {"uuid":"yaka-yaka"};
fetch(`http://localhost:5247/RegisterUser3`,{
      method:'POST',
     body:data,   
})
.then(response => response.json())
.then(data => console.log(data));
Enter fullscreen mode Exit fullscreen mode

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

JavaScript

fetch(`http://localhost:5247/RegisterUser3`,{
      method:'POST',
     body:{"uuid":"this-is-uuid-123"},   
})
.then(response => response.json())
.then(data => console.log(data));
Enter fullscreen mode Exit fullscreen mode

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.

JavaScript

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));
Enter fullscreen mode Exit fullscreen mode

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):

C#

record UuidHolder{
    public string uuid{get;set;}
}
Enter fullscreen mode Exit fullscreen mode

Define RegisterUser4 For Working Example
We will use the following WebAPI for our working example:

C#

app.MapPost("/RegisterUser4", IResult ([FromBody] UuidHolder uuid) =>   {
    return Results.Ok(new { Message = $"BODY - You sent in uuid: {uuid.uuid}" });
})
Enter fullscreen mode Exit fullscreen mode

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

JavaScript

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));
Enter fullscreen mode Exit fullscreen mode

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.

C#

app.MapPost("/RegisterUser5", IResult ([FromHeader] string uuid) =>   {
    return Results.Ok(new { Message = $"HEADER - You sent in uuid: {uuid}" });
})
Enter fullscreen mode Exit fullscreen mode

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.

C#

fetch(`http://localhost:5247/RegisterUser5`,{
      method:'POST', 
    headers: {
           'Content-type':'application/json; charset=UTF-8',
            "uuid":"123-456-789"
       },
})
.then(response => response.json())
.then(data => console.log(data));
Enter fullscreen mode Exit fullscreen mode

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.

C#

app.UseSwagger();
app.UseSwaggerUI();
Enter fullscreen mode Exit fullscreen mode

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:

Image description

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.

Top comments (0)