This is a tutorial on how to implement a simple Employee database management REST API application in ASP.NET, utilizing design patterns like Repository and Dependency Injection. The application uses in-memory database.
This tutorial is actually an updated and combined version of my two previous tutorials in my GitHub pages – How to use in-memory database and How to implement a REST API with Repository Design – created and compiled using the latest Visual Studio Community 2022, .NET 6.0 as the target framework, ASP.NET Core, and EF Core.
You can download the Visual Studio project here!
Content
- How application works
- The API illustrated
- Project Setup
- Employee class
- EmployeeDBContext
- IEmployeeRepository interface
- EmployeeRepository
- EmployeesController
- How to configure in-memory database
- Conclusion
How the application works
Before I discuss the implementation of the application, it would be nice to show you first how I tested the application with a REST client, so that you get the big picture of how the application works. I am using the Boomerang REST Client plugin for Google Chrome to test my API. Let’s start by inserting an employee record in the database.
Insert an employee record in the database
Using the REST client is simple and straightforward. Enter the URL of our API in the address box just like in a web browser. Specify POST as the HTTP method. On the left panel under the JSON tab, enter the employee data that you want to enter. Then hit SEND.
On the right panel, you get a 201 Created response back if everything goes smoothly, and also the employee record that has just been created. If you click the HEADERS tab, also on the right panel , you also get a glimpse of the headers (illustrated below).
Let’s retrieve what we have just created!
Retrieve all employees
Below is how to retrieve all employees.
As shown above, to retrieve ALL employees, just don’t append any id number at the end of the URL.
Retrieve employee given id number
To retrieve an employee given an id number, add the id number at the end of the URL, and select GET
as HTTP method.
After hitting SEND
, you get the employee data in the BODY
tab on the right panel, and you also get 200 OK
response. If you don’t specify the id at the end of the URL, you get all the employees in the database.
Retrieve employee that does not exist
What if we use an id that does not exist when making a GET request? If the entity does not exist in the database, that is, if employee ID is not found, we then get a 404 Not Found .
Modify a record using PUT
Let’s say, we want to modify the age of an employee.
As you notice above, we are sending the whole employee record as part of our request despite the fact I just want to change one field (this is a PUT requirement).
In this example, I want to change the employee’s last name – change it from “Bowie” to “Smith”. So, in the JSON data, I set the value of the LName
to “Smith”, append “1” (id number) at the end of the URL, and select PUT
as the HTP method. You also need to specify the corresponding id number in the body of the data. After I hit SEND
, I get 200 OK and also the record that has just been changed.
Modify a record using PATCH
With PATCH method, you only need to send the field that you want changed, as opposed to sending the whole employee entity with PUT.
As illustrated above, except for the body of data, everything else is the same. You specify the employee id in the URL. Then, in the body you specify 3 things:
"op"
for operation"path"
for key or field you want to change"value"
for new value
the operation that you want to perform (replace), the field that you want to change, and then the new value. In this example, I want to change the email address from “bowie@gmail.com” to “davidbowie@gmail.com”. After you hit send, you should get OK response and the modified record.
Now, on to our regular programming!
API Illustrated
The bulk of our code will reside in the Controller and Repository classes. You can remove the Repository component from the equation and the application will still work; however, without repository, you will be putting a lot of data access logic in the controller which is not a good practice.
The DbContext component of the application uses the EntityFramework library, which allows us to work with data in the database without us writing any SQL-related code. You access the tables and fields via class objects and class properties (at least, that’s my own definition) . In a way, your application is not tightly coupled with a specific database.
The controller works like a traffic controller which directs the flow of requests and responses; it contains all the business or service logic of the application. The Controller will have all the HTTP methods(GET, POST, PUT, PATCH, DELETE) implemented.
So, next I’ll discuss how to set up your project in Visual Studio.
Project Setup
In Visual Studio, create ASP.NET Web Core API project. I named my project InMemoryDbAspNet6WebAPI, and named my solution ProjectDB. You can check out my previous post on how to create a project in Visual Studio Community 2022 to see how I created an ASP.NET Web Core API project and a Console Application project inside a solution.
After the project creation is done, you should have something similar to the project illustrated below:
Everything we need to run our own API has been created for us. Our main program (Program.cs) has been configured to run a custom API which you can create inside the Controllers directory. In fact, this newly-created application is ready to run and if you hit F5(or Ctrl-F5) it will open up to the default WeatherForecast API.
Install Nuget Packages
After the project is set up, we need to install some packages in Visual Studio to enable us to work with databases, and these packages are simply libraries that our application will need. These are the packages that we need to install:
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.InMemory
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.AspNetCore.JsonPatch
To install a package in Visual Studio, simply right-click the project in the Solution Explorer (as illustrated below) and select Manage Nuget Packages.
The Nuget Package Manager will be displayed as illustrated below:
Click the Browse tab on the top. In the search box, type in what you’re looking for. In our example, type EntityFramework, and it should display EntityFramework package as the first one in the list. Click that package. On the right panel, click the Install button, and after you do so, Install button will change to Uninstall to let you know it’s been installed and that you can uninstall it later if you want.
Repeat this process to install the other packages that I listed above. And with this, we are ready to implement our API!
Classes
This is the list of all the classes that we are going to implement (in this order):
- Employee.cs
- EmployeeDBContext.cs
- IEmployeeRepository.cs
- EmployeeRepository.cs
- EmployeeController.cs
- Configure main program to use In-MemoryDatabase service, and EmployeeRepository service.
Without further ado, let’s begin with the Employee class!
Employee class
This class will represent an employee record(or table). To create this class, right-click the project, click Add, and click class. Name it Employee.cs
, and it should create a fairly empty class with no property or method. Copy and paste the code below:
using System.ComponentModel.DataAnnotations;
namespace InMemoryDbAspNet6WebAPI;
public class Employee
{
public int Id { get; set; }
[Required]
public string FName { get; set; }
[Required]
public string LName { get; set; }
[EmailAddress(ErrorMessage = "E-mail is not valid")]
public string Email { get; set; }
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime DateOfHire { get; set; }
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime DateOfBirth { get; set; }
public string Position { get; set; }
public string Department { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zipcode { get; set; }
}
As you can see above, we define fields that identify an employee such as their names and address.
EmployeeDBContext class
EmployeeDBContext class will represent our employee database. To create this class, right-click the project, click Add, and click class. Name it EmployeeDBContext.cs and copy the source code shown below:
using Microsoft.EntityFrameworkCore;
namespace InMemoryDbAspNet6WebAPI;
public class EmployeeDBContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
public EmployeeDBContext(DbContextOptions<EmployeeDBContext> options)
: base(options)
{
}
}
Line 4 shows us that our EmployeeDBContext
class inherits from the DbContext
base class, and later on we will see that we can pass an object to DbContext
via the constructor. On line 6 we declare a collection of Employees to represent rows of Employees in our database, using DbSet
.
On line 8, we pass a DbContextOptions
object to our DBContext
base. This is important. The options
parameter, as you will see later, is set as UseInMemoryDatabase
in the main program which allows us to use the memory for our database.
IEmployeeRepository interface
The IEmployeeRepository
interface will define the method that will be implemented in the EmployeeRepository service. To create this interface, right-click the project, click Add, click New Item, and click Interface in the list. Name it IEmployeeRepository.cs. Copy the source code shown below:
using Microsoft.AspNetCore.JsonPatch;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace InMemoryDbAspNet6WebAPI;
public interface IEmployeeRepository
{
Task<IEnumerable<Employee>> GetEmployeesAsync();
Task<Employee> GetEmployeeByIdAsync(int id);
Task<Employee> AddEmployeeAsync(Employee employee);
Task<Employee> DeleteEmployeeAsync(int id);
Task<Employee> UpdateEmployeeAsync(int id, Employee employee);
Task<Employee> UpdateEmployeePatchAsync(int id, JsonPatchDocument employee);
}
To briefly explain, I am using repository design in order to decouple the database implementation from the controller, which means this technique allows us to use a different data store in the future. This also means that instead of instantiating our DbContext
inside the controller, we will inject IEmployeeRepository
into the controller’s constructor instead. The complete interface is shown below:
As you can see in the source code, the interface defines five methods – the first one, called GetEmployees()
, returns all the employees from the database, and the last four corresponds to HTTP methods GET, POST, DELETE, and PUT. Also, notice that the methods return Task<Employee> as the service is implemented as asynchronous.
EmployeeRepository class
EmployeeRepository
will inherit from IEmployeeRepository
, and will implement all the interface’s methods. To create this class, right-click the project, click Add, and click class. Name it EmployeeRepository.cs. Copy the source code shown below :
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.AspNetCore.JsonPatch;
namespace InMemoryDbAspNet6WebAPI;
public class EmployeeRepository : IEmployeeRepository
{
private readonly EmployeeDBContext _dbContext;
public EmployeeRepository(EmployeeDBContext dBContext)
{
_dbContext = dBContext;
}
public async Task<IEnumerable<Employee>> GetEmployeesAsync()
{
var query = (from employees in _dbContext.Employees
select employees).ToListAsync();
return await query;
}
public async Task<Employee> GetEmployeeByIdAsync(int id)
{
Employee employee = await _dbContext.Employees.FindAsync(id);
return employee;
}
public async Task<Employee> AddEmployeeAsync(Employee employee)
{
_dbContext.Employees.Add(employee);
await _dbContext.SaveChangesAsync();
return employee;
}
public async Task<Employee> DeleteEmployeeAsync(int id)
{
var employee = await GetEmployeeByIdAsync(id);
if(employee == null)
{
return employee;
}
_dbContext.Employees.Remove(employee);
await _dbContext.SaveChangesAsync();
return employee;
}
public async Task<Employee> UpdateEmployeeAsync(int id, Employee employee)
{
var employeeQuery = await GetEmployeeByIdAsync(id);
if(employeeQuery == null)
{
return employeeQuery;
}
_dbContext.Entry(employeeQuery).CurrentValues.SetValues(employee);
await _dbContext.SaveChangesAsync();
return employeeQuery;
}
public async Task<Employee> UpdateEmployeePatchAsync(int id, JsonPatchDocument employeeDocument)
{
var employeeQuery = await GetEmployeeByIdAsync(id);
if (employeeQuery == null)
{
return employeeQuery;
}
employeeDocument.ApplyTo(employeeQuery);
await _dbContext.SaveChangesAsync();
return employeeQuery;
}
}
Line 7 shows EmployeeRepository inheriting from IEmployeeRepository
. Line 11 shows the constructor getting the DBContext which is then stored as a local variable. Next, we will talk about each method in depth starting with GetEmployees().
GetEmployeesAsync method
GetEmployeesAsync on line 16 is shown below:
public async Task<IEnumerable<Employee>> GetEmployeesAsync()
{
var query = (from employees in _dbContext.Employees
select employees).ToListAsync();
return await query;
}
GetEmployeesAsync
simply returns a list of all the employees in the database. Take note of LINQ command that I use to select all the employees. Because I’m returning a collection of database, I have to convert the result to a list, thus the call to .ToListAsync()
. (Note: Something I completely overlooked is how I named this method. I should have named this GetEmployeesAsync
!)
GetEmployeeByIdAsync method
GetEmployeeByIdAsync
on line 23 is shown below:
public async Task<Employee> GetEmployeeByIdAsync(int id)
{
Employee employee = await _dbContext.Employees.FindAsync(id);
return employee;
}
GetEmployeeByIdAsync
returns an Employee object given the employee ID. To find that ID, all we need to is call FindAsync(id)
method which is a method of DbSet class – remember in our EmployeeDBContext
class, we had this declaration:
public DbSet<Employee> Employees { get; set; }
AddEmployeeAsync method
AddEmployeeAsync
on line 30 is shown below:
public async Task<Employee> AddEmployeeAsync(Employee employee)
{
_dbContext.Employees.Add(employee);
await _dbContext.SaveChangesAsync();
return employee;
}
AddEmployeeAsync inserts an Employee object into the database. This method is really simple – we simple call the the Add method of DbSet class passing it the Employee object, then call SaveChangesAsync after that. I’m returning back the employee in case the API wants to do something to it (return it back to client as response, etc.).
DeleteEmployeeAsync method
The DeleteEmployeeAsync
method on line 38 is shown below:
public async Task<Employee> DeleteEmployeeAsync(int id)
{
var employee = await GetEmployeeByIdAsync(id);
if(employee == null)
{
return employee;
}
_dbContext.Employees.Remove(employee);
await _dbContext.SaveChangesAsync();
return employee;
}
DeleteEmployeeAsync
removes an Employee object into the database given the employee ID. The first thing that the method does is check if the employee with that ID exists and if it doesn’t it just returns null (because employee
variable is null). Otherwise, it goes ahead and removes the employee from the database. Finally, you do a SaveChangesAsync
to make sure the removal is finalized and saved.
UpdateEmployeeAsync
The UpdateEmployeeAsync
method on line 53 is shown below:
public async Task<Employee> UpdateEmployeeAsync(int id, Employee employee)
{
var employeeQuery = await GetEmployeeByIdAsync(id);
if(employeeQuery == null)
{
return employeeQuery;
}
_dbContext.Entry(employeeQuery).CurrentValues.SetValues(employee);
await _dbContext.SaveChangesAsync();
return employeeQuery;
}
The UpdateEmployeeAsync
method updates the employee’s record based on what has changed in the json data. On line 9, we then set the value of the current employee object to the new one.
Finally, you need to call SaveChanges
to actually update the database.
And that completes our DbContext
and our EmployeeRepository
service. Now, on to the EmployeesController!
UpdateEmployeePatchAsync
While UpdateEmployeeAsync
is a full record update, UpdateEmployeePatchAsync
can be used for a partial update, that is, you don’t have to send the whole employee record just to update one field. Client can just send a json document containing the specific field to be modified and its new value.
public async Task<Employee> UpdateEmployeePatchAsync(int id, JsonPatchDocument employeeDocument)
{
var employeeQuery = await GetEmployeeByIdAsync(id);
if (employeeQuery == null)
{
return employeeQuery;
}
employeeDocument.ApplyTo(employeeQuery);
await _dbContext.SaveChangesAsync();
return employeeQuery;
}
To update our record, we use the ApplyTo()
method on line 8, and it basically uses the jason document that was sent from the client, and applies it to the current record.
EmployeesController
The controller is where our API lives, and it is where Http methods are implemented. To create this class, right-click the Controllers folder, click Add, and click Controller. Name it EmployeesController.cs. Copy the source code below:
using Microsoft.AspNetCore.JsonPatch;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace InMemoryDbAspNet6WebAPI.Controllers;
[Route("api/v2/[controller]")]
[ApiController]
public class EmployeesController : Controller
{
private readonly IEmployeeRepository _employeeRepository;
public EmployeesController(IEmployeeRepository employeeRepository)
{
_employeeRepository = employeeRepository;
}
// GET: api/v2/Employees/
[HttpGet]
public async Task<IEnumerable<Employee>> GetEmployees()
{
return await _employeeRepository.GetEmployeesAsync();
}
// GET: api/v2/Employees/5
[HttpGet("{id}")]
public async Task<IActionResult> GetEmployee([FromRoute] int id)
{
var employee = await _employeeRepository.GetEmployeeByIdAsync(id);
if (employee == null)
{
return NotFound();
}
return Ok(employee);
}
// POST: api/v2/Employees
[HttpPost]
public async Task<IActionResult> PostEmployee([FromBody] Employee employee)
{
await _employeeRepository.AddEmployeeAsync(employee);
return CreatedAtAction("GetEmployee", new { id = employee.Id }, employee);
}
// DELETE: api/v2/Employees/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteEmployee([FromRoute] int id)
{
var employee = await _employeeRepository.DeleteEmployeeAsync(id);
if (employee == null)
{
return NotFound();
}
return Ok(employee);
}
// PUT: api/v2/Employees/5
[HttpPut("{id}")]
public async Task<IActionResult> PutEmployee([FromRoute] int id, [FromBody] Employee employee)
{
if (id != employee.Id)
{
return BadRequest();
}
var updatedEmployee = await _employeeRepository.UpdateEmployeeAsync(id, employee);
if (updatedEmployee == null)
{
return NotFound();
}
return Ok(updatedEmployee);
}
// PATCH: api/v2/Employees/5
[HttpPatch("{id}")]
public async Task<IActionResult> PatchEmployee([FromRoute] int id, [FromBody] JsonPatchDocument employeeDocument)
{
var updatedEmployee = await _employeeRepository.UpdateEmployeePatchAsync(id, employeeDocument);
if (updatedEmployee == null)
{
return NotFound();
}
return Ok(updatedEmployee);
}
}
The controller implements GetEmployees
, GetEmployee
, PostEmployee
, DeleteEmployee
, and PutEmployee
, which correspond to the API’s GET, POST, DELETE and PUT methods, which in turn, correspond to select, insert, delete and update database query, respectively. We will discuss each of these methods next, but first let’s talk about dependency injection.
Dependency injection
The reason why we defined IEmployeeRepository
interface earlier is because we want to use it in our controller instead of using the DbContext
. And the way to do this is via dependency injection in the constructor, as shown below (line 13 in the above code):
public EmployeesController(IEmployeeRepository employeeRepository)
{
_employeeRepository = employeeRepository;
}
By doing this we get an instance of the EmployeeRepository
service which we, then, store in our local variable _employeeRepository
.
Now, let’s discuss each of the action methods of the controller.
GetEmployees()
First, let’s discuss GetEmployees()
method as shown below (line 19 in the above code).
// GET: api/v2/Employees/
[HttpGet]
public async Task<IEnumerable<Employee>> GetEmployees()
{
return await _employeeRepository.GetEmployees();
}
This method calls the corresponding IEmployeeRepository.GetEmployees()
method that we talked about earlier, and returns all employee records from the database.
We put [HttpGet]
attribute above the method because we want this method to only handle GET requests. Moreover, the [HttpGet]
attribute creates our GET endpoint that corresponds to http://(servername)/api/v2/Employees/ – the URI that the client can use to send a GET request to our API to request all employees.
GetEmployee(int id)
This method, shown below (line 26 in the above code) calls the GetEmployeeByIdAsync(id)
method.
// GET: api/v2/Employees/5
[HttpGet("{id}")]
public async Task<IActionResult> GetEmployee([FromRoute] int id)
{
var employee = await _employeeRepository.GetEmployeeByIdAsync(id);
if (employee == null)
{
return NotFound();
}
return Ok(employee);
}
Also, notice that GetEmployeeByIdAsync
may return null if it doesn’t find that id in the database, in which case, it simply returns a NotFound()
status back to the client; otherwise, it returns the Employee object as well as a 200 status code(Success).
Like GetEmployees()
, we add [HttpGet("{id}")]
attribute above the method because we want it to handle only GET requests; but this time we also indicate that this method requires id. Moreover, the [HttpGet("{id}")]
attribute creates our GET endpoint that corresponds to http://(servername)/api/v2/Employees/5 – the URI that the client can use to send our API a GET request to request an Employee object with id=5.
PostEmployee(Employee employee)
This method, shown below (line 38 in the above code) calls the AddEmployeeAsync(Employee employee)
and returns 201 status code (Created).
// POST: api/v2/Employees
[HttpPost]
public async Task<IActionResult> PostEmployee(Employee employee)
{
await _employeeRepository.AddEmployeeAsync(employee);
return CreatedAtAction("GetEmployee", new { id = employee.Id }, employee);
}
After the inserting Employee into the database, we call CreatedAtAction(Action Name, Route Values, Created Resource)
to allow us to return a resource location(URI) in the HTTP Response header – in this example, I want to return http://localhost:5000/api/v2/Employees/1.
So, for the first parameter, the Action Name, I pass “GetEmployee” which refers to the GetEmployee()
method of the controller. For the second parameter, Route Values, I pass the value of the newly created employee.Id
. The third parameter can be anything really – in this example, I am returning the newly created Employee object; later on in the demonstration, you will see how this third parameter object gets used in the REST client.
We add the [HttpPost]
attribute above the method to mark this action as a handler for POST requests only. Moreover, the [HttPost]
attribute creates our POST endpoint that corresponds to http://(servername)/api/v2/Employees/ – the URI that the client can use to send a POST request to our API and attach employee data in body of the request.
DeleteEmployee(int id)
This method, shown below (line 46 in the above code) calls the DeleteEmployeeAsync(id)
method and it deletes an employee from the database given the employee id.
// DELETE: api/v2/Employees/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteEmployee(int id)
{
var employee = await _employeeRepository.DeleteEmployeeAsync(id);
if (employee == null)
{
return NotFound();
}
return Ok(employee);
}
Similar to GetEmployee(int id)
, it also searches for employee with the given id, and it also returns null if it doesn’t find an employee with that id. When things go smoothly, we return a 200 status code (Success) and the Employee object back to the client.
We add [HttpDelete("{id}")]
attribute because we want this method to only handle DELETE requests. Moreover, the [HttpDelete("{id}")]
attribute creates our DELETE endpoint that corresponds to http://(servername)/api/v2/Employees/5 – the URI that the client can use to send a DELETE request to remove an employee with id=5.
PutEmployee(int id, Employee employee)
This method shown below (line 60 in the above code) calls UpdateEmployeeAsync(employee)
, and updates an employee record with the given id.
// PUT: api/v2/Employees/5
[HttpPut("{id}")]
public async Task<IActionResult> PutEmployee([FromRoute] int id, [FromBody] Employee employee)
{
if (id != employee.Id)
{
return BadRequest();
}
var updatedEmployee = await _employeeRepository.UpdateEmployeeAsync(id, employee);
if (updatedEmployee == null)
{
return NotFound();
}
return Ok(updatedEmployee);
}
But before we call UpdateEmployeeAsync
, we first check if id is the same as the employee entity’s id, and if it isn’t, we simply return "BadRequest"
. If everything is good, we then call UpdateEmployeeAsync
.
When calling this endpoint from our REST client, we attach an entity that has all the properties and their values whether or not these were actually updated.
It is important to note that PUT action updates the entity in its entirety. Read usage examples from this Microsoft doc.
According to the HTTP specification, a PUT request requires the client to send the entire updated entity, not just the changes. To support partial updates, use HTTP PATCH.
And finally, we mark this method with [HttpPut(“{id}”)] attribute so it only handles PUT requests. This attribute also creates our PUT endpoint that corresponds to http://(servername)/api/v2/Employees/5 – the URI that the client can use to send a POST request to update employee data, and attach a employee data in the body of the request.
PatchEmployee(int id, Employee employee)
This method shown below (line 60 in the above code) calls UpdateEmployeePatchAsync(id, employee)
, and updates an employee record with the given id, and the json document that contains the changes you want to make in the database.
// PATCH: api/v2/Employees/5
[HttpPatch("{id}")]
public async Task<IActionResult> PatchEmployee([FromRoute] int id, [FromBody] JsonPatchDocument employeeDocument)
{
var updatedEmployee = await _employeeRepository.UpdateEmployeePatchAsync(id, employeeDocument);
if (updatedEmployee == null)
{
return NotFound();
}
return Ok(updatedEmployee);
}
Some words about attribute routing
If you have noticed, we have been using attribute routing for our Web API application. The attributes such as [HttpGet]
and [HttpPost]
that we add above our methods is a way to match or map the URLs of incoming requests to actions (our controller methods such as GetEmployees
, PostEmployees
, etc.). That is, if an incoming request is GET http://(servername)/api/v2/Employees/5 we will be able to rout it to GetEmployees(int id)
method.
This is the recommended way of routing in ASP.NET Core 6.0 for REST APIs:
REST APIs should use attribute routing to model the app’s functionality as a set of resources where operations are represented by HTTP verbs.
Attribute routing uses a set of attributes to map actions directly to route templates.
This is a quote from this Microsoft Docs page which I highly recommend for reading! You can learn a lot about various ways routing for REST API and MVC.
Besides those attributes we put above the methods, we also have the controller attribute that applies to all the methods – [Route]
and [ApiController]
, as shown below (line 7 in the above code)
[Route("api/v2/[controller]")]
[ApiController]
public class EmployeesController : Controller
The "api/v2/[controller]"
string inside the attribute is called the route template, and that string becomes part of our desired URI – http://localhost:5000/api/v2/Employees. The [controller]
part, of course, refers to Employees, the name of the controller.
The [ApiController]
attribute applies inference rules for the default data sources of action parameters. Specifically, data binding will be done automatically by your API – when you use [FromBody]
, [FromRoute]
, [FromForm]
, [FromHeader]
, [FromQuery]
and [FromServices]
attributes inside the parameters. You can read more about source binding in this Microsoft doc.
Mapping of actions (methods) to route templates
Check out the table below, in which I map controller methods with route templates and the resulting URL (third column).
Method | Route Template | Example URI |
GetEmployees | “api/v2/Employees” | http://localhost:5000/api/v2/Employees |
GetEmployee | “api/v2/Employees{id}” | http://localhost:5000/api/v2/Employees/2 |
PostEmployee | “api/v2/Employees” | http://localhost:5000/api/v2/Employees |
DeleteEmployee | “api/v2/Employees{id}” | http://localhost:5000/api/v2/Employees/2 |
PutEmployee | “api/v2/Employees{id}” | http://localhost:5000/api/v2/Employees/2 |
Next, we talk about how to set up our in-memory database and register the services that our API needs!
Configure In-MemoryDatabase and EmployeeRepository services
Earlier, I talked about Dependency Injection and how we can inject IEmployeeRepository
in the constructor of EmployeesController
(shown below) to get an instance of the EmployeeRepository
service.
public EmployeesController(IEmployeeRepository employeeRepository)
{
_employeeRepository = employeeRepository;
}
But where do we create an instance of EmployeeRepository? The answer is in the main program, which is in Program.cs, which is where we configure two services – InMemoryDatabase
and EmployeeRepository
. We will use AddDbContext
method to instantiate our EmployeeDBContext
class, and AddScoped
method to instantiate our EmployeeRepository
class. This is what Program.cs look like after I added these two services:
using InMemoryDbAspNet6WebAPI;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddDbContext<EmployeeDBContext>(options => options.UseInMemoryDatabase("Employees"));
builder.Services.AddScoped<IEmployeeRepository, EmployeeRepository>();
builder.Services.AddCors();
builder.Services.AddControllers().AddNewtonsoftJson();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (builder.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthorization();
app.UseCors(
//options => options.WithOrigins("https://localhost:7067/").AllowAnyMethod() // Specific origin if this is what you want.
builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.WithExposedHeaders("*")
);
app.MapControllers();
app.Run();
Line 12 shows you how I configured my EmployeeDBContext
to use InMemoryDatabase
. Line 13 is where I create an instance of EmployeeRepository
. Basically, what we are doing is instantiating these two services and adding them in the collection of services that our application keeps track of, so that they can be used readily in our controller via dependency injection. After adding these services in our main program, it now makes sense why we can inject IEmployeeRepository
in the constructor of the EmployeesController
like so:
public EmployeesController(IEmployeeRepository employeeRepository)
{
_employeeRepository = employeeRepository;
}
And we’re done coding!
Test it with a REST client. Like I mentioned in the beginning of this tutorial, I am using a Chrome plugin called Boomerang SOAP & REST Client. You can use any client for this testing but I do recommend Boomerang as it is easy to install in the browser and it is easy to use.
Check out my future tutorial on how to create your own REST client just like Boomerang!
Conclusion
This tutorial demonstrates how to create a REST API that implements CRUD – creates, read, updates, and deletes employees in an in-memory database. I also added a bonus implementation of the PATCH
method! This tutorial also shows how to use Repository and Dependency Injection design patterns to de-couple the database implementation from the API implementation.
This tutorial was developed and compiled in Visual Studio Community 2022 using .NET 6 framework, ASP.NET Core Web API, and EF Core.