I'm always excited to take on new projects and collaborate with innovative minds.
If youâve been working with ASP.NET Core APIs, chances are youâve written dozens of controllersâeach doing pretty much the same thing: CRUD (Create, Read, Update, Delete) operations for different entities.
Letâs be honestâthis gets repetitive fast.
Wouldnât it be better if there was a smarter way to handle all entities using just one generic controller?
Well, you can! Letâs walk through this clean and efficient approach.
Instead of creating separate controllers for each entity, weâll use generics and a bit of clever abstraction to build a universal controller.
The benefits?
To make this work, we need a common ground among all our entities. A simple interface can do the trick:
public interface IEntity
{
int Id { get; set; }
}
Now, all your entities (like Product, Customer, etc.) just need to implement this interface.
Weâll also use a generic repository pattern to abstract data access:
public interface IRepository<TEntity> where TEntity : class, IEntity
{
Task<IEnumerable<TEntity>> GetAllAsync();
Task<TEntity?> GetByIdAsync(int id);
Task<TEntity> AddAsync(TEntity entity);
Task UpdateAsync(TEntity entity);
Task DeleteAsync(int id);
}
This lets you plug in any entity without worrying about duplication.
Hereâs where the magic happens.
[ApiController]
[Route("api/[controller]")]
public class GenericController<TEntity> : ControllerBase where TEntity : class, IEntity
{
private readonly IRepository<TEntity> _repository;
public GenericController(IRepository<TEntity> repository)
{
_repository = repository;
}
[HttpGet]
public async Task<IActionResult> Get() => Ok(await _repository.GetAllAsync());
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var entity = await _repository.GetByIdAsync(id);
return entity == null ? NotFound() : Ok(entity);
}
[HttpPost]
public async Task<IActionResult> Post([FromBody] TEntity entity)
{
var result = await _repository.AddAsync(entity);
return CreatedAtAction(nameof(Get), new { id = result.Id }, result);
}
[HttpPut("{id}")]
public async Task<IActionResult> Put(int id, [FromBody] TEntity entity)
{
if (id != entity.Id) return BadRequest();
await _repository.UpdateAsync(entity);
return NoContent();
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
await _repository.DeleteAsync(id);
return NoContent();
}
}
This one controller can now handle any entity you throw at it.
For every new entity, you simply:
IEntityIRepository<TEntity> implementation in the DI containerThis approach works best for standard CRUD operations.
If your API needs heavy customization, business logic, or specific authorization per endpoint, a traditional per-entity controller might still be a better fit.
Iâve prepared a complete working example of this approach, including:
Writing generic controllers in ASP.NET Core isnât just a âcool trick.â Itâs a powerful way to embrace the DRY (Donât Repeat Yourself) principle and keep your APIs clean and scalable.
If youâre tired of repetitive code, give this approach a shotâyouâll be amazed how much time and effort it can save.
đ Have you tried this pattern in your projects? Share your experiences below!
Your email address will not be published. Required fields are marked *