Async Calls in ASP.NET Core MVC/Razor Pages
This sample demonstrates how to perform asynchronous operations, such as calling external APIs, within your ASP.NET Core MVC or Razor Pages applications. Asynchronous programming is crucial for building responsive and scalable web applications, especially when dealing with I/O-bound operations like network requests.
Why Use Async?
By using asynchronous methods (marked with the `async` and `await` keywords), your application can free up threads while waiting for long-running operations to complete. This allows the web server to handle other incoming requests, significantly improving performance and user experience, preventing thread pool starvation, and reducing resource consumption.
Example Scenario: Fetching Data from a Public API
We'll simulate fetching data from a hypothetical public API. This could be a service that provides weather information, stock prices, or user profiles. For this example, we'll use a simple placeholder API call.
Controller/Page Model Implementation (C#)
Here's how you might implement an asynchronous action method in an MVC controller or an asynchronous handler in a Razor Pages page model.
MVC Controller Example
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json; // Or System.Text.Json
namespace MyWebApp.Controllers
{
public class DataController : Controller
{
private readonly HttpClient _httpClient;
public DataController(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<IActionResult> Index()
{
var apiUrl = "https://api.example.com/data/items";
try
{
// Asynchronously call the external API
var response = await _httpClient.GetAsync(apiUrl);
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
// Deserialize JSON response
var data = JsonConvert.DeserializeObject<List<string>>(jsonContent);
// Pass data to the view
return View(data);
}
else
{
// Handle API error
return StatusCode((int)response.StatusCode, "Error fetching data from API.");
}
}
catch (HttpRequestException ex)
{
// Handle network or request errors
return StatusCode(500, $"API request failed: {ex.Message}");
}
}
}
}
Razor Pages Page Model Example
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Newtonsoft.Json; // Or System.Text.Json
namespace MyWebApp.Pages
{
public class DataModel : PageModel
{
private readonly HttpClient _httpClient;
public List<string> MyData { get; set; }
public DataModel(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task OnGetAsync()
{
var apiUrl = "https://api.example.com/data/items";
try
{
// Asynchronously call the external API
var response = await _httpClient.GetAsync(apiUrl);
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
// Deserialize JSON response
MyData = JsonConvert.DeserializeObject<List<string>>(jsonContent);
}
else
{
// Handle API error
ModelState.AddModelError("", "Error fetching data from API.");
}
}
catch (HttpRequestException ex)
{
// Handle network or request errors
ModelState.AddModelError("", $"API request failed: {ex.Message}");
}
}
}
}
Setting up HttpClient
It's recommended to register HttpClient
as a singleton service in your application's Startup.cs
or Program.cs
file:
// In Startup.cs (or Program.cs for .NET 6+)
public void ConfigureServices(IServiceCollection services)
{
// ... other services
services.AddHttpClient(); // Registers HttpClient as a transient service
// Or configure with a base address and default headers
services.AddHttpClient("MyApiClient", client =>
{
client.BaseAddress = new Uri("https://api.example.com/");
client.DefaultRequestHeaders.Add("Accept", "application/json");
});
}
Razor View /cshtml Example
Displaying the fetched data in a view:
@model MyWebApp.Pages.DataModel // For Razor Pages
// Or @model IEnumerable<string> for MVC views
// For MVC Controller: View
// @{ ViewData["Title"] = "API Data"; }
// For Razor Pages: _ViewImports.cshtml might handle this
@{
ViewData["Title"] = "API Data";
}
<h2>Data from External API</h2>
<p>
This data was fetched asynchronously from an external service.
</p>
<h3>Items:</h3>
@if (Model.MyData != null && Model.MyData.Any())
{
<ul>
@foreach (var item in Model.MyData)
{
<li>@item</li>
}
</ul>
}
else
{
<p>No data available or an error occurred.</p>
}
// For MVC Controller: View
// @{
// var data = Model as IEnumerable<string>;
// }
// <h2>Data from External API</h2>
// <p>This data was fetched asynchronously from an external service.</p>
// <h3>Items:</h3>
// @if (data != null && data.Any())
// {
// <ul>
// @foreach (var item in data)
// {
// <li>@item</li>
// }
// </ul>
// }
// else
// {
// <p>No data available or an error occurred.</p>
// }
Advanced Considerations
- Error Handling: Implement robust error handling for API calls, including timeouts, network issues, and non-successful status codes.
- Cancellation Tokens: Use
CancellationToken
to allow requests to be cancelled if the client navigates away or the server needs to shut down gracefully. - Throttling and Retries: For external APIs, consider implementing strategies for API rate limiting, retries, and circuit breakers.
- Deserialization: Choose an appropriate JSON serializer (e.g.,
System.Text.Json
for modern .NET, orNewtonsoft.Json
). - Dependency Injection: Inject
HttpClientFactory
for more advancedHttpClient
management, especially in scenarios with many outgoing requests.