If y’all recall, when we started to build YouReallyNeedABudget in the aspnetcore series, we went for speed over quality, and even though our API controllers were slim (mostly because they didn’t do very much), they still have a lot that can be improved upon.

We injected our Entity Framework DbContext directly into the controllers and did all of our database IO right in the controller methods themselves.

By doing that, we have created a direct dependency on Entity Framework by the API. That’s not actually a bad thing if we KNOW we will always use EF, but in general, it is good to program to an interface to abstract away the database technology and allow for future changes, test mocking, and a more readable API.

Plus, I already have an urge to swap in Dapper instead :)

This post will walk through the refactoring of one of our controllers. We will:

  • Move the database code out of the controller and into a repository class
  • Let aspnetcore handle injecting the concrete repository class into the controller
  • Program to the repository interface

Now, about that Repository Pattern…

I know, I know… Many of you quiver when Entity Framework and Repository Pattern are mentioned in the same sentence. We’re going to do it anyway because in our case, we don’t need the advanced features that you might lose.

Let’s take a look at the current state of the controller…

using System.Linq;
using Microsoft.AspNetCore.Mvc;
using YouReallyNeedABudget.DataAccess;
using YouReallyNeedABudget.Models;
using AutoMapper;

namespace YouReallyNeedABudget.WebApi.Controllers
{
    [Route("api/[controller]")]
    public class TransactionsController : Controller
    {

        private readonly BudgetContext _dbContext;
        private readonly IMapper _mapper;

        public TransactionsController(BudgetContext dbContext, IMapper mapper)
        {
            _dbContext = dbContext;
            _mapper = mapper;
        }

        [HttpPost]
        public IActionResult Post([FromBody]DTO.Transaction transactionDTO)
        {
            var newTransaction = _mapper.Map<Transaction>(transactionDTO);

            _dbContext.Transactions.Add(newTransaction);
            _dbContext.SaveChanges();

            return Created(string.Format("/api/transaction/{0}", newTransaction.ID), _mapper.Map<DTO.Transaction>(newTransaction));
        }

        [HttpDelete("{id}")]
        public IActionResult Delete(int id)
        {
            _dbContext.Transactions.Remove(_dbContext.Transactions.Where(tx => tx.ID == id).SingleOrDefault());
            _dbContext.SaveChanges();

            return NoContent();
        }
    }
}

Note: For the purposes of this post, we aren’t going to worry about exception handling.

Refactoring

We have two endpoints, one for creating a new transaction, and one for deleting. And you’ll notice the Entity Framework code does just that, adds and removes. That maps nicely to Add(transaction) and Remove(id) repository methods, so lets first create an interface to program to instead.

using YouReallyNeedABudget.Models;

namespace YouReallyNeedABudget.DataAccess
{
    public interface ITransactionRepository
    {
        void Add(Transaction transaction);
        void Remove(int id); 
    }
}

How lovely…

And for the concrete version…

using System.Linq;
using YouReallyNeedABudget.Models;

namespace YouReallyNeedABudget.DataAccess
{
    public class TransactionRepository : ITransactionRepository
    {
        BudgetContext _dbContext;

        public TransactionRepository(BudgetContext dbContext)
        {
            _dbContext = dbContext;
        }

        public void Add(Transaction transaction)
        {
            if (string.IsNullOrEmpty(transaction.PayeeName) == false)
            {
                if (_dbContext.Payees.SingleOrDefault(p => p.Name == transaction.PayeeName) == null)
                {
                    _dbContext.Payees.Add(new Payee { Name = transaction.PayeeName });
                }
            }

            _dbContext.Transactions.Add(transaction);
            _dbContext.SaveChanges();
        }

        public void Remove(int id)
        {
            _dbContext.Transactions.Remove(_dbContext.Transactions.Where(tx => tx.ID == id).SingleOrDefault());
            _dbContext.SaveChanges();
        }

    }
}

Note: these will live in our data access layer…

Fortunately, and with great pleasure, aspnetcore will handle injecting the DbContext into the constructor just like it did when we used it in the controller.

Also, notice the add-if-not-exists payee clause in the Add method. In this application, payees are just a list of names that serve very little purpose. We could name the method something like AddTransactionAndAddPayeeIfNotExists() to be more clear, but I won’t here. We also could have created a separate payee repository and implemented that logic in the controller or in a service method, but again, payees are not important enough to justify it.

Injecting the repository interface

To use our new repository we have to tell the dependency injection framework that when it needs to fulfill a ITransactionRepository reference, to use a specific concrete implementation - the one we created above.

To do this, add this to your Startup.cs ConfigureServices() method:

services.AddScoped<ITransactionRepository, TransactionRepository>();

Pointing out that if you were to implement the interface using another ORM like Dapper, a mock repository for testing, or with no ORM at all, this is where you would specify which implementation to use.

Piece of cake!

Now we just need to make use of it in our original controller method:

using Microsoft.AspNetCore.Mvc;
using YouReallyNeedABudget.DataAccess;
using YouReallyNeedABudget.Models;
using AutoMapper;

namespace YouReallyNeedABudget.WebApi.Controllers
{
    [Route("api/[controller]")]
    public class TransactionsController : Controller
    {

        private readonly ITransactionRepository _transactionRepo;
        private readonly IMapper _mapper;

        public TransactionsController(ITransactionRepository repo, IMapper mapper)
        {
            _transactionRepo = repo;
            _mapper = mapper;
        }

        [HttpPost]
        public IActionResult Post([FromBody]DTO.Transaction transactionDTO)
        {
            var newTransaction = _mapper.Map<Transaction>(transactionDTO);

            _transactionRepo.Add(newTransaction);

            return Created(string.Format("/api/transaction/{0}", newTransaction.ID), _mapper.Map<DTO.Transaction>(newTransaction));
        }

        [HttpDelete("{id}")]
        public IActionResult Delete(int id)
        {
            _transactionRepo.Remove(id);

            return NoContent();
        }
    }
}

And that’s all she wrote.

Still lots to do, but this is a great start. Let me know if that was useful or if you have any suggestions. :)

Comment on the reddit post.