A web service accepts HTTP requests and returns data for clients such as JavaScript applications. Data is commonly JSON. The URL and HTTP method together identify an operation handled by an endpoint or controller action. REST is a common pattern, but there is no authoritative specification that settles every design choice; focus on returning the data the client expects. section 19.0 section 19.2
In ASP.NET Core, start with the operation each client needs:
- Use minimal API endpoints in Program.cs or controller action methods. Both build on ASP.NET Core request handling. section 19.0 section 19.3
- Match a method and route, then obtain the data the operation needs. A route value can identify one object; a POST body can carry a JSON representation of a new object. section 19.3
- Return data or an appropriate nondata response. For example, a lookup can return a product as JSON or set a 404 status when no product matches. section 19.3
A product API
Register GET /api/products/{id} to fetch one product, GET /api/products to fetch all products, and POST /api/products to add a product from a JSON request body. After restarting ASP.NET Core, request http://localhost:5000/api/products/1 and http://localhost:5000/api/products to check the GET routes. Test POST with PowerShell Invoke-RestMethod and a JSON body; entering its arguments exactly matters. section 19.3
Listing 19.3 Creating a web service in the Program.cs file in the Platform folder
using Microsoft.EntityFrameworkCore;
using WebApp.Models;
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<DataContext>(opts => {
opts.UseSqlServer(builder.Configuration[
"ConnectionStrings:ProductConnection"]);
opts.EnableSensitiveDataLogging(true);
});
var app = builder.Build();
const string BASEURL = "api/products";
app.MapGet($"{BASEURL}/{{id}}", async (HttpContext context,
DataContext data) => {
string? id = context.Request.RouteValues["id"] as string;
if (id != null) {
Product? p = data.Products.Find(long.Parse(id));
if (p == null) {
context.Response.StatusCode = StatusCodes.Status404NotFound;
} else {
context.Response.ContentType = "application/json";
await context.Response
.WriteAsync(JsonSerializer.Serialize<Product>(p));
}
}
});
app.MapGet(BASEURL, async (HttpContext context, DataContext data) => {
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonSerializer
.Serialize<IEnumerable<Product>>(data.Products));
});
app.MapPost(BASEURL, async (HttpContext context, DataContext data) => {
Product? p = await
JsonSerializer.DeserializeAsync<Product>(context.Request.Body);
if (p != null) {
await data.AddAsync(p);
await data.SaveChangesAsync();
context.Response.StatusCode = StatusCodes.Status200OK;
}
});
app.MapGet("/", () => "Hello World!");
var context = app.Services.CreateScope().ServiceProvider
.GetRequiredService<DataContext>();
SeedData.SeedDatabase(context);
app.Run();
The Program.cs listing shows the minimal API route definitions and how the single-product endpoint handles its response. section 19.3
A Blazor WebAssembly client provides the other side of this arrangement. Because browser restrictions prevent it from using SQL to reach the database, its components consume a web service over HTTP. An injected HttpClient can retrieve JSON data or send a DELETE request; the component can check the response before refreshing its data. section 37.3.2





