In .NET development, leveraging primary constructors for dependency injection (DI) alongside generic interfaces offers a streamlined approach to managing CRUD (Create, Read, Update, Delete) operations. This combination enhances code clarity, reduces boilerplate, and promotes scalable architecture in API development.
Introduction
Managing dependencies efficiently is crucial for building maintainable APIs in .NET . This article explores how to simplify CRUD operations by integrating primary constructors for DI and utilizing generic interfaces. By adopting these practices, developers can enhance code reusability, maintainability, and scalability in their .NET applications.
By using the Generic Interface We can use it for all controllers and make the crud very simple and very scalable . In other hand we can pass the instance in class level instead of creating a constructor. This is the another advance in . Net .
Base Controller
BaseController
inherits from ControllerBase
, providing a base class for other controllers to inherit common attributes and behaviors related to API controllers.
[ApiController]
[Route("api/[controller]")]
public class BaseController : ControllerBase
{
}
Using Primary Constructors for Dependency Injection
Primary constructors allow for direct dependency injection into controllers without explicit constructor definitions. This approach simplifies code structure and improves readability.
Now we can Create the Other Controllers. For sample I am here creating the
UserController. Here’s how you can implement primary constructors in a
User Controller`User Controller
UserController
inherits from BaseController
. This inheritance allows UserController
to inherit the [ApiController]
and [Route("api/[controller]")]
attributes, making these attributes available to all methods within UserController
.
public class UserController(IUserRepo _userRepo) : BaseController
{
//URL : api/User
[HttpPost]
public async Task<IActionResult> Create(User user)
{
try
{
var res = await _userRepo.CreateAsync(user);
return Ok(res);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
//URL : api/User
[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetAll()
{
IEnumerable<User> user= await _userRepo.GetByAllAsync();
return Ok(user);
}
// URL : api/User/5
[HttpGet("{id}")]
public async Task<IActionResult> Get(long id)
{
try
{
var user = await _userRepo.GetAsync(id);
if (user == null)
{
return NotFound();
}
return Ok(user);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
// URL : api/User/5
[HttpPut("{id}")]
public async Task<IActionResult> Update(long id,User user)
{
try
{
if(id!=user.Id)
{
return BadRequest("Id Mismatch");
}
var res = await _userRepo.UpdateAsync(user);
return Ok(res);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
// URL : api/User/5
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(long id)
{
try
{
await _userRepo.DeleteAsync(id);
return NoContent();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
}
UserRepo
The UserRepo
class implements both IUserRepo
for user-specific operations and IGenericRepo<User>
for generic CRUD operations.
public class UserRepo(DbContext _context):GenericRepo<User>(_context),IUserRepo
{
}
IUserRepo
The IUserRepo
interface defines specific operations that are tailored for the User
entity.
If you have any specific request you write here and implement in the User Repo.
public interface IUserRepo:IGenericRepo<User>
{
}
IGeneric
Now I am going to create an Interface generically . These are all the operations common to all the entities
public interface IGenericRepo<T> where T:class
{
Task<T> CreateAsync(T entity);
Task<T> GetAsync(long id);
Task<T> UpdateAsync(T entity);
Task DeleteAsync(long id);
Task<IEnumerable<T>> GetByAllAsync();
}
Generic
GenericRepo<T>
implements the IGenericRepo<T>
interface, which defines the generic repository operations (CreateAsync
, GetAsync
, UpdateAsync
, DeleteAsync
, GetByAllAsync
). This approach allows for generic handling of CRUD operations across different entity types.
Now we can Implement this interface dynamically for all the entities
public class GenericRepo<T>(DbContext _context) : IGenericRepo<T> where T : class
{
internal readonly DbSet<T> _dbSet = _context.Set<T>();
public async Task<T> CreateAsync(T entity)
{
var res= await _dbSet.AddAsync(entity);
await save();
return res.Entity;
}
public async Task DeleteAsync(long id)
{
var entity = await _dbSet.FindAsync(id);
Console.WriteLine(entity);
if (entity != null)
{
_dbSet.Remove(entity);
await save();
}
}
public async Task<T> GetAsync(long id)
{
return await _dbSet.FindAsync(id);
}
public async Task<IEnumerable<T>> GetByAllAsync()
{
return await _dbSet.ToListAsync();
}
public async Task<T> UpdateAsync(T entity)
{
_context.Entry(entity).State = EntityState.Modified;
await save();
return entity;
}
private async Task save()
{
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
throw new ApplicationException("Error saving changes to the database.", ex);
}
catch (Exception ex)
{
throw new ApplicationException("An error occurred while saving changes.", ex);
}
}
}
Conclusion
- Recap of the benefits of using generic interfaces and inheritance for scalable and maintainable .NET applications.
- Simplicity: Reduces boilerplate by eliminating explicit constructor definitions and repetitive CRUD implementation.
- Scalability: Promotes scalable architecture with generic repositories that can handle operations for any entity type.
- Maintainability: Enhances code maintainability by centralizing data access logic and dependency management.
Top comments (0)