Skip to content

Instantly share code, notes, and snippets.

@arruw
Last active March 24, 2021 22:05
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save arruw/d0c5f5afc600a50b7de8630aa2d11a61 to your computer and use it in GitHub Desktop.
Save arruw/d0c5f5afc600a50b7de8630aa2d11a61 to your computer and use it in GitHub Desktop.
ASP.NET Core - Generic web API controller with generic repository pattern (created on, modified on, soft delete) ... view demo https://github.com/matjazmav/generic-api
[Authorize]
[Route("api/[controller]")]
public abstract class ApiController<T> : Controller where T : class, IEntity
{
private IApplicationRepository<T> _repository;
public ApiController(IApplicationRepository<T> repository)
{
_repository = repository;
}
[HttpGet]
[ValidateModel]
public IActionResult Query()
{
return Ok(_repository.Get());
}
[HttpGet("{id}")]
[ValidateModel]
public IActionResult Find(Guid id)
{
var record = _repository.Get(id);
if (record == null)
return NotFound();
return Ok(record);
}
[HttpPost]
[ValidateModel]
public async Task<IActionResult> Create([FromBody] T record)
{
_repository.Create(record);
if (await _repository.SaveAsync() == 0)
return BadRequest();
return CreatedAtAction("Find", new { id = record.Id }, record);
}
[HttpPut("{id}")]
[ValidateModel]
public async Task<IActionResult> Update(Guid id, [FromBody] T record)
{
if (id != record.Id)
return BadRequest();
_repository.Update(record);
if (await _repository.SaveAsync() == 0)
return BadRequest();
return Ok(record);
}
[HttpDelete("{id}")]
[ValidateModel]
public async Task<IActionResult> Delete(Guid id)
{
_repository.Delete(id);
if (await _repository.SaveAsync() == 0)
return BadRequest();
return NoContent();
}
}
public class ApplicationRepository<T> : IApplicationRepository<T> where T : class, IEntity
{
private DbContext _context;
public ApplicationRepository(ApplicationDbContext context)
{
_context = context;
}
public IQueryable<T> Get()
{
return _context.Set<T>().Where(e => !e.IsDeleted);
}
public T Get(Guid id)
{
return Get().SingleOrDefault(e => e.Id == id);
}
public void Create(T record)
{
record.CreatedOn = DateTime.Now;
record.ModifiedOn = record.CreatedOn;
_context.Add(record);
}
public void Update(T record)
{
record.ModifiedOn = DateTime.Now;
_context.Set<T>().Attach(record);
_context.Entry(record).State = EntityState.Modified;
}
public void Delete(Guid id)
{
var record = Get(id);
if (record != null) {
record.ModifiedOn = DateTime.Now;
record.IsDeleted = true;
}
}
public int Save()
{
return _context.SaveChanges();
}
public Task<int> SaveAsync()
{
return _context.SaveChangesAsync();
}
#region Dispose
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if(disposing)
{
if(_context != null)
{
_context.Dispose();
_context = null;
}
}
}
#endregion
}
public interface IApplicationRepository<T> : IDisposable
{
IQueryable<T> Get();
T Get(Guid id);
void Create(T record);
void Update(T record);
void Delete(Guid id);
int Save();
Task<int> SaveAsync();
}
@kaushik74
Copy link

Hi,

Thanks for the Generic Controller code. It would be great if you provide the example how this Generic Controller (ApiController) is implemented in other Controllers?. Looking for the example.

Thanks,
Sreenivas Kaushik

@arruw
Copy link
Author

arruw commented Oct 19, 2017

Hey I'm currently working on full example and blog post about how to use single generic controller to handle common EF Core CRUD actions & how to extend them with additional specific actions for specific type of generic controller. + Swagger and other cool sweets.

Stay around, I'll post link here when I'm done.

@TonyValenti
Copy link

Hi! I'm really interested in this as well. I'm working on something that could really benefit from Generics but I don't know how to tell ASP.NET to create a controller out of a generic type. Do you have an example you could publish?

@hanslai
Copy link

hanslai commented Feb 6, 2018

Inside abstract controller's Create(), CreatedAtAction's first parameter which is the route name cannot be hard coded since all the derived class need to have a different route name. And that route name need to be constant. I am still try to figure out how to handle this myself.

@cathalnoonan
Copy link

@hanslai
I forked this Gist and made minor changes that might be of use to you.
https://gist.github.com/3976203a8f76ccf47e805cab834bf5bd

An alternative approach you could use would be to add the "virtual" modifier to the method signature; allowing you to override the current implementation where necessary.

This would allow you to keep the current implementation for classes that don't need any adjustments to @matjazmav 's implementation.

@hanslai
Copy link

hanslai commented Feb 10, 2018

@cathalnoonan thanks for your help. I actually tried passing in the route name to the base controller before also. But it does not work, because attribute name need to be constant. So, it will not even pass the compiler

I might need to override those functions. which I really do not want to do, because I have a lot of simple controllers need to override.

@arruw
Copy link
Author

arruw commented Sep 12, 2018

Wow guys :) didn't noticed that this gist is so "popular". I really must provide full code example and maybe a blog post.

@arruw
Copy link
Author

arruw commented Dec 8, 2019

Here I made a repository with working demo of the generic REST API. https://github.com/matjazmav/generic-api

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment