- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
Guided: Building Your First ASP.NET Core Web API Endpoint
This lab guides you through building an ASP.NET Core 10 Web API from a pre-configured starter project. Throughout the course of this lab, you will employ the fundamentals of controller-based Web APIs, dependency injection, attribute routing, and action methods — along with updated features and paradigms from .NET 10 and C# 14. By the end of the lab, you'll have a functional Product Catalog API and a solid understanding of how the minimal hosting model, controllers, ActionResults and HTTP status codes work together in ASP.NET Core.
Lab Info
Table of Contents
-
Challenge
Introduction
Welcome to the Guided: Building Your First ASP.NET Core Web API Endpoint lab.
In this lab, you will build a Product Catalog Web API. A small internal service that returns a list of products and lets callers fetch a single product by its id. You will use ASP.NET Core 10 with the minimal hosting model, attribute-routed controllers, and constructor-injected services.
In the interest of time, the starter project is already in the workspace under
ProductService/. You will not be creating anything from scratch; instead you'll focus on the core "plumbing" of an ASP.NET Core Web API: dependency injection, controllers, attribute routing, and action methods.What you will build
By the end of the lab you will have a Web API that exposes:
GET /api/products: returns the full product list as JSON.GET /api/products/{id}: returns a single product, or404 Not Foundif the id does not exist.
info> A
solutiondirectory is available for you to reference and compare your work at any stage. Keep in mind that it represents just one valid approach. Your implementation is equally correct as long as it behaves the same and passes validation. -
Challenge
Running the Product Service
The starter project under
ProductService/is a stock ASP.NET Core 10 Web API project. AProductServiceis already included under theServicesfolder.Before making any changes, confirm the starter builds and runs. Stop the app now (
Ctrl+C) so your terminal is free to use for the next task. -
Challenge
Configuring Services and Routing
ASP.NET Core 10 uses the minimal hosting model. The
Program.cscreates aWebApplicationBuilder, registers services in the dependency injection container (builder.Services), builds theWebApplication, and configures the request pipeline.Open
Program.csnow. The file contains threeTODOcomments:TODO 1,TODO 2, andTODO 3. These mark exactly where you will need to make changes. Each task below asks you to find one TODO and add one line of code below it.By the end of this step, all three TODOs are replaced by real code and your
Program.csis ready to host the controller you will build in Step 4. -
Challenge
Creating the Products Controller
With
Program.cswired up, the application is ready to host controllers. TheProductsController.csis already in place atControllers/ProductsController.cswith the correctusingstatements, namespace, and an empty class that inherits fromControllerBase. The file compiles as-is but you're going to need to make some changes.Open
Controllers/ProductsController.csnow. It contains twoTODOcomments -TODO 1andTODO 2- that mark the changes you will make in this step. Each task below asks you to resolve one TODO.TODO 1sits above the class declaration. Resolving it adds the[ApiController]attribute, a privatereadonlyIProductServicefield, and a constructor that acceptsIProductServiceand assigns it to the field.TODO 2sits aboveTODO 1. Resolving it adds the[Route("api/[controller]")]attribute so the controller answers requests at/api/products.
The
[ApiController]attribute opts the class in to Web API behaviors such as automatic model validation andProblemDetailsresponses. Attribute routing keeps the URL next to the controller that serves it - the token[controller]is replaced with the controller's name minus theControllersuffix, soProductsControllerbecomesproducts. -
Challenge
Implementing the Endpoints
With the controller shape in place, it is time to add the two action methods that actually answer requests. Using
ActionResult<T>lets an action return either the data (Ok(products)—> HTTP200) or a result such asNotFound()(HTTP404) from the same method, while keeping the response type discoverable for OpenAPI and clients.The
[HttpGet]and[HttpGet("{id:int}")]attributes bind each method to a route template. The{id:int}constraint is resolved by ASP.NET Core before your method runs, so a non-integer id returns404automatically without reachingGetById. -
Challenge
Inspecting HTTP Responses
Writing the code is only half the job. A professional development team confirms an endpoint behaves correctly by sending real HTTP requests and inspecting the response payload, headers, and status code.
In this step you will start the finished API, then use
curlfrom the terminal to call both endpoints and read the wire-level response. You will run the API in the background with&, leaving the prompt free forcurl. When you are done, you will stop the API withkill.The
-iflag oncurlprints the response status line and headers above the JSON body. This is the part you want to see.curl -iprints the status line, headers, and body of the HTTP response. For a healthyGET /api/productsyou should seeHTTP/1.1 200 OK, aContent-Type: application/jsonheader, and a JSON array of products in the body. A correctly implementedGetByIdreturns404 Not Foundfor an id that does not exist, with no JSON body. ASP.NET Core's[ApiController]attribute usually attaches aProblemDetailspayload, but the bareNotFound()helper you wrote returns an empty body — the status code is what matters here. -
Challenge
Conclusion
Congratulations on completing the Guided: Building Your First ASP.NET Core Web API Endpoint lab! You have built a working Product Catalog Web API using ASP.NET Core 10 - registering services, wiring up controllers, implementing attribute-routed action methods, and verifying the endpoints with real HTTP requests.
What you have accomplished
- Ran the starter project - Used
dotnet runto confirm the stockWebApplicationBuilderhost starts and listens onhttp://localhost:5000. - Registered the product service with dependency injection - Added
builder.Services.AddScoped<IProductService, ProductService>();so controllers can receiveIProductServicethrough constructor injection. - Registered controller services - Added
builder.Services.AddControllers();to enable controller discovery, model binding, JSON formatting, and the[ApiController]behaviors. - Mapped controller routes onto the pipeline - Added
app.MapControllers();so incoming HTTP requests reach your controller actions. - Finished the
ProductsController- Starting from the pre-created skeleton, added the[ApiController]attribute and wiredIProductServiceinto the controller through constructor injection, storing it in aprivate readonlyfield. - Configured attribute routing - Added
[Route("api/[controller]")]to map the controller to/api/products. - Implemented
GET /api/products- Added theGetAllaction decorated with[HttpGet], returningActionResult<IEnumerable<Product>>andOk(_productService.GetAll()). - Implemented
GET /api/products/{id}- Added theGetByIdaction with[HttpGet("{id:int}")], returningOk(product)on a match andNotFound()when the service returnsnull. - Verified the endpoints over HTTP - Started the API in the background and used
curl -ito inspect the status line, headers, and JSON body forGET /api/products, and the404 Not Foundresponse for a missing product id.
Key takeaways
- The minimal hosting model centralises everything in one
Program.cs- services are registered onbuilder.Servicesbeforebuilder.Build(), and middleware and route mappings are configured onappbeforeapp.Run(). - Dependency injection lifetimes matter:
AddScopedcreates a new instance per HTTP request, which is the right default for services that touch request-specific state. - Attribute routing keeps the URL next to the controller that serves it - the
[controller]token is replaced with the controller's name minus theControllersuffix, soProductsControlleranswers at/api/products. ActionResult<T>lets an action return either the data (Ok(...)) or a result such asNotFound()from the same method, while keeping the response type discoverable for OpenAPI and clients.- Route constraints like
{id:int}are resolved before your method runs - a non-integer id returns404automatically without reachingGetById. - Verifying at the wire level - sending real HTTP requests and reading status codes, headers, and payloads is how professional teams confirm an endpoint behaves correctly, not by inspecting the code alone.
Experiment before you go
- Add a
POST /api/productsaction that accepts aProductfrom the request body and returnsCreatedAtAction(...), then call it withcurl -X POST -H "Content-Type: application/json" -d '{...}' http://localhost:5000/api/products. - Add a
[HttpGet("search")]action that accepts acategoryquery string and filters the product list. - Swap
AddScopedforAddSingletononIProductServiceand observe how the same instance is reused across requests. - Inject
ILogger<ProductsController>through the constructor and log each incoming request. - Explore the
solution/directory to compare your implementation with the reference version at each step.
- Ran the starter project - Used
About the author
Real skill practice before real-world application
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.