Deep Dive: AJAX and the Fetch API in ASP.NET Core MVC .NET 10
1. Introduction
In modern web development, users expect fast, responsive interfaces that don’t require a full page reload for every action. While my previous post covered the basics of JavaScript in MVC, this “Deep Dive” focuses specifically on how to wire up AJAX using the Fetch API to talk to your ASP.NET Core MVC controllers.
We will build a “Product Like” feature to demonstrate the full end-to-end flow.
2. The Data Transfer Object (DTO)
First, we need a simple class to represent the data being sent from the client to the server.
namespace MyApp.Models.Dtos
{
public class LikeProductDto
{
public int ProductId { get; set; }
public bool IsLiked { get; set; }
}
}
3. The Controller Action
Your controller needs an action method decorated with [HttpPost] and [ValidateAntiForgeryToken]. It uses [FromBody] to tell MVC to deserialize the JSON request body into our DTO.
using Microsoft.AspNetCore.Mvc;
using MyApp.Models.Dtos;
public class ProductsController : Controller
{
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Like([FromBody] LikeProductDto data)
{
if (data == null || data.ProductId <= 0)
{
return BadRequest(new { message = "Invalid product data." });
}
// Logic to update the database would go here...
// e.g., _service.ToggleLike(data.ProductId, data.IsLiked);
return Json(new {
success = true,
message = $"Product {data.ProductId} was {(data.IsLiked ? "liked" : "unliked")}!",
newLikeCount = 125 // Example updated state
});
}
}
4. The Razor View (Index.cshtml)
The view needs three things:
- The HTML element to trigger the action.
- The Antiforgery token (usually generated by a hidden form).
- A script section to handle the click event.
@model ProductViewModel
<div class="product-card">
<h3>@Model.Name</h3>
<p>@Model.Description</p>
<!-- We use data-attributes to store the ID for JS to read -->
<button id="like-button"
class="btn btn-outline-primary"
data-product-id="@Model.Id"
data-is-liked="false">
Like (<span id="like-count">@Model.LikeCount</span>)
</button>
</div>
<!-- This helper generates the hidden input for the Antiforgery Token -->
@Html.AntiForgeryToken()
@section Scripts {
<script>
document.getElementById('like-button').addEventListener('click', async function() {
const btn = this;
const productId = btn.getAttribute('data-product-id');
const isLiked = btn.getAttribute('data-is-liked') === 'true';
// 1. Get the token value from the hidden input generated by @Html.AntiForgeryToken()
const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
try {
// 2. Perform the Fetch call
const response = await fetch('/Products/Like', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'RequestVerificationToken': token // Crucial for security!
},
body: JSON.stringify({
productId: parseInt(productId),
isLiked: !isLiked // Toggle the state
})
});
// 3. Handle the response
if (response.ok) {
const result = await response.json();
// 4. Update the UI dynamically
btn.setAttribute('data-is-liked', !isLiked);
btn.classList.toggle('btn-primary');
btn.classList.toggle('btn-outline-primary');
document.getElementById('like-count').innerText = result.newLikeCount;
console.log(result.message);
} else {
alert('Something went wrong on the server.');
}
} catch (error) {
console.error('Error during Fetch:', error);
}
});
</script>
}
5. Key Takeaways
- Antiforgery Token: MVC’s security middleware expects the
RequestVerificationTokenheader for POST/PUT/DELETE requests. Without it, you’ll get a400 Bad Requestor403 Forbidden. - JSON Serialization: Use
JSON.stringify()in JavaScript and[FromBody]in C# to ensure the data is mapped correctly. - Data Attributes: Use
data-*attributes to bridge the gap between your C# Model and your JavaScript logic without hardcoding values in scripts. - Error Handling: Always use
try/catchand checkresponse.okto handle network issues or server errors gracefully.
6. Summary of the Interaction
- Browser: User clicks the button.
- JavaScript: Collects the Product ID from the button’s
data-attribute and the security token from the hidden input. - JavaScript: Sends an HTTP POST request to
/Products/Likewith a JSON body. - Server: MVC validates the Antiforgery token.
- Server: MVC deserializes the JSON into a
LikeProductDtoobject. - Server: The
Likeaction runs, processes the logic, and returns aJsonResult. - JavaScript: Receives the JSON response and updates the DOM (button text and count) without reloading the page.
Leave a comment