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

ASP.NET Core Web API from Setup to Services

GearHub rents power tools to contractors and needs an internal Web API to track its equipment fleet. Each piece of equipment has a maintenance history (a parent-child relationship), a PDF manual that technicians download on site, and a photo uploaded when the equipment is checked in. The API must behave differently in Development (detailed errors and a larger upload limit) and Production (generic errors and a smaller upload limit). It relies on injected loggers and a manual/image storage service whose base path and upload limit come from environment-specific configuration. You’ll build this API step by step: first the pipeline, then the controllers, then error handling and downloads, and finally updates, JSON Patch, and image uploads.

Lab platform
Lab Info
Level
Intermediate
Last updated
Oct 21, 2026
Duration
1h 40m

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

    Step 1: Explore the GearHub equipment API workspace

    GearHub needs an equipment API

    GearHub rents power tools to contractors, and every drill, saw, and generator in the fleet needs to be tracked: its rental status, the maintenance it has been through, the PDF manual technicians pull up on site, and the photo taken when it's checked back in. Over the next four steps you will grow a bare ASP.NET Core Web API into the service that does all of this, one file at a time. ## Tour the starter workspace

    Open src/ and note the shape you'll be working in:

    • Program.cs wires up the builder, registers the repository and storage service, and calls into PipelineConfigurator before mapping controllers—you won't need to touch it.
    • Models/Equipment.cs holds the domain types: an Equipment owns a list of MaintenanceRecord entries, one per service visit.
    • Models/Dtos.cs holds the shapes the API actually sends and accepts: EquipmentDto, EquipmentCreateDto, EquipmentUpdateDto, MaintenanceRecordDto, and MaintenanceRecordCreateDto. The input DTOs already have the validation attributes Step 5 relies on.
    • Repositories/IEquipmentRepository.cs and Repositories/EquipmentRepository.cs are an in-memory store that starts empty and assigns each new piece of equipment its Id; it's complete, and every controller you write calls into it.
    • Controllers/EquipmentController.cs and Controllers/MaintenanceRecordController.cs are the two routed controllers you'll fill in.
    • Services/EquipmentMediaStorageService.cs will stream manuals and save uploaded photos once you finish it in steps 4 and 5. It reads MediaStorage:BasePath and MediaStorage:MaxImageSizeBytes from configuration.
    • appsettings.Development.json and appsettings.Production.json layer different MediaStorage settings on top of appsettings.json—a bigger upload limit and a different base path in Development than in Production. That difference is the point: the API should behave differently per environment without any code branching on the environment name outside of PipelineConfigurator. ## How the steps build on each other

    Step 2 makes the request pipeline itself environment-aware and adds a logging middleware component. Step 3 completes the controller actions that list and create equipment and add maintenance records under it. Step 4 standardizes how errors and alternate formats look, and adds asynchronous manual downloads through an injected service. Step 5 closes the loop: validated updates, JSON Patch for partial edits, and a guarded photo upload that reuses the same storage service.

    Each task's check is an automated test that calls the code you wrote. Some tests use an in-memory test server that sends real HTTP requests, so what you build has to actually behave correctly, not just compile. ## Before you start editing

    Every task tells you exactly which file and method to change; the surrounding code already compiles and runs, so you're always completing behavior, not fixing broken scaffolding.

  2. Challenge

    Step 2: Configure the environment-aware request pipeline

    Step 2: Configure the environment-aware request pipeline

    ASP.NET Core builds its request pipeline as an ordered chain of middleware, and IWebHostEnvironment identifies the current hosting environment, whether it's running in Development or Production. src/PipelineConfigurator.cs holds two static methods, called from Program.cs: one configures error handling for the environment, and the other maps a status endpoint. Right now, both methods contain placeholders. ## Reporting the active environment

    Besides differing error behavior, the API needs a cheap way for anyone hitting it to confirm which environment answered. Minimal APIs let you map a route directly from WebApplication without a controller, which is exactly what a lightweight status check calls for. ## Writing your own middleware

    The middleware class you'll complete has a constructor that accepts the next delegate in the chain and an InvokeAsync(HttpContext) method that returns a Task. ASP.NET Core invokes middleware in registration order. src/Middleware/RequestLoggingMiddleware.cs already has that shape wired into the pipeline, but its placeholder method doesn't yet forward, log, or measure requests.

  3. Challenge

    Step 3: Design the Equipment and MaintenanceRecord controllers

    Step 3: Design the Equipment and MaintenanceRecord controllers

    When returning equipment or maintenance data, you'll return DTOs rather than the Equipment or MaintenanceRecord entities themselves, so the wire format stays stable even if the domain model changes shape. EquipmentRepository is already implemented and injected into both controllers. You're wiring the HTTP surface on top of it, not the storage underneath it. It starts empty, so equipment exists only once a client creates it. ## Returning a precise 201 on create

    A REST POST that creates a resource should answer with 201 Created, a Location header pointing at the new resource, and the resource itself in the body. CreatedAtAction builds all three from an action name and route values in one call. ## Nesting a child resource under its parent

    A maintenance record only makes sense attached to one piece of equipment, so its routes live under /api/equipment/{equipmentId}/maintenance rather than as a flat /api/maintenance. MaintenanceRecordController already carries that route template - it just needs to check the parent exists before creating a child under it.

  4. Challenge

    Step 4: Standardize errors, negotiate formats, and stream manuals

    Step 4: Standardize errors, negotiate formats, and stream manuals

    A client that asks for equipment that doesn't exist should get the same shape of error every time - ProblemDetails is the standard ASP.NET Core type for exactly that, carrying a status, a title, and a detail message as one JSON object instead of an ad hoc string. ## Letting clients ask for XML instead of JSON

    Content negotiation means the same action can answer in more than one format depending on the client's Accept header. Adding the XML formatters to MVC's output pipeline is what makes an Accept: application/xml request get XML back from an action that otherwise only ever produced JSON. ## Streaming a manual through an injected service

    EquipmentMediaStorageService is registered in Program.cs and injected wherever a controller needs it; its base path for manuals comes from the MediaStorage:BasePath setting, which is different in appsettings.Development.json and appsettings.Production.json. Streaming the file asynchronously means the API starts sending bytes to the client before it has read the whole PDF into memory.

  5. Challenge

    Step 5: Complete the equipment lifecycle with validation, patch, and upload

    Step 5: Complete the equipment lifecycle with validation, patch, and upload

    EquipmentUpdateDto already carries [Required] and [Range] attributes, and model validation records any violations in ModelState. Because both controllers have [ApiController], MVC already answers an invalid request body with an automatic 400 before your action runs. That check lives in the MVC request pipeline, not in your action. It doesn't run when code calls the action method directly, as this task's unit test does, or when SuppressModelStateInvalidFilter is enabled. An explicit ModelState.IsValid guard lets your action reject errors already recorded in ModelState; it doesn't run the validation attributes itself. ## Editing part of a resource with JSON Patch

    A PUT replaces a whole resource; a PATCH with a JSON Patch document (application/json-patch+json) describes individual changes. JsonPatchDocument<EquipmentUpdateDto>.ApplyTo mutates a DTO instance in place and records patch-operation errors in ModelState, such as an attempt to update a property that doesn't exist. ## Guarding an upload before it touches disk

    An upload endpoint that trusts the client's claimed content type or an unlimited size is one bad photo away from filling the disk or storing something that isn't an image. MediaStorage:MaxImageSizeBytes in configuration sets that ceiling differently for Development and Production, and EquipmentMediaStorageService is where that limit and the content-type check both belong, since the upload controller action already delegates to it.

  6. Challenge

    Step 6: Run the application

    Every task is done, and each one was graded the moment you completed it. What you have not done yet is watch the whole application run at once—which is the only place the pieces you wrote separately become a single working program.

    Start the application

    Open the Terminal tab and run:

    dotnet run --project src/Lab.csproj
    

    The application runs in the Terminal and prints its hosting logs there. The pieces you wrote in the previous steps are now running together, but you'll need to send HTTP requests to see the endpoints in action.

    Press Ctrl+C to stop the application before running the checks below. ### Run the checks yourself

    The lab ran each task's test for you as you went, but they are ordinary tests and you can run them whenever you like. The whole suite:

    dotnet test tests/Lab.Tests.csproj --nologo --verbosity quiet
    

    Or one task at a time:

    bash runTest.sh Task_2_1
    

    Each failing test's name identifies the task it belongs to, so the output tells you which step to go back to rather than which line to stare at. One thing worth trying before you finish: break something on purpose and watch the matching check fail, then put it back. Knowing what a failure looks like is worth as much as knowing what a pass looks like, and it costs you nothing here.

About the author

Pluralsight’s AI authoring technology is designed to accelerate the creation of hands-on, technical learning experiences. Serving as a first-pass content generator, it produces structured lab drafts aligned to learning objectives defined by Pluralsight’s Curriculum team. Each lab is then enhanced by our Content team, who configure the environments, refine instructions, and conduct rigorous technical and quality reviews. The result is a collaboration between artificial intelligence and human expertise, where AI supports scale and efficiency, and Pluralsight experts ensure accuracy, relevance, and instructional quality, helping learners build practical skills with confidence.

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