Repository pattern with ASP.NET Core

By Steve Smith, Scott Addie, and Luke Latham

The repository pattern is a design pattern that isolates data access behind interface abstractions. Connecting to the database and manipulating data storage objects is performed through methods provided by the interface's implementation. Consequently, there's no need for calling code to deal with database concerns, such as connections, commands, and readers.

Implementation of the repository pattern with ASP.NET Core has the following benefits:

  • Organization of the app is less complex with no direct interdependency between the business and data access layers.
  • It's easier to reuse database access code because the code is centrally managed by one or more repositories.
  • The business domain can be independently unit tested from the database layer.

View or download sample code (how to download)

The sample app uses the repository pattern to initialize and display a list of movie character names. The app uses Entity Framework Core and an ApplicationDbContext class for its data persistence, but the database infrastructure isn't apparent where the data is accessed. Data access and database objects are abstracted behind a repository.

Repository interface

A repository interface defines the properties and methods for implementation. In the sample app, the repository interface for movie character data is ICharacterRepository. ICharacterRepository defines the ListAll and Add methods required to work with Character instances in the app:

public interface ICharacterRepository
{
    IEnumerable<Character> ListAll();
    void Add(Character character);
}
public interface ICharacterRepository
{
    IEnumerable<Character> ListAll();
    void Add(Character character);
}

Character is defined as:

public class Character
{
    public Character(string name)
    {
        Name = name;
    }

    [Key]
    public Guid Id { get; private set; } = Guid.NewGuid();
    public string Name { get; private set; } = String.Empty;
}
public class Character
{
    public Character(string name)
    {
        Name = name;
    }

    private Character()
    {
    }

    [Key]
    public Guid Id { get; private set; } = Guid.NewGuid();
    public string Name { get; private set; } = String.Empty;
}

Repository concrete type

The interface is implemented by a concrete type. In the sample app, CharacterRepository manages the database context and implements the ListAll and Add methods:

public class CharacterRepository : ICharacterRepository
{
    private readonly ApplicationDbContext _dbContext;

    public CharacterRepository(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IEnumerable<Character> ListAll()
    {
        return _dbContext.Characters.AsEnumerable();
    }

    public void Add(Character character)
    {
        _dbContext.Characters.Add(character);
        _dbContext.SaveChanges();
    }
}
public class CharacterRepository : ICharacterRepository
{
    private readonly ApplicationDbContext _dbContext;

    public CharacterRepository(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IEnumerable<Character> ListAll()
    {
        return _dbContext.Characters.AsEnumerable();
    }

    public void Add(Character character)
    {
        _dbContext.Characters.Add(character);
        _dbContext.SaveChanges();
    }
}

Register the repository service

The repository and database context are registered with the service container in Startup.ConfigureServices. In the sample app, ApplicationDbContext is configured with the call to the extension method AddDbContext. ICharacterRepository is registered as a scoped service:

public void ConfigureServices(IServiceCollection services)
{
    // Add database services.
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseInMemoryDatabase("InMemoryDb")
    );

    // Add framework services.
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    // Register application services.
    services.AddScoped<ICharacterRepository, CharacterRepository>();
}
public void ConfigureServices(IServiceCollection services)
{
    // Add database services.
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseInMemoryDatabase()
    );

    // Add framework services.
    services.AddMvc();

    // Register application services.
    services.AddScoped<ICharacterRepository, CharacterRepository>();
}

Inject an instance of the repository

In a class where database access is required, an instance of the repository is requested via the constructor and assigned to a private field for use by class methods. In the sample app, ICharacterRepository is used to:

  • Populate the database if no characters exist.
  • Obtain a list of the characters for display.

Notice how the calling code only interacts with the interface's implementation, CharacterRepository. Calling code doesn't use the ApplicationDbContext directly:

public class IndexModel : PageModel
{
    private readonly ICharacterRepository _characterRepository;

    public IndexModel(ICharacterRepository characterRepository)
    {
        _characterRepository = characterRepository;
    }

    public IEnumerable<Character> Characters { get; set; }

    public void OnGet()
    {
        PopulateCharactersIfNoneExist();

        Characters = _characterRepository.ListAll();
    }

    private void PopulateCharactersIfNoneExist()
    {
        if (!_characterRepository.ListAll().Any())
        {
            _characterRepository.Add(new Character("Darth Maul"));
            _characterRepository.Add(new Character("Darth Vader"));
            _characterRepository.Add(new Character("Yoda"));
            _characterRepository.Add(new Character("Mace Windu"));
        }
    }
}
public class HomeController : Controller
{
    private readonly ICharacterRepository _characterRepository;

    public HomeController(ICharacterRepository characterRepository)
    {
        _characterRepository = characterRepository;
    }

    public IActionResult Index()
    {
        PopulateCharactersIfNoneExist();

        var characters = _characterRepository.ListAll();

        return View(characters);
    }

    private void PopulateCharactersIfNoneExist()
    {
        if (!_characterRepository.ListAll().Any())
        {
            _characterRepository.Add(new Character("Darth Maul"));
            _characterRepository.Add(new Character("Darth Vader"));
            _characterRepository.Add(new Character("Yoda"));
            _characterRepository.Add(new Character("Mace Windu"));
        }
    }
}

Generic repository interface

This topic and its sample app demonstrate the simplest implementation of the repository pattern, where one repository is created for each business object. If the app grows beyond a few objects, a generic repository interface may reduce the amount of code required to implement the repository pattern. For more information, see DevIQ: Repository Pattern: Generic Repository Interface.

Additional resources