Part #2: Using Interceptors With Entity Framework Core

Build out your endpoints and make requests using Swagger

Nathan
The Tech Collective
5 min readMay 13, 2024

--

Source: stepwise.pl

In our previous article, we set up Entity Framework Core and connected it to a SQLite database. We also created our first table using the Employee entity.

The next step is to make requests to our API and save entities into our database. To execute these requests we will use Swagger.

Setup Swagger

We will use Swagger to interact with our API within the browser. Swagger is an easy way for us to run our CRUD operations and test whether or not our Interceptors are working.

Run the below command in your terminal. Remember to be at the root of your project:

dotnet add package Swashbuckle.AspNetCore

Inside Program.cs register the following services. AddEndpointsApiExplorer helps Minimal APIs with routing and AddSwaggerGen adds the Swagger generator to the services collection.

var builder = WebApplication.CreateBuilder();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

//

var app = builder.Build();

//

Further down we then need to add:

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

//

When running our app we can now access Swagger by appending /swagger/index.html to the end of our localhost URL.

Building our endpoints

Our app will have five endpoints:

// Get all Employees returns a collection of Employees
app.MapGet("/employees")

// Get a single Employee
app.MapGet("/employees/{id}")

// Create an Employee and return a collection of updated Employees
app.MapPost("/employees")

// Update an Employee and return updated Employee
app.MapPut("/employees/{id}")

// Delete an Employee and returns a collection of updated Employees
app.MapDelete("/employees/{id}")

All our endpoints will exist in the Program.cs file and sit between these two lines of code:

var app = builder.Build();

//

app.Run();

GET "/employees"

The first endpoint will be GET "/employees". Each method will need access to the DataContext class we created in Part #1. The DataContext class is our representation of the database. We will use dependency injection to allow our method to work with DataContext.

Whenever a GET request is made to /employees, the code will hit the below endpoint:

app.MapGet("/employees", async (DataContext context) =>
{
return "Hello";
});

Notice that the method passes a DataContext class as an argument. We can now use this class to work with our database.

app.MapGet("/employees", async (DataContext context) =>
{
return Results.Ok(await context.Employees.ToListAsync());
});

We are returning a 200 OK HTTP response and all the Employee values from the database in the response body. To get the Employee values we are using our DataContext. The DataContext traverses the database, grabs the Employees table and calls a ToListAsync(). ToListAsync() creates a new List<Employee> collection of our Employee values. By default, our endpoint will convert the response body into JSON. All the endpoints will interact with the injectedDataContext in the same way.

GET "/employees/{id}" & DELETE "/employees/{id}"

To get a single Employee from the database we need to know its unique Id. If we append the Id to the request URL and set it as a parameter in our method, .NET is intelligent enough to map the values.

Now that we have the Id we can use the DataContext to search the Employees table for any entry with a matching Id. FirstOrDefault() returns the first element of a sequence that matches our query expression.

Below we search through Employees and find an entry with an Id that matches the Id passed in our request. If no match is found we return a 400 Bad Request or a 200 OK with the single Employee in the body.

app.MapGet("/employees/{id}", async (DataContext context, int id) =>
{
var employee = context.Employees.FirstOrDefault(e => e.Id == id);
if(employee is null)
return Results.BadRequest($"{id} is not a valid Employee Id.");

return Results.Ok(employee);
});

Our delete endpoint is very similar. But when we locate our matching Employee we call the Remove() method on our DataContext. The Remove() method takes the Employee object we located. To complete the operation we then need to call SaveChangesAsync().

app.MapDelete("/employees/{id}", async (DataContext context, int id) =>
{
var employeeToDelete = await context.Employees.FirstOrDefaultAsync(e => e.Id == id);
if (employeeToDelete is not null)
{
context.Employees.Remove(employeeToDelete);
await context.SaveChangesAsync();
}
return Results.Ok(await context.Employees.ToListAsync());
});

POST "/employees/"

For our POST operation, we need to make our endpoint accept an Employee in the request body. All we need to do is add an Employee parameter to the method and .NET Core will know to expect it in the body.

Why doesn’t the endpoint expect DataContext in the request body if the method includes it as a parameter? That is because we already registered it as an AppDbContext earlier on.

All we need to do now is call Add() on our DataContext and pass in the Employee from the request.

app.MapPost("/employees", async (DataContext context, Employee employee) =>
{
context.Employees.Add(employee);
await context.SaveChangesAsync();

return Results.Ok(await context.Employees.ToListAsync());
});

PUT "/employees/{id}"

We will combine all we have learned already to complete our PUT endpoint. In this endpoint, we use the passed Id parameter to locate the correct Employee in the database. Then we update the correct Employee with the Employee from the request body.

There is no obligation for every field in the Employee request body to hold a new value. We need to check whether the value in the request body is null. If the value is null, we want to keep the original value for the Employee.

Like our other endpoints, we call the appropriate method on our Employees DbSet. In this case Update(). Finally, we can save the changes with SaveChangesAsync().

app.MapPut("/employees/{id}", async (DataContext context, Employee employee, int id) =>
{
var employeeToUpdate = await context.Employees.FirstOrDefaultAsync(e => e.Id == id);

employeeToUpdate.FirstName = employee.FirstName ?? employeeToUpdate.FirstName;
employeeToUpdate.LastName = employee.LastName ?? employeeToUpdate.LastName;
employeeToUpdate.Department = employee.Department ?? employeeToUpdate.Department;
employeeToUpdate.JobTitle = employee.JobTitle ?? employeeToUpdate.JobTitle ;

context.Employees.Update(employeeToUpdate);

await context.SaveChangesAsync();

return Results.Ok(employeeToUpdate);
});

Running Swagger

Go to the terminal and enter dotnet run. Your app will launch in the browser. Append /swagger/index.html to the end of your URL. For instance, your full URL may look like this: http://localhost:5033/swagger/index.html.

Screenshot by author

You can now interact with the endpoints and add sample data to your database. To view your data in DB Browser for SQLite you can hit the ‘Browse Data’ option.

In Part #1 we created our empty project and with the help of Entity Framework wired it up to a SQLite database. Part #2 has extended our app and we can now use Swagger to interact directly with our database through our new Minimal API endpoints.

In the final article of the series, we will learn how to step into these Minimal API requests and override the SaveChangesAsync method in Entity Framework Core. By overriding this method we can introduce our Interceptors.

--

--