Skip to main content

Creating SchoolApp.Api: Your First ASP.NET Core Web API – Web API | Lesson 66

Published / updated

Introduction​

In Lesson 65, you sent requests to a tiny practice API. Now you create the real one: SchoolApp.Api, the project every SMS client will talk to for the rest of the course.

This lesson creates it from the official template, opens every file the template makes, and adds the first endpoint, GET /api/school.

By the end of this lesson, you will be able to:

  • Create an ASP.NET Core Web API project with controllers.
  • Explain each file the template creates.
  • Read Program.cs: services first, then the request pipeline.
  • Run the API with a launch profile, and explain the HTTPS redirect warning.
  • Send requests from a .http file and from curl.exe.

Why this matters​

Every ASP.NET Core project starts like this one. Knowing what each file does, and what each line of Program.cs does, lets you fix startup problems that stop many beginners for hours.

It is also where interviews often begin: "Walk me through Program.cs" is a classic first question for any ASP.NET Core role.

The concept​

Example: what ASP.NET Core is​

ASP.NET Core is the part of .NET for building web applications and APIs. It includes Kestrel, a fast web server that runs inside your program, so dotnet run is all you need to serve requests.

Think of the school building. Kestrel is the gate that lets visitors in, and the pipeline is the reception desk, which checks every visitor the same way.

Controllers are the offices, each handling one kind of business: the fees office, the admissions office.

Example: the files in the project​

dotnet new webapi --use-controllers creates these files:

FileWhat it holds
SchoolApp.Api.csprojThe project: Microsoft.NET.Sdk.Web and the OpenAPI package
Program.csStartup: services and the request pipeline
Controllers/Controller classes; the template's sample is deleted
appsettings.jsonSettings, such as log levels (Lesson 70)
appsettings.Development.jsonSettings used only while developing
Properties/launchSettings.jsonHow dotnet run starts the app on this computer: ports and environment
SchoolApp.Api.httpRequests you can send from the editor

The Web SDK in the project file brings in all of ASP.NET Core, so there is no package to add for it.

Example: two halves of Program.cs​

Program.cs always has two halves, split by builder.Build():

HalfCodePurpose
1. Servicesbuilder.Services.Add...Register what the app can use: controllers, OpenAPI, later the database (Lesson 69)
2. Pipelineapp.Use..., app.Map...Describe what happens to every request, in order (Lesson 67)

app.Run() starts Kestrel and waits for requests. Nothing after it runs until the app stops.

Example: environments and launch profiles​

The environment tells the app where it is running: Development on your computer, Production on the real server. Code can check it, as Program.cs does to show the OpenAPI document only while developing.

launchSettings.json holds launch profiles, ways to start the app locally. The http profile listens on http://localhost:5080; the https profile adds https://localhost:7080.

Both profiles set ASPNETCORE_ENVIRONMENT to Development. The file is for your computer only: servers never use it.

Apply it to the SMS​

Step 1: create the project​

From the repository root:

Terminal
dotnet new webapi --use-controllers -n SchoolApp.Api -o src/SchoolApp.Api -f net10.0
dotnet sln SchoolApp.slnx add src/SchoolApp.Api/SchoolApp.Api.csproj --solution-folder src

Delete the sample files Controllers/WeatherForecastController.cs and WeatherForecast.cs. The template picks random port numbers; SchoolApp uses fixed ones, 5080 and 7080, so every lesson's commands match:

src/SchoolApp.Api/Properties/launchSettings.json
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5080",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7080;http://localhost:5080",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
src/SchoolApp.Api/SchoolApp.Api.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.11" />
</ItemGroup>

</Project>

Step 2: Program.cs​

The template's Program.cs, with comments added for SchoolApp:

src/SchoolApp.Api/Program.cs
// Lesson 66: the start of SchoolApp.Api. Program.cs has two halves:
// 1. Register services (the "builder" part), 2. build the app and describe how requests are handled.
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

// Services: things the app can use. AddControllers finds every controller class in this project.
builder.Services.AddControllers();
builder.Services.AddOpenApi(); // describes the API as an OpenAPI document (Lesson 71)

WebApplication app = builder.Build();

// The request pipeline: what happens to every request, in this order (Lesson 67).
if (app.Environment.IsDevelopment())
{
app.MapOpenApi(); // /openapi/v1.json, only while developing
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers(); // send each request to the matching controller action

app.Run(); // start the web server and wait for requests

AddControllers registers everything controllers need and finds every controller class in the project. MapControllers connects each controller's routes to the pipeline.

Step 3: the first controller​

src/SchoolApp.Api/Controllers/SchoolController.cs
using Microsoft.AspNetCore.Mvc;
using SchoolApp.Api.Models;

namespace SchoolApp.Api.Controllers;

// Lesson 66: the first controller. [ApiController] turns on API behaviour (Lesson 73);
// [Route] gives its address; each public method with an HTTP attribute is an action.
[ApiController]
[Route("api/school")]
public class SchoolController : ControllerBase
{
// GET /api/school
[HttpGet]
public SchoolInfo Get()
{
// Fixed values for now; Lesson 70 moves them into configuration.
return new SchoolInfo("NexCoding Public School", "2026-27", DateTime.UtcNow);
}
}
src/SchoolApp.Api/Models/SchoolInfo.cs
namespace SchoolApp.Api.Models;

// Lesson 66: what GET /api/school returns. A record becomes JSON with camelCase names.
public record SchoolInfo(string Name, string CurrentAcademicYear, DateTime ServerTimeUtc);

A controller is a class that handles requests for one resource. [Route("api/school")] gives its address, and [HttpGet] makes Get an action: the method that runs for GET /api/school. The returned record is turned into JSON automatically.

Step 4: run the API​

Terminal
dotnet run --project src/SchoolApp.Api
Output
Using launch settings from src\SchoolApp.Api\Properties\launchSettings.json...
Building...
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5080
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: D:\SahasraTechnologies\SMSCOURSE\src\SchoolApp.Api
warn: Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware[3]
Failed to determine the https port for redirect.

dotnet run used the first profile, http. The app listens on port 5080, in the Development environment. The warning appeared with the first request; the next example explains it.

Step 5: send requests​

In a second terminal:

Terminal 2
curl.exe -i http://localhost:5080/api/school
Output
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 07 Oct 2026 14:29:26 GMT
Server: Kestrel
Transfer-Encoding: chunked

{"name":"NexCoding Public School","currentAcademicYear":"2026-27","serverTimeUtc":"2026-10-07T14:29:26.9713976Z"}

The C# property CurrentAcademicYear became currentAcademicYear in JSON: ASP.NET Core uses camelCase names by default, the usual style for JSON.

Visual Studio and VS Code (with the REST Client extension) can send requests straight from a .http file. Click "Send Request" above a request to see the response next to the code:

src/SchoolApp.Api/SchoolApp.Api.http
# Lesson 66: requests you can send from Visual Studio or VS Code (REST Client):
# click "Send Request" above each one. ### separates requests.
@SchoolApp.Api_HostAddress = http://localhost:5080

GET {{SchoolApp.Api_HostAddress}}/api/school
Accept: application/json

###

GET {{SchoolApp.Api_HostAddress}}/openapi/v1.json

###

A request to a path no controller handles, such as /api/nothing, gets 404 Not Found.

Example: the HTTPS redirect warning​

app.UseHttpsRedirection() sends anyone using plain HTTP to the HTTPS address. With the http profile, there is no HTTPS address, so it logs "Failed to determine the https port for redirect" and lets the request through.

With the https profile, the redirect works:

Terminal
dotnet run --project src/SchoolApp.Api --launch-profile https
curl.exe -i http://localhost:5080/api/school
Output
HTTP/1.1 307 Temporary Redirect
Content-Length: 0
Date: Wed, 07 Oct 2026 14:29:48 GMT
Server: Kestrel

307 sends the client to the HTTPS address in the Location header. HTTPS needs a development certificate, which the .NET SDK can create and trust once with dotnet dev-certs https --trust. The course uses the http profile, so commands stay simple.

Example: the same app in Production​

To see the environment at work, start the app without a launch profile and set the environment yourself, in Git Bash or any shell that accepts this form:

Terminal
ASPNETCORE_ENVIRONMENT=Production dotnet run --project src/SchoolApp.Api --no-launch-profile --urls http://localhost:5080

The log now says "Hosting environment: Production". GET /api/school still returns 200, but GET /openapi/v1.json returns 404, because Program.cs maps it only in Development.

This is how a real server behaves: no launch profile, the environment set by the server, and development tools switched off. If you forget to set it, the default is Production, the safe choice.

Example: dotnet watch​

dotnet watch --project src/SchoolApp.Api starts the app and watches the files. Save a change, and it applies the change or restarts the app by itself, so you do not have to stop and start it after every edit.

Common mistakes​

  1. Registering services after builder.Build(). It throws, because the service list is closed. Fix: every builder.Services.Add... comes before Build().
  2. Code after app.Run(). It never runs while the app is up. Fix: put pipeline code before Run().
  3. Expecting launchSettings.json on the server. It is only for local development. Fix: set environment and URLs on the server itself (Lesson 70).
  4. Forgetting MapControllers(). Every request returns 404. Fix: map controllers in the pipeline.
  5. Two programs on the same port. Startup fails with "address already in use". Fix: stop the other program, or change the port.

In real projects​

From 16 years of .NET projects

A new developer on my team once spent a whole morning on an API that returned 404 for every request. The controller was correct, the route was correct, and the build was clean.

During a merge, app.MapControllers() had been deleted from Program.cs. Without it, no controller was ever reached.

Since then, a new team member's first task is to read Program.cs aloud, line by line, and say what each line does. Ten minutes of that saves mornings like that one.

Best practices​

  • Keep Program.cs short: services, then pipeline, then Run().
  • Fix ports in launchSettings.json so commands and .http files stay valid.
  • Show development-only tools, such as the OpenAPI document, only in Development.
  • Keep a .http file with a request for every endpoint.
  • Use HTTPS outside your own computer.

Recap​

  • ASP.NET Core builds web APIs; Kestrel is the web server inside the app.
  • Program.cs registers services, then builds the request pipeline, then runs.
  • A controller handles one resource; each action handles one method and route.
  • Launch profiles set ports and the Development environment locally.
  • .http files and curl.exe send requests while you develop.

Homework​

Library module task: Create a single-file library API with one controller, LibraryController, that answers GET /api/library with the library's name, loan days, and fine per day. Add a .http file with the request, run the app, and send it.

A correct result looks like:

Output
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Wed, 07 Oct 2026 14:30:08 GMT
Server: Kestrel
Transfer-Encoding: chunked

{"name":"NexCoding Public School Library","loanDays":14,"finePerDay":2}
Show the solution
library-assignment/066-library-api.cs
// Library homework, Lesson 66: the start of a library API, with one controller, like SchoolApp.Api.
// Run with: dotnet run library-assignment/066-library-api.cs
// Then send the requests in library-assignment/066-library.http.
#:sdk Microsoft.NET.Sdk.Web
#:property PublishAot=false
using Microsoft.AspNetCore.Mvc;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();

WebApplication app = builder.Build();
app.MapControllers();
app.Run("http://localhost:5066");

// GET /api/library: the library's rules, from the Lesson 3 homework.
[ApiController]
[Route("api/library")]
public class LibraryController : ControllerBase
{
[HttpGet]
public LibraryInfo Get() => new LibraryInfo("NexCoding Public School Library", LoanDays: 14, FinePerDay: 2m);
}

public record LibraryInfo(string Name, int LoanDays, decimal FinePerDay);
library-assignment/066-library.http
# Library homework, Lesson 66: requests for 066-library-api.cs.
@Library_HostAddress = http://localhost:5066

GET {{Library_HostAddress}}/api/library
Accept: application/json

###

A single-file app can hold controllers too: AddControllers finds LibraryController in the same file.

Quiz​

  1. What is Kestrel?

    1. A database
    2. The web server inside an ASP.NET Core app
    3. A testing tool
    4. A JSON library
    Show answer

    Answer: B. The web server inside an ASP.NET Core app dotnet run starts Kestrel, which listens on port 5080.

  2. Where must builder.Services.AddControllers() go?

    1. After app.Run()
    2. Before builder.Build()
    3. Inside a controller
    4. In launchSettings.json
    Show answer

    Answer: B. Before builder.Build() Services are registered first, then the app is built.

  3. What does launchSettings.json control?

    1. Production servers
    2. How dotnet run starts the app on your computer
    3. The database schema
    4. JSON naming
    Show answer

    Answer: B. How dotnet run starts the app on your computer Ports and the environment for local runs only.

  4. Why did CurrentAcademicYear appear as currentAcademicYear?

    1. A typo
    2. ASP.NET Core writes JSON names in camelCase by default
    3. The browser changed it
    4. The record was renamed
    Show answer

    Answer: B. ASP.NET Core writes JSON names in camelCase by default camelCase is the usual JSON style.

  5. Which status did plain HTTP get with the https profile?

    1. 200
    2. 307 Temporary Redirect
    3. 404
    4. 500
    Show answer

    Answer: B. 307 Temporary Redirect UseHttpsRedirection sent the client to https://localhost:7080.

Interview questions​

Questions are grouped by experience level. Click a question to see a model answer. All questions: Web API interview questions.

Fresher (0-2 yrs)​

F What is Kestrel? concept

The cross-platform web server built into ASP.NET Core. It runs inside your application, so dotnet run alone can serve HTTP requests. In production it can face the internet directly or sit behind a reverse proxy such as IIS, Nginx, or a cloud load balancer.

See the example · Permalink

F What are the two halves of Program.cs in an ASP.NET Core app? concept

Before builder.Build(), services are registered with builder.Services.Add..., such as controllers and OpenAPI. After it, the request pipeline is built with app.Use... and app.Map..., and app.Run() starts the server. Services cannot be added after Build().

See the example · Permalink

F What is a controller, and what is an action? concept

A controller is a class, usually deriving from ControllerBase, that handles requests for one resource, with a route such as [Route("api/school")]. An action is a public method in it with an HTTP attribute such as [HttpGet]; it runs for the matching method and path.

See the example · Permalink

Mid (3-5 yrs)​

M What is launchSettings.json for, and is it used in production? concept

It holds launch profiles for running the app on a developer's computer: URLs and ports, and environment variables such as ASPNETCORE_ENVIRONMENT=Development. It is not used in production; servers set the environment and URLs through their own configuration.

See the example · Permalink

M How does the hosting environment change an app's behaviour, and what is the default? concept

ASPNETCORE_ENVIRONMENT sets it, and code checks it with app.Environment.IsDevelopment(), to show tools such as the OpenAPI document only while developing; settings files can differ per environment too. When it is not set, the environment is Production, so development features stay off by default.

See the example · Permalink

M Why does an app log 'Failed to determine the https port for redirect'? scenario

UseHttpsRedirection wants to send HTTP requests to HTTPS, but the app has no HTTPS endpoint, for example when started with the http launch profile. It lets the request through and logs the warning. With an HTTPS endpoint configured, HTTP requests get a 307 redirect instead.

See the example · Permalink

Senior (6-9 yrs)​

S Every request to a new API returns 404, though the controller and route look correct. What do you check? scenario

That AddControllers() registers controllers and MapControllers() is in the pipeline, that the controller is public with [ApiController] and a [Route], that the action has the right HTTP attribute, and that the URL, including the api/ prefix and port, matches. Logging at Information level for routing shows which endpoint, if any, matched.

How to answer: Work from the outside in: the right app and port, the pipeline, the registration, then the route template. Name the logging you would switch on to confirm.

See the example · Permalink

S How would you structure a Web API solution so the API project stays thin? design

The API project holds only HTTP concerns: controllers, middleware, configuration, and the composition root. Business rules and entities live in a core project, and data access in a data project, both referenced by the API. Controllers translate HTTP to service calls and results back to HTTP.

How to answer: Describe the projects and their references, what belongs in each, and why it keeps rules testable without HTTP. Relate it to the layers from the repository pattern lesson.

See the example · Permalink

Architect / Lead (10+ yrs)​

A What decides how an ASP.NET Core API is hosted in production, and what must differ from development? design

The hosting model: Kestrel directly, behind a reverse proxy, in a container, or on a managed platform, chosen for TLS termination, scaling, and operations needs. In production the environment is Production, development tools such as OpenAPI UIs are off or protected, HTTPS is enforced, URLs come from server configuration, and secrets come from a secure store, never source files.

How to answer: Cover hosting options with trade-offs, then a checklist of what changes between environments, and how configuration is supplied per environment.

See the example · Permalink

Resources​

  • Code for this lesson: git tag lesson-66-create-api-project (code repository coming soon)
  • Files: src/SchoolApp.Api/Program.cs, src/SchoolApp.Api/Controllers/SchoolController.cs, src/SchoolApp.Api/SchoolApp.Api.http, library-assignment/066-library-api.cs
  • Video: coming soon

What's next​

Every request passes through the pipeline built in Program.cs. Next: what the pipeline is, why its order matters, and how to add your own step to it. Lesson 67: The middleware pipeline

Want live sessions, code reviews, and interview preparation? Join the next batch at Sahasra Technologies.