Part #2: Using Interceptors With Entity Framework Core
Build out your endpoints and make requests using Swagger
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
.
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.