Creating SchoolApp.Api: Your First ASP.NET Core Web API – Web API | Lesson 66
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
.httpfile and fromcurl.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:
| File | What it holds |
|---|---|
SchoolApp.Api.csproj | The project: Microsoft.NET.Sdk.Web and the OpenAPI package |
Program.cs | Startup: services and the request pipeline |
Controllers/ | Controller classes; the template's sample is deleted |
appsettings.json | Settings, such as log levels (Lesson 70) |
appsettings.Development.json | Settings used only while developing |
Properties/launchSettings.json | How dotnet run starts the app on this computer: ports and environment |
SchoolApp.Api.http | Requests 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():
| Half | Code | Purpose |
|---|---|---|
| 1. Services | builder.Services.Add... | Register what the app can use: controllers, OpenAPI, later the database (Lesson 69) |
| 2. Pipeline | app.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:
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:
{
"$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"
}
}
}
}
<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:
// 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
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);
}
}
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
dotnet run --project src/SchoolApp.Api
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:
curl.exe -i http://localhost:5080/api/school
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:
# 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:
dotnet run --project src/SchoolApp.Api --launch-profile https
curl.exe -i http://localhost:5080/api/school
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:
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
- Registering services after
builder.Build(). It throws, because the service list is closed. Fix: everybuilder.Services.Add...comes beforeBuild(). - Code after
app.Run(). It never runs while the app is up. Fix: put pipeline code beforeRun(). - Expecting
launchSettings.jsonon the server. It is only for local development. Fix: set environment and URLs on the server itself (Lesson 70). - Forgetting
MapControllers(). Every request returns404. Fix: map controllers in the pipeline. - 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
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.csshort: services, then pipeline, thenRun(). - Fix ports in
launchSettings.jsonso commands and.httpfiles stay valid. - Show development-only tools, such as the OpenAPI document, only in
Development. - Keep a
.httpfile 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.csregisters 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
Developmentenvironment locally. .httpfiles andcurl.exesend 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:
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 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 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
What is Kestrel?
Show answer
Answer: B. The web server inside an ASP.NET Core app dotnet run starts Kestrel, which listens on port 5080.
Where must builder.Services.AddControllers() go?
Show answer
Answer: B. Before builder.Build() Services are registered first, then the app is built.
What does launchSettings.json control?
Show answer
Answer: B. How dotnet run starts the app on your computer Ports and the environment for local runs only.
Why did CurrentAcademicYear appear as currentAcademicYear?
Show answer
Answer: B. ASP.NET Core writes JSON names in camelCase by default camelCase is the usual JSON style.
Which status did plain HTTP get with the https profile?
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.
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().
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.
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.
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.
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.
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.
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.
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.
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.