ASP.NET Core  

Implementing Repository Pattern with ASP.NET Core, EF Core, and Angular

Introduction

When building full-stack applications, keeping backend logic clean and maintainable is essential. Using the Repository Pattern in ASP.NET Core with Entity Framework Core (EF Core) helps in organizing database access, while Angular handles the frontend.

In this article, we will build a simple full-stack application where:

  • Angular fetches and displays data from the backend.

  • ASP.NET Core API uses Repository Pattern to manage database operations.

  • EF Core interacts with SQL Server for data storage.

By the end, you will have a clear understanding of repository pattern in real-world applications.

Step 1: Setting Up the Backend (ASP.NET Core + EF Core)

1. Create ASP.NET Core Web API Project

dotnet new webapi -n FullStackRepoApi
cd FullStackRepoApi
dotnet run
  • Runs at https://localhost:5001 by default.

2. Install EF Core Packages

dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools

3. Create the Model

Create Models/User.cs:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

4. Configure DbContext

Create Data/AppDbContext.cs:

using Microsoft.EntityFrameworkCore;
using FullStackRepoApi.Models;

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}
    public DbSet<User> Users { get; set; }
}

Add connection string in appsettings.json:

"ConnectionStrings": {
  "DefaultConnection": "Server=localhost;Database=FullStackRepoDb;User Id=sa;Password=YourPassword;"
}

Configure DbContext in Program.cs:

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

Step 2: Implement the Repository Pattern

1. Create Repository Interface

Repositories/IUserRepository.cs:

using FullStackRepoApi.Models;
using System.Collections.Generic;
using System.Threading.Tasks;

public interface IUserRepository
{
    Task<IEnumerable<User>> GetAllUsersAsync();
    Task<User> GetUserByIdAsync(int id);
    Task AddUserAsync(User user);
    Task UpdateUserAsync(User user);
    Task DeleteUserAsync(int id);
}

2. Implement Repository

Repositories/UserRepository.cs:

using FullStackRepoApi.Models;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Threading.Tasks;

public class UserRepository : IUserRepository
{
    private readonly AppDbContext _context;
    public UserRepository(AppDbContext context)
    {
        _context = context;
    }

    public async Task<IEnumerable<User>> GetAllUsersAsync() => await _context.Users.ToListAsync();

    public async Task<User> GetUserByIdAsync(int id) => await _context.Users.FindAsync(id);

    public async Task AddUserAsync(User user)
    {
        await _context.Users.AddAsync(user);
        await _context.SaveChangesAsync();
    }

    public async Task UpdateUserAsync(User user)
    {
        _context.Users.Update(user);
        await _context.SaveChangesAsync();
    }

    public async Task DeleteUserAsync(int id)
    {
        var user = await _context.Users.FindAsync(id);
        if (user != null)
        {
            _context.Users.Remove(user);
            await _context.SaveChangesAsync();
        }
    }
}

3. Register Repository in DI Container

In Program.cs:

builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddControllers();
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAngular",
        builder => builder.WithOrigins("http://localhost:4200")
                          .AllowAnyMethod()
                          .AllowAnyHeader());
});
var app = builder.Build();
app.UseCors("AllowAngular");
app.MapControllers();
app.Run();

4. Create Users Controller

Controllers/UsersController.cs:

using Microsoft.AspNetCore.Mvc;
using FullStackRepoApi.Models;
using System.Collections.Generic;
using System.Threading.Tasks;

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly IUserRepository _userRepository;
    public UsersController(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    [HttpGet]
    public async Task<IEnumerable<User>> GetUsers() => await _userRepository.GetAllUsersAsync();

    [HttpGet("{id}")]
    public async Task<ActionResult<User>> GetUser(int id)
    {
        var user = await _userRepository.GetUserByIdAsync(id);
        if (user == null) return NotFound();
        return user;
    }

    [HttpPost]
    public async Task<ActionResult> AddUser(User user)
    {
        await _userRepository.AddUserAsync(user);
        return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
    }

    [HttpPut("{id}")]
    public async Task<ActionResult> UpdateUser(int id, User user)
    {
        if (id != user.Id) return BadRequest();
        await _userRepository.UpdateUserAsync(user);
        return NoContent();
    }

    [HttpDelete("{id}")]
    public async Task<ActionResult> DeleteUser(int id)
    {
        await _userRepository.DeleteUserAsync(id);
        return NoContent();
    }
}

Step 3: Setting Up Angular Frontend

1. Create Angular Project

ng new fullstack-repo-app
cd fullstack-repo-app
ng serve --open
  • Runs at http://localhost:4200.

2. Add HttpClientModule

In app.module.ts:

import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';

@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule,
    FormsModule
  ],
})
export class AppModule { }

3. Create User Service

ng generate service services/user

user.service.ts:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface User {
  id?: number;
  name: string;
  email: string;
}

@Injectable({ providedIn: 'root' })
export class UserService {
  private apiUrl = 'https://localhost:5001/api/users';

  constructor(private http: HttpClient) {}

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.apiUrl);
  }

  addUser(user: User): Observable<User> {
    return this.http.post<User>(this.apiUrl, user);
  }

  updateUser(user: User): Observable<void> {
    return this.http.put<void>(`${this.apiUrl}/${user.id}`, user);
  }

  deleteUser(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`);
  }
}

4. Create Component for Users

ng generate component user-list

user-list.component.ts:

import { Component, OnInit } from '@angular/core';
import { UserService, User } from '../services/user.service';

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html',
})
export class UserListComponent implements OnInit {
  users: User[] = [];
  newUser: User = { name: '', email: '' };

  constructor(private userService: UserService) {}

  ngOnInit(): void {
    this.userService.getUsers().subscribe(data => this.users = data);
  }

  addUser(): void {
    this.userService.addUser(this.newUser).subscribe(user => {
      this.users.push(user);
      this.newUser = { name: '', email: '' };
    });
  }

  deleteUser(id: number): void {
    this.userService.deleteUser(id).subscribe(() => {
      this.users = this.users.filter(u => u.id !== id);
    });
  }
}

user-list.component.html:

<h2>Users List</h2>
<ul>
  <li *ngFor="let user of users">
    {{ user.name }} - {{ user.email }}
    <button (click)="deleteUser(user.id!)">Delete</button>
  </li>
</ul>

<h3>Add New User</h3>
<input [(ngModel)]="newUser.name" placeholder="Name">
<input [(ngModel)]="newUser.email" placeholder="Email">
<button (click)="addUser()">Add</button>

Step 4: Running the Full-Stack Application

  1. Run SQL Server and ensure the database is accessible.

  2. Run ASP.NET Core API:

dotnet run
  1. Run Angular app:

ng serve --open
  1. Open http://localhost:4200 to view, add, and delete users.

  • Angular communicates with ASP.NET Core API, which uses Repository Pattern to interact with SQL Server.

Benefits of This Approach

  1. Clean Separation: Controllers, repositories, and frontend are clearly separated.

  2. Reusability: Repository methods can be reused in multiple controllers or services.

  3. Maintainability: Backend changes do not affect Angular code directly.

  4. Testability: Repositories can be mocked in unit tests.

  5. Scalability: Easily extendable for larger projects with multiple entities.

Conclusion

Implementing the Repository Pattern in an ASP.NET Core + EF Core backend with Angular frontend creates a clean and professional full-stack application.

  • ASP.NET Core handles API requests and business logic.

  • Repository Pattern organizes data access.

  • EF Core interacts with SQL Server efficiently.

  • Angular provides a responsive and interactive UI.

This architecture is ideal for building scalable, maintainable, and testable enterprise applications.

Mastering this pattern prepares you for more advanced topics like Unit of Work, Authentication, Authorization, and API versioning in full-stack projects.