Full-stack application development with AngularJS 11 and Asp.Net MVC Core 5.0 — Generic Repositories and Contollers[part4/4]
Make sure you have read and completed the previous part of this series before you begin.
In the previous part, I created the web service and added support for handling HTTP GET requests. In this part, I will complete the implementation of generic repositories and controllers.
Creating a Repository Pattern
Updating Models in ServerApp by replacing ProductId, SupplierId, and RatingId with “Id”.
Replacing all instances of ProductId with Id as shown below:
Adding migrations to making changes in the database as well:
dotnet ef migrations add ReplacingId
dotnet ef database update
Verifying the database and changes have been made.
Creating a folder named Data in FullStackApp/ServerApp.
Create another folder named EFCore in the newly created Data folder
Create an interface named IEntity.cs to ServerApp/Data folder
namespace ServerApp.Data
{
public interface IEntity
{
long Id { get; set; }
}
}
All model class should now implement IEntity.cs interface as shown below:
Create another generic interface named IRepository.cs to ServerApp/Data folder:
using System.Collections.Generic;
using System.Threading.Tasks;namespace ServerApp.Data
{
public interface IRepository<T> where T : class, IEntity
{
Task<List<T>> GetAll();
Task<T> Get(long id);
Task<T> Add(T entity);
Task<T> Update(T entity);
Task<T> Delete(long id);
}
}
Move DataContext.cs from ServerApp/Models folder to ServerApp/Data/EFCore folder
Create an abstract generic class named Repository.cs that will implement IRepository.cs interface
using ServerApp.Models;namespace ServerApp.Data.EFCore
{
public abstract class Repository<TEntity, TContext> :
IRepository<TEntity>
where TEntity : class, IEntity
where TContext : DataContext
{}
}
We need to implement the interface methods
Now implement the interface:
The final view of the class will be as follows:
Now completing the method by providing the business logic code.
using Microsoft.EntityFrameworkCore;
using ServerApp.Models;
using System.Collections.Generic;
using System.Threading.Tasks;namespace ServerApp.Data.EFCore
{
public abstract class Repository<TEntity, TContext> :
IRepository<TEntity>
where TEntity : class, IEntity
where TContext : DataContext{
private readonly TContext context;public Repository(TContext context)
{
this.context = context;
}public async Task<TEntity> Add(TEntity entity)
{
context.Set<TEntity>().Add(entity);
await context.SaveChangesAsync();
return entity;
}public async Task<TEntity> Delete(long id)
{
var entity = await context.Set<TEntity>().FindAsync(id);
if (entity == null)
{
return entity;
}
context.Set<TEntity>().Remove(entity);
await context.SaveChangesAsync();
return entity;
}public async Task<TEntity> Get(long id)
{
return await context.Set<TEntity>().FindAsync(id);
}public async Task<List<TEntity>> GetAll()
{
return await context.Set<TEntity>().ToListAsync();
}public async Task<TEntity> Update(TEntity entity)
{
context.Entry(entity).State = EntityState.Modified;
await context.SaveChangesAsync();
return entity;
}
}
}
Create repository classes for Product, Supplier, and Rating to ServerApp/Data/EFCore folder
Content of ProductRepository.cs class
using ServerApp.Models;namespace ServerApp.Data.EFCore
{
public class ProductRepository : Repository<Product, DataContext>
{
DataContext _context;
public ProductRepository(DataContext context) : base(context)
{
_context = context;
}
}
}
Content of SupplierRepository.cs class
using ServerApp.Models;namespace ServerApp.Data.EFCore
{
public class SupplierRepository : Repository<Supplier, DataContext>
{
DataContext _context;
public SupplierRepository(DataContext context) : base(context)
{
_context = context;
}
}
}
Content of RatingRepository.cs class
using ServerApp.Models;namespace ServerApp.Data.EFCore
{
public class RatingRepository : Repository<Rating, DataContext>
{
DataContext _context;
public RatingRepository(DataContext context) : base(context)
{
_context = context;
}
}
}
Now register the service of three repositories in startup.cs class as shown below:
services.AddScoped<ProductRepository>();
services.AddScoped<SupplierRepository>();
services.AddScoped<RatingRepository>();
Creating Generic Controller Classes
Create a class named AppController.cs to ServerApp/Controllers folder
Content of AppController.cs class
using Microsoft.AspNetCore.Mvc;
using ServerApp.Data;
using System.Collections.Generic;
using System.Threading.Tasks;namespace ServerApp.Controllers
{[Route("api/[controller]")]
[ApiController]
public abstract class AppController<TEntity, TRepository> : ControllerBase
where TEntity : class, IEntity
where TRepository : IRepository<TEntity>
{private readonly TRepository repository;
public AppController(TRepository repository)
{
this.repository = repository;
}// GET: api/[controller][HttpGet]
public async Task<ActionResult<IEnumerable<TEntity>>> Get()
{
return await repository.GetAll();
}// GET: api/[controller]/5
[HttpGet("{id}")]
public async Task<ActionResult<TEntity>> Get(long id)
{
var entity = await repository.Get(id);
if (entity == null)
{
return NotFound();
}
return entity;
}// PUT: api/[controller]/5
[HttpPut("{id}")]
public async Task<IActionResult> Put(long id, TEntity entity)
{
if (id != entity.Id)
{
return BadRequest();
}
await repository.Update(entity);
return NoContent();
}// POST: api/[controller]
[HttpPost]
public async Task<ActionResult<TEntity>> Post(TEntity entity)
{
await repository.Add(entity);
return CreatedAtAction("Get", new { id = entity.Id }, entity);
}// DELETE: api/[controller]/5
[HttpDelete("{id}")]
public async Task<ActionResult<TEntity>> Delete(long id)
{
var entity = await repository.Delete(id);
if (entity == null)
{
return NotFound();
}
return entity;
}
}
}
Replacing the content of ProductValueController.cs with the code written below: (Remove the previous code; we will add those code later in Repository file)
using Microsoft.AspNetCore.Mvc;
using ServerApp.Data.EFCore;
using ServerApp.Models;namespace ServerApp.Controllers
{
[Route("api/products")]
[ApiController]
public class ProductValuesController : AppController<Product, ProductRepository>
{
ProductRepository _repository;public ProductValuesController(ProductRepository repository) : base(repository)
{
_repository = repository;
}
}
}
Replacing the content of SupplierValueController.cs with the code written below:
using Microsoft.AspNetCore.Mvc;
using ServerApp.Data.EFCore;
using ServerApp.Models;namespace ServerApp.Controllers
{[Route("api/suppliers")]
[ApiController]
public class SupplierValuesController : AppController<Supplier, SupplierRepository>
{
SupplierRepository _repository;
public SupplierValuesController(SupplierRepository repository) : base(repository)
{
_repository = repository;
}
}
}
Now go to the browser to request the following URLs:
https://localhost:5001/api/products
https://localhost:5001/api/suppliers
This shows that our code is working fine with a generic repository pattern and controller implemented.
Applying filtering in the Web Service
Add the following method in ServerApp/Data/ProductRepository.cs
public async Task<List<Product>> GetWithRelated(string category, string search, bool related = false)
{
IQueryable<Product> query = _context.Products;if (!string.IsNullOrWhiteSpace(category))
{
string catLower = category.ToLower();
query = query.Where(p => p.Category.ToLower().Contains(catLower));
}if (!string.IsNullOrWhiteSpace(search))
{
string searchLower = search.ToLower();
query = query.Where(p => p.Name.ToLower().Contains(searchLower)
|| p.Description.ToLower().Contains(searchLower));
}if (related)
{
query = query.Include(p => p.Supplier).Include(p => p.Ratings);
List<Product> data = await query.ToListAsync();
data.ForEach(p =>
{
if (p.Supplier != null)
{
p.Supplier.Products = null;
}if (p.Ratings != null)
{
p.Ratings.ForEach(r => r.Product = null);
}
});return data;
}else
{
return await query.ToListAsync();
}
}
Now update the ProductValuesController.cs and add the following action method:
[HttpGet("rel")]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts(string category, string search, bool related = false)
{
return await _repository.GetWithRelated(category, search, related);
}
Now update the URL in ClientApp/src/app/models/repository.ts
Now open a browser and navigate to the following URLs to see different results.
https://localhost:5001/api/products/rel?category=sports
https://localhost:5001/api/products/rel?category=sports&related=true
https://localhost:5001/api/products/rel?related=true
https://localhost:5001/api/products/
The result is filtered with Sports Category
The result is filtered with Sports Category but it is also loading navigation property value by eager loading.
All products are showing with navigation properties also loaded.
All products are displaying with no navigation properties loaded.
The full code of this article can be downloaded from the Github repository.