Introduction
In the previous articles, we explored the fundamentals of .NET Core Web API, HTTP methods, routing, and the difference between Minimal APIs and Controllers.
Now it's time to put that knowledge into practice.
In this article, we'll create an Employee Management Web API from scratch using C# and .NET 8.
We'll build an API that allows users to:
Retrieve all employees.
Retrieve an employee by ID.
Add a new employee.
Update employee information.
Delete an employee.
We'll also understand the project structure, configure the application, create a controller, implement endpoints, and test everything using Postman.
By the end of this tutorial, you'll have a basic working Web API that you can run on your local machine.
1. Prerequisites
Before starting, make sure you have the following tools installed:
| Tool | Purpose |
|---|---|
| .NET 8 SDK | Build and run the Web API |
| Visual Studio 2022 or later | Development environment |
| C# | Programming language |
| Postman | Test HTTP requests |
You can also use Visual Studio Code with the .NET SDK.
To check whether the .NET SDK is installed, open Command Prompt or a terminal and execute:
dotnet --versionYou should see an installed SDK version. For this tutorial, use a .NET 8 SDK.
You can verify the installed SDKs using:
dotnet --list-sdks2. Create a new .NET Core Web API project
There are two convenient ways to create a Web API project.
Option A: Using Visual Studio
Open Visual Studio.
Select Create a new project.
Search for
ASP.NET Core Web API.Select the Web API template.
Click Next.
Enter the project name:
EmployeeManagementAPI.Select the project location.
Click Next.
Choose
.NET 8.0as the framework.Select the Controller-based option if the wizard offers it.
Click Create.
For this tutorial, we'll use Controllers rather than Minimal APIs.
Option B: Using the .NET CLI
You can also create the project directly from the terminal.
Execute:
dotnet new webapi --use-controllers -n EmployeeManagementAPIMove into the project directory:
cd EmployeeManagementAPIRestore the project dependencies:
dotnet restoreBuild the application:
dotnet buildIf the build completes successfully, your project is ready for development.
3. Understand the project structure
After creating the project, you'll see a structure similar to this:
EmployeeManagementAPI
│
├── Controllers
│ └── WeatherForecastController.cs
│
├── Properties
│ └── launchSettings.json
│
├── appsettings.json
├── appsettings.Development.json
├── EmployeeManagementAPI.csproj
└── Program.csLet's understand the important files.
Controllers
This folder contains controller classes that handle incoming HTTP requests.
For example, we'll create:
Controllers
└── EmployeesController.csProgram.cs
This file contains application startup configuration, service registration, middleware, and endpoint mapping.
appsettings.json
This file contains application configuration settings, such as connection strings and logging configuration.
launchSettings.json
This file contains development launch profiles, including local application URLs and environment settings.
For this tutorial, we'll keep the project simple and focus on the files needed to build our first API.
4. Create the Employee model
A model represents the data structure used by our application.
In our Employee Management application, every employee will contain:
Employee ID
Employee Name
Department
Email Address
Create a folder named Models.
Inside it, create a file named Employee.cs.
Project structure:
EmployeeManagementAPI
│
├── Controllers
├── Models
│ └── Employee.cs
│
└── Program.csAdd the following code:
namespace EmployeeManagementAPI.Models;
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Department { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}Let's understand the properties.
| Property | Data type | Purpose |
|---|---|---|
| Id | int | Unique employee identifier |
| Name | string | Employee name |
| Department | string | Employee department |
| string | Employee email address |
We'll use this model to represent employee information in our API.
In a later article, we'll introduce DTOs to separate API request and response models from application entities.
5. Configure Program.cs
Now open Program.cs.
This is the entry point of our application.
Replace its contents with:
var builder = WebApplication.CreateBuilder(args);
// Register controller services
builder.Services.AddControllers();
var app = builder.Build();
// Configure middleware
if (app.Environment.IsDevelopment())
{
// Development-specific configuration can go here
}
app.UseHttpsRedirection();
// Map controller endpoints
app.MapControllers();
app.Run();Let's understand the important statements.
1. Create the builder
var builder = WebApplication.CreateBuilder(args);This creates the application builder and loads the default application configuration.
2. Register controllers
builder.Services.AddControllers();This registers the services required for controller-based API development.
3. Build the application
var app = builder.Build();This builds the application using the registered services and configuration.
4. Enable HTTPS redirection
app.UseHttpsRedirection();This middleware redirects HTTP requests to HTTPS when the application has an appropriate HTTPS endpoint configured.
5. Map controller endpoints
app.MapControllers();This makes attribute-routed controller endpoints available to the application's routing system.
6. Run the application
app.Run();This starts the web application and begins accepting requests.
Our basic application configuration is now ready.
6. Create the EmployeesController
Now we'll create the main controller that handles employee-related operations.
Inside the Controllers folder, create:
EmployeesController.csAdd the following code:
using Microsoft.AspNetCore.Mvc;
using EmployeeManagementAPI.Models;
namespace EmployeeManagementAPI.Controllers;
[ApiController]
[Route("api/[controller]")]
public class EmployeesController : ControllerBase
{
private static readonly List<Employee> Employees =
[
new Employee
{
Id = 101,
Name = "Rahul",
Department = "IT",
Email = "rahul@example.com"
},
new Employee
{
Id = 102,
Name = "Priya",
Department = "HR",
Email = "priya@example.com"
},
new Employee
{
Id = 103,
Name = "Amit",
Department = "Finance",
Email = "amit@example.com"
}
];
private static int _nextId = 104;
// GET: api/employees
[HttpGet]
public IActionResult GetEmployees()
{
return Ok(Employees);
}
// GET: api/employees/101
[HttpGet("{id:int}")]
public IActionResult GetEmployeeById(int id)
{
var employee = Employees.FirstOrDefault(x => x.Id == id);
if (employee is null)
{
return NotFound(new
{
Message = "Employee not found."
});
}
return Ok(employee);
}
// POST: api/employees
[HttpPost]
public IActionResult CreateEmployee(Employee employee)
{
employee.Id = _nextId++;
Employees.Add(employee);
return CreatedAtAction(
nameof(GetEmployeeById),
new { id = employee.Id },
employee);
}
// PUT: api/employees/101
[HttpPut("{id:int}")]
public IActionResult UpdateEmployee(
int id,
Employee updatedEmployee)
{
var employee = Employees.FirstOrDefault(x => x.Id == id);
if (employee is null)
{
return NotFound(new
{
Message = "Employee not found."
});
}
employee.Name = updatedEmployee.Name;
employee.Department = updatedEmployee.Department;
employee.Email = updatedEmployee.Email;
return Ok(employee);
}
// DELETE: api/employees/101
[HttpDelete("{id:int}")]
public IActionResult DeleteEmployee(int id)
{
var employee = Employees.FirstOrDefault(x => x.Id == id);
if (employee is null)
{
return NotFound(new
{
Message = "Employee not found."
});
}
Employees.Remove(employee);
return NoContent();
}
}Now let's understand how each API endpoint works.
7. Understand the API endpoints
We have created five endpoints using different HTTP methods.
A. GET – Retrieve all employees
[HttpGet]
public IActionResult GetEmployees()
{
return Ok(Employees);
}This endpoint retrieves all employees from the collection.
Request:
GET /api/employeesExpected status:
200 OKResponse:
[
{
"id": 101,
"name": "Rahul",
"department": "IT",
"email": "rahul@example.com"
},
{
"id": 102,
"name": "Priya",
"department": "HR",
"email": "priya@example.com"
},
{
"id": 103,
"name": "Amit",
"department": "Finance",
"email": "amit@example.com"
}
]The Ok() method returns an HTTP 200 response containing the employee collection.
B. GET – Retrieve an employee by ID
[HttpGet("{id:int}")]
public IActionResult GetEmployeeById(int id)
{
var employee = Employees.FirstOrDefault(x => x.Id == id);
if (employee is null)
{
return NotFound();
}
return Ok(employee);
}This endpoint retrieves an individual employee using their ID.
Request:
GET /api/employees/101Expected response:
{
"id": 101,
"name": "Rahul",
"department": "IT",
"email": "rahul@example.com"
}If the employee does not exist, the API returns:
404 Not FoundC. POST – Create a new employee
[HttpPost]
public IActionResult CreateEmployee(Employee employee)
{
employee.Id = _nextId++;
Employees.Add(employee);
return CreatedAtAction(
nameof(GetEmployeeById),
new { id = employee.Id },
employee);
}This endpoint creates a new employee.
Request:
POST /api/employeesRequest body:
{
"name": "Neha",
"department": "Marketing",
"email": "neha@example.com"
}The API assigns a new ID and adds the employee to the collection.
Expected status:
201 CreatedExample response:
{
"id": 104,
"name": "Neha",
"department": "Marketing",
"email": "neha@example.com"
}CreatedAtAction() returns a 201 response and identifies the location of the newly created employee.
D. PUT – Update an existing employee
[HttpPut("{id:int}")]
public IActionResult UpdateEmployee(
int id,
Employee updatedEmployee)
{
var employee = Employees.FirstOrDefault(x => x.Id == id);
if (employee is null)
{
return NotFound();
}
employee.Name = updatedEmployee.Name;
employee.Department = updatedEmployee.Department;
employee.Email = updatedEmployee.Email;
return Ok(employee);
}This endpoint updates an existing employee.
Request:
PUT /api/employees/101Request body:
{
"name": "Rahul Sharma",
"department": "Engineering",
"email": "rahul.sharma@example.com"
}Expected response:
{
"id": 101,
"name": "Rahul Sharma",
"department": "Engineering",
"email": "rahul.sharma@example.com"
}The ID comes from the URL. The remaining fields are taken from the request body.
E. DELETE – Remove an employee
[HttpDelete("{id:int}")]
public IActionResult DeleteEmployee(int id)
{
var employee = Employees.FirstOrDefault(x => x.Id == id);
if (employee is null)
{
return NotFound();
}
Employees.Remove(employee);
return NoContent();
}This endpoint removes an employee from the collection.
Request:
DELETE /api/employees/101Expected status:
204 No ContentA 204 response indicates that the operation succeeded and the server has no response body to return.
8. Run your first Web API
Now that we have created the model, controller, and application configuration, let's run the project.
Open the terminal inside your project directory and execute:
dotnet runYou should see output indicating that the application is listening on a local address.
For example:
Now listening on: https://localhost:7001
Now listening on: http://localhost:5001
Application started.These ports are examples. Your application may use different ports depending on its launch settings.
Copy the HTTPS URL displayed in your terminal.
For example:
https://localhost:7001Your employee API base URL will be:
https://localhost:7001/api/employeesYour first Web API is now running.
9. Test the API using Postman
Postman allows you to send HTTP requests and inspect API responses without building a frontend application.
Let's test each endpoint.
Test 1: Retrieve all employees
Method: GET
URL:
https://localhost:7001/api/employees
Click Send.
You should receive a 200 OK response containing all employees.
Test 2: Retrieve an employee by ID
Method: GET
URL:
https://localhost:7001/api/employees/101
Expected result: Employee Rahul.
Test 3: Create a new employee
Method: POST
URL:
https://localhost:7001/api/employeesSelect Body → raw → JSON.
Request body:
{
"name": "Neha",
"department": "Marketing",
"email": "neha@example.com"
}Click Send.
Expected result: 201 Created.
Test 4: Update an employee
Method: PUT
URL:
https://localhost:7001/api/employees/101Select Body → raw → JSON.
Request body:
{
"name": "Rahul Sharma",
"department": "Engineering",
"email": "rahul.sharma@example.com"
}Expected result: 200 OK.
Test 5: Delete an employee
Method: DELETE
URL:
https://localhost:7001/api/employees/101
Expected result: 204 No Content.
After deletion, send the GET request again:
GET /api/employees/101You should receive:
404 Not FoundThis confirms that the employee was removed from the in-memory collection.
10. Understand the complete project structure
After completing the implementation, your project should look like this:
EmployeeManagementAPI
│
├── Controllers
│ └── EmployeesController.cs
│
├── Models
│ └── Employee.cs
│
├── Properties
│ └── launchSettings.json
│
├── appsettings.json
├── appsettings.Development.json
├── EmployeeManagementAPI.csproj
└── Program.csLet's understand the request flow:
Postman / Client Application
|
v
HTTP Request
|
v
Kestrel
|
v
Middleware
|
v
Routing
|
v
EmployeesController
|
v
Employee Collection
|
v
HTTP Response
|
v
Postman / Client ApplicationThis connects the concepts we learned in Days 1 and 2 with the practical implementation.
11. Important limitations of this example
Our first API uses an in-memory collection to keep the implementation beginner-friendly.
However, there are some limitations you should understand.
1. Data is not permanent
The employee records exist only while the application is running. Restarting the application resets the collection to its initial values.
2. No SQL Server integration
We are not using a database yet. Database connectivity will be introduced in the upcoming Week 2 articles.
3. No separate service layer
For simplicity, the controller directly handles the collection operations. In a larger application, business logic should be moved into appropriate services.
4. No DTOs
We are using the Employee model directly for request and response data. Later, we'll understand why DTOs are important for separating API contracts from internal application models.
5. Not production-ready storage
A static in-memory list is not suitable for a multi-user production application. It is not persistent or designed for concurrent data modification.
The purpose of this tutorial is to understand the basic structure and working of a controller-based Web API before adding more advanced architecture.
12. Common mistakes beginners should avoid
1. Forgetting AddControllers()
If you are using controllers, register them in the service container:
builder.Services.AddControllers();2. Forgetting MapControllers()
Without mapping controller endpoints, the application will not expose the controller routes:
app.MapControllers();3. Using the wrong API URL
Check the actual port displayed when your application starts.
4. Sending invalid JSON
When using Postman, select Body → raw → JSON and provide valid JSON.
5. Using the wrong HTTP method
A GET endpoint cannot be called using POST unless an endpoint supporting POST has also been defined.
6. Expecting data to survive an application restart
Our example uses an in-memory collection. It is not connected to SQL Server.
13. Interview questions
Q1. How do you create a Web API project in .NET 8?
You can create a controller-based Web API using the Visual Studio Web API template or the CLI command:
dotnet new webapi --use-controllers -n EmployeeManagementAPIQ2. What is the purpose of Program.cs?
Program.cs is the application's entry point. It is used to configure services, middleware, application settings, and endpoint mapping.
Q3. What is the purpose of AddControllers()?
It registers the services required for controller-based API development.
Q4. What is the purpose of MapControllers()?
It maps attribute-routed controller endpoints so incoming requests can reach the appropriate controller actions.
Q5. What is ControllerBase?
ControllerBase is a base class that provides functionality commonly used by API controllers, such as returning HTTP responses.
Q6. What is IActionResult?
IActionResult represents an HTTP action result. It allows a controller action to return different response types, such as 200 OK, 201 Created, 400 Bad Request, and 404 Not Found.
Q7. What is the difference between Ok() and CreatedAtAction()?
Ok() returns an HTTP 200 response, while CreatedAtAction() returns an HTTP 201 response and identifies the newly created resource.
Q8. Why do we use [ApiController]?
It enables API-specific conventions, including automatic model validation responses for invalid models in supported scenarios, binding-source inference, and other API behaviors.
Q9. Why is an in-memory collection not suitable for production?
It does not provide permanent storage, is not shared across multiple application instances, and requires appropriate concurrency handling. Production applications generally use persistent storage such as SQL Server.
Q10. What is the difference between a Web API and a frontend application?
A frontend application provides the user interface and sends requests, while a Web API handles backend operations and returns data or HTTP responses.
Conclusion
Congratulations! You have now built your first controller-based .NET Core Web API using C# and .NET 8.
In this tutorial, we:
Created a new Web API project.
Understood the project structure.
Created an Employee model.
Configured Program.cs.
Created an EmployeesController.
Implemented GET, POST, PUT, and DELETE endpoints.
Ran the API locally.
Tested the endpoints using Postman.
You have now moved from understanding Web API concepts to implementing a working application.
The next step is to understand how the client and server exchange structured data.
What's next?
In Day 7, we'll explore Understanding Request and Response with JSON.
We'll understand request headers, request bodies, response bodies, content types, JSON serialization, deserialization, and how data travels between an Angular application and a .NET Core Web API.
Skills Tutorials – Learn • Build • Grow Together
Continue learning with our 30-day .NET Core Web API series.