I'm always excited to take on new projects and collaborate with innovative minds.

Social Links

Simplify Your ASP.NET Core APIs with a Single Generic Controller

Simplify Your ASP.NET Core APIs with a Single Generic Controller

🚀 Why Repeat Yourself When You Can Simplify?

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.


🎯 The Idea: One Controller, Infinite Entities

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?

  • Less boilerplate code
  • Consistent API structure
  • Easier maintenance
  • Quicker onboarding for new developers

🏗️ Setting Up the Building Blocks

1. Base Entity Interface

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.


2. Generic Repository

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.


3. Generic Controller

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.


đź§© Plugging It All Together

For every new entity, you simply:

  1. Create the entity class implementing IEntity
  2. Register the corresponding IRepository<TEntity> implementation in the DI container
  3. Map the generic controller via minimal APIs or reflection-based routing (or by explicitly specifying endpoints if needed)

âś… Why This Approach Works Well

  • Reduces code clutter—no more dozens of near-identical controllers.
  • Easier to test—one generic controller covers all cases.
  • Great for internal APIs or admin panels where customization isn’t essential.

⚠️ When to Be Cautious

This 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.


đź’ľ Get The Full Working Example on GitHub

I’ve prepared a complete working example of this approach, including:

  • Fully working ASP.NET Core API

  • In-memory data storage

  • Swagger documentation

👉 View the GitHub Repo here


đź’ˇ Final Thoughts

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!

3 min read
Jul 03, 2025
By Dheer Gupta
Share

Leave a comment

Your email address will not be published. Required fields are marked *