In this module we are going to implement the repository pattern in the Vehicles controller. This will help us solve the problem in unit testing the controller we ran into in the last module.
We are going to create a dependency on the Vehicles Repository in the Vehicles controller so that we can isolate that component during testing of the controller HTTP GET method.
Create the Vehicle Repository
As noted in the last module, one of the keys to isolating a component in C# is through interfaces. We are going to use an interface to define what the repository should look like. What properties it should have, what functions it should perform, and so on. You can think of this as an API to the repository component we are about to build.
Create the Vehicle Repository Interface
Create a new folder on the root of the FredsCarsAPI project and in the new folder create a file called IVehicleRepository. Fill the new file with the following contents.
FredsCarsAPI/Repositories/IVehicleRepository.cs
using FredsCarsAPI.Models;
namespace FredsCarsAPI.Repositories
{
public interface IVehicleRepository
{
IQueryable<Vehicle> Vehicles { get; }
}
}
In the code above we have defined an interface called IVehicleRespository. This interface defines a read only property called Vehicles that returns an IQueryable<Vehicle>
. This is saying that any class that implements me must have this property and implement it’s get
accessor function.
One of the advantages to using an interface, is that the client code doesn’t need to know the implementation details of the component. Further more, we can replace the concrete implementations of the interface anytime we need a different behavior. This is going to come in handy when we get to the controller unit test as we will soon see.
Create the Vehicle Repository Class
In the Repositories folder, create a new class file called VehicleRepository.cs and fill it with the contents below.
FredsCarsAPI/Repositories/VehicleRepository.cs
using FredsCarsAPI.Data;
using FredsCarsAPI.Models;
namespace FredsCarsAPI.Repositories
{
public class VehicleRepository : IVehicleRepository
{
private ApplicationDbContext _context;
public VehicleRepository(ApplicationDbContext context)
{
_context = context;
}
public IQueryable<Vehicle> Vehicles => _context.Vehicles;
}
}
In the code above we have created a new class called VehicleRepository that implements the IVehicleRepository interface. This class will be the concrete implimentation of the interface in the application during runtime. The colon after the class name can be read as implements in this case. (This operator is also used to inherit from another class but here it is being used to implement an interface.)
public class VehicleRepository : IVehicleRepository
Next we create a private variable called _context of type ApplicationDbContext. We receive the ApplicationDbContext in the constructor through DI and set the private _context variable to the incoming context. We registered our DbContext with DI back in module 22.
At this point we have all of the plumbing we need in the Vehicle Repo class. Now we can actually implement the Repo interface with the next single simple line.
public IQueryable<Vehicle> Vehicles => _context.Vehicles;
This line declares the mandatory interface variable Vehicles of type IQueryable<Vehicle>
and returns the Vehicles DbSet of the DbContext using the “goes into” operator (‘=>’).
Register the Vehicle Repository with DI
The next thing we need to do is register the repository with dependency injection. Open the program.cs file and make the modification shown below in bold blue font.
FredsCarsAPI/Program.cs
/*** existing code ***/
// Add services to the container.
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
builder.Configuration
.GetConnectionString("ApplicationContext")));
builder.Services.AddScoped<IVehicleRepository, VehicleRepository>();
/*** existing code ***/
The code above adds the Vehicle Repository to the DI services container using the AddScoped lifetime for the service. This means the lifetime of the repo will only be for one Web API request. And a new instance will be created for each request.
Now anytime a component asks for an instance of an IVehicleRepository service in its constructor parameter list, ASP.Net Core DI will create an instance of that service and feed it to the constructor.
Use the IVehicle Repository in the Vehicle Controller
Open the VehiclesController.cs file and make the modifications below in bold blue font.
FredsCarsAPI/Controllers/VehiclesController.cs
using FredsCarsAPI.Data;
using FredsCarsAPI.Models;
using FredsCarsAPI.Models.DTOs;
using FredsCarsAPI.Repositories;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace FredsCarsAPI.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class VehiclesController : ControllerBase
{
private IVehicleRepository _vehicleRepo;
public VehiclesController(IVehicleRepository vehicleRepo)
{
_vehicleRepo = vehicleRepo;
}
[HttpGet]
public async Task<ApiResult<VehicleDTO>> GetVehicles(
int pageIndex = 0, int pageSize = 10)
{
// get Vehicles page
var dataQuery = _vehicleRepo.Vehicles.AsNoTracking()
.Include(v => v.VehicleType)
.ConvertVehiclesToDTOs();
return await ApiResult<VehicleDTO>.CreatAsync(
dataQuery,
pageIndex,
pageSize);
}
}
}
In the code above, we have modified our DI setup to take in the Vehicle Repo rather then the whole DbContext.
From this:
private ApplicationDbContext _context;
public VehiclesController(ApplicationDbContext context)
{
_context = context;
}
to this:
private IVehicleRepository _vehicleRepo;
public VehiclesController(IVehicleRepository vehicleRepo)
{
_vehicleRepo = vehicleRepo;
}
The fact that vehicleRepo
is an interface means that it hides its implementation details from the Vehicle controller. And the Vehicle controller doesn’t care what those details are or where they come from. We have already registered the concrete implementation with DI for runtime in program.cs. And we will be able to use a different implementation for unit testing as we’ll see in the next module.
Within the actual HTTP GET method, we just made one very little change.
// get Vehicles page
var dataQuery = _vehicleRepo.Vehicles.AsNoTracking()
.Include(v => v.VehicleType)
.ConvertVehiclesToDTOs();
We are now using the repo to offload the duties of hitting the DbContext from the controller.
If you run the program at this point the behavior should be the same. And if you run some API tests from Swagger or Postman, the JSON result should also be the same as in previous modules.
What’s Next
Now that we’ve completed our little pitstop, learned about the repository pattern, and refactored our controller to use it, we can move along and in the next module finish our unit testing for the VehiclesController.