Featured resource
2026 Tech Forecast
2026 Tech Forecast

1,500+ tech insiders, business leaders, and Pluralsight Authors share their predictions on what’s shifting fastest and how to stay ahead.

Download the forecast
  • Lab
    • Libraries: If you want this lab, consider one of these libraries.
    • Core Tech
Labs

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 platform
Lab Info
Level
Beginner
Last updated
Apr 24, 2026
Duration
35m

Contact sales

By clicking submit, you agree to our Privacy Policy and Terms of Use, and consent to receive marketing emails from Pluralsight.
Table of Contents
  1. 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, or 404 Not Found if the id does not exist.

    info> A solution directory 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.

  2. Challenge

    Running the Product Service

    The starter project under ProductService/ is a stock ASP.NET Core 10 Web API project. A ProductService is already included under the Services folder.

    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.

  3. Challenge

    Configuring Services and Routing

    ASP.NET Core 10 uses the minimal hosting model. The Program.cs creates a WebApplicationBuilder, registers services in the dependency injection container (builder.Services), builds the WebApplication, and configures the request pipeline.

    Open Program.cs now. The file contains three TODO comments: TODO 1, TODO 2, and TODO 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.cs is ready to host the controller you will build in Step 4.

  4. Challenge

    Creating the Products Controller

    With Program.cs wired up, the application is ready to host controllers. The ProductsController.cs is already in place at Controllers/ProductsController.cs with the correct using statements, namespace, and an empty class that inherits from ControllerBase. The file compiles as-is but you're going to need to make some changes.

    Open Controllers/ProductsController.cs now. It contains two TODO comments - TODO 1 and TODO 2 - that mark the changes you will make in this step. Each task below asks you to resolve one TODO.

    • TODO 1 sits above the class declaration. Resolving it adds the [ApiController] attribute, a private readonly IProductService field, and a constructor that accepts IProductService and assigns it to the field.
    • TODO 2 sits above TODO 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 and ProblemDetails responses. Attribute routing keeps the URL next to the controller that serves it - the token [controller] is replaced with the controller's name minus the Controller suffix, so ProductsController becomes products.

  5. 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) —> HTTP 200) or a result such as NotFound() (HTTP 404) 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 returns 404 automatically without reaching GetById.

  6. 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 curl from 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 for curl. When you are done, you will stop the API with kill.

    The -i flag on curl prints the response status line and headers above the JSON body. This is the part you want to see. curl -i prints the status line, headers, and body of the HTTP response. For a healthy GET /api/products you should see HTTP/1.1 200 OK, a Content-Type: application/json header, and a JSON array of products in the body. A correctly implemented GetById returns 404 Not Found for an id that does not exist, with no JSON body. ASP.NET Core's [ApiController] attribute usually attaches a ProblemDetails payload, but the bare NotFound() helper you wrote returns an empty body — the status code is what matters here.

  7. 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

    1. Ran the starter project - Used dotnet run to confirm the stock WebApplicationBuilder host starts and listens on http://localhost:5000.
    2. Registered the product service with dependency injection - Added builder.Services.AddScoped<IProductService, ProductService>(); so controllers can receive IProductService through constructor injection.
    3. Registered controller services - Added builder.Services.AddControllers(); to enable controller discovery, model binding, JSON formatting, and the [ApiController] behaviors.
    4. Mapped controller routes onto the pipeline - Added app.MapControllers(); so incoming HTTP requests reach your controller actions.
    5. Finished the ProductsController - Starting from the pre-created skeleton, added the [ApiController] attribute and wired IProductService into the controller through constructor injection, storing it in a private readonly field.
    6. Configured attribute routing - Added [Route("api/[controller]")] to map the controller to /api/products.
    7. Implemented GET /api/products - Added the GetAll action decorated with [HttpGet], returning ActionResult<IEnumerable<Product>> and Ok(_productService.GetAll()).
    8. Implemented GET /api/products/{id} - Added the GetById action with [HttpGet("{id:int}")], returning Ok(product) on a match and NotFound() when the service returns null.
    9. Verified the endpoints over HTTP - Started the API in the background and used curl -i to inspect the status line, headers, and JSON body for GET /api/products, and the 404 Not Found response for a missing product id.

    Key takeaways

    • The minimal hosting model centralises everything in one Program.cs - services are registered on builder.Services before builder.Build(), and middleware and route mappings are configured on app before app.Run().
    • Dependency injection lifetimes matter: AddScoped creates 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 the Controller suffix, so ProductsController answers at /api/products.
    • ActionResult<T> lets an action return either the data (Ok(...)) or a result such as NotFound() 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 returns 404 automatically without reaching GetById.
    • 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/products action that accepts a Product from the request body and returns CreatedAtAction(...), then call it with curl -X POST -H "Content-Type: application/json" -d '{...}' http://localhost:5000/api/products.
    • Add a [HttpGet("search")] action that accepts a category query string and filters the product list.
    • Swap AddScoped for AddSingleton on IProductService and 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.
About the author

James Millar is a freelance software developer based in Malmesbury, England. He has a passion for the cloud and has designed services that handle millions of transactions every day.

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.

Get started with Pluralsight