Introduction
This tutorial is Part of the Step by Step Series: How to build a Clean Web API from the basics of ASP.NET Core.
We are starting building a Customer Web API with Net Core 6
using the native dependency injection provided on this framework and creating a repository pattern taking advantage of the entity framework and one of the most used nuget packages, automapper following the Solid Principles.
You may need to research this on the web if you don't know the SOLID principles.
Why should I use this?
Dependency Injection
In short, dependency injection is a design pattern in which an object receives other objects. A form of inversion of control, dependency injection aims to separate the concerns of constructing objects and using them, leading to loosely coupled programs.
Some of the problems that you resolve are these:
- Use of an interface or base class to abstract the dependency implementation.
- Register the dependency in a service container. ASP.NET Core provides a built-in service container.
- Inject the service into the class's constructor where it needs. The framework takes on the responsibility of creating an instance of the dependency and disposing of it when it's no longer needed.
See more details in Dependency Injection
Repository pattern
According to Martin Flower A repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to the repository for satisfaction.
See more details in repository pattern.
AutoMappter
Automapper is a simple library that helps us to transform one object type into another. It is a convention-based object-to-object mapper that requires minimal configuration.
What problems will resolve automapper?
One of the common uses is when we have to map one object type to another. It is boring to do it every time we have to do it, so this simple tool makes it for us.
See more details in automapper
HTTP methods
We are defining these HTTP methods in our Customer API project following the standard conventions to create restful services.
Prerequisites
In this case, we are using Visual Studio 2022, Download here
SQL Express Edition or Developer Edition, Download here
Create a web API project
We start Creating a new project
Step 1
From the File menu, select New > Project.
Enter Web API in the search box.
Select the ASP.NET Core Web API template and select Next.
In the Configure your new project dialog, name the project CustomerAPI and select Next.
In the Additional information dialog:
Confirm the Framework is .NET 6.0 (Long-term support).
Confirm the checkbox for Use controllers(uncheck to use minimal APIs) is checked.
Select Create.
Step 2
Step 3
Your solution should look like this:
We remove all unnecessary files WeatherForecastController.cs and WeatherForecast.cs.
In this case, we choose the first option under API called API Controller-Empty.
And then, we create the controller class, CustomersController, inheriting from ControllerBase, and before continuing to define the endpoint, start defining the necessary DTO.
If you are unfamiliar with the Dto class, we recommend reading Create Data Transfer Objects (DTOs) | Microsoft Docs..
But to summarize, what I have to do is create a DTO because we don't want to reveal all our columns specified by internal objects related to our database. We define in our DTO only the properties that I want to expose on the endpoints.
At this point, we are to define one property to reuse similar properties. For this purpose, we only have one property, but in real scenarios, we may need to specify more than one.
namespace WebApiCustomers.Dtos;
public class BaseDto
{
public int Id { get; set; }
}
The following Dto Classes are CustomerReadDto, CustomerCreateDto, and CustomerUpdateDto; we must create Dto according to requirements.
We have these cases:
CustomerReadDto, used to read Customer Info and return from the Get Methods.
CustomerCreateDto, used in POST Method, for creating customer records.
CustomerUpdateDto, used in Put Method, for updating customer records.
namespace WebApiCustomers.Dtos;
public class CustomerReadDto
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public string? EmailAddress { get; set; }
}
namespace WebApiCustomers.Dtos;
public class CustomerCreateDto
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public string? EmailAddress { get; set; }
}
namespace WebApiCustomers.Dtos;
public class CustomerUpdateDto: BaseDto
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public string? EmailAddress { get; set; }
}
Scaffold Models Class from Existing Database
Because in real scenarios, you will likely work with an existing database. In this case, we are creating a little database "CustomerDemoDb" on SQL Server Object Explorer Tab From Visual Studio.
Step One: Create a CustomerDemoDb database by right-clicking > New Database > CustomerDemoDb and then using New Query and copy-pasting this syntax.
Use CustomerDemoDb
CREATE TABLE [dbo].[Customer]
(
[Id] INT NOT NULL PRIMARY KEY IDENTITY (1, 1),
[FirstName] NVARCHAR (150) NULL,
[LastName] NVARCHAR (150) NULL,
[EmailAddress] NVARCHAR (150) NULL,
[DateOfBirth] DATE NULL,
[CreatedAt] DATE NULL
);
Great! We created the database, and now we have to build our DatabaseDbContext for adding and getting records from this database.
For creating the Database Context, we have to install some Packages:
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
To Accomplish This, Go to > Nuget Package Manager > Browse Type each of them.
The Last One is necessary to execute the command "Scaffold-DbContext" we are using in the next step.
Create Data Folder in your project, and on the same Screen, Go to Package Manage Console which shows it up at the Bottom, and Type this command :
Remember to replace [ServerName] with your Current ServerName.
Scaffold-DbContext "Server=[ServerName];Database=CustomerDemoDb;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -outPutDir Data
On This approach, we create our DbContext using "First Database."
Your Data Folder should look like this.
Go to CustomerDemoDbContext, Copy the ConnectionString and define your connecting string on appsettings.json.
"ConnectionStrings": {
"CustomerDbConext": "Server=[ServerName];Database=CustomerDemoDb;Trusted_Connection=True;"
}
From CustomerDemoDbContext Class, Remove the block inside the OnConfiguring Method because on the next step with defining our connectionstring on File Program.cs on services File on NetCore 5 is Startup.cs.
Add this code after this line of code:
var builder = WebApplication.CreateBuilder(args);
Code to add :
builder.Services.AddDbContext<CustomerDemoDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("CustomerDbConext")));
Here we are using Dependency Injection for injecting DbContext Service. The Method AddDbContext internally defines the LifeTime of Service AddScope for Injecting our DbContext.
Resuming, we can register services in 3 ways :
Transient, this is created each time on every request.
Scoped, this is created once and reused on the same request.
Singleton, this is created once and is reused across the application lifetime.
If you want more detail about Dependency Injection LifeTime in ASP.NET Core, I recommend reading this article.
Dependency Injection Lifetimes in ASP.NET Core — Code Maze (code-maze.com)
Create Generic Repository for CRUD Operations with Repository Pattern and Entity Framework
On this Topic, We have to create a Repository Class Based on a Design Pattern named Repository Pattern, and one of the common uses is to define all related methods for accessing database records.
The first step is to create Repositories Folder and define IRepository Interface like this:
namespace WebApiCustomers.Repositories;
public interface IRepository<TEntity>
{
Task<IEnumerable<TEntity>> GetAllAsync();
Task<TEntity?> GetAsync(int? id);
Task AddAsync(TEntity entity);
Task UpdateAsync(TEntity entity);
Task DeleteAsync(int id);
Task SaveAsync();
}
In this case, we take advantage of Generic Class TEntity and DbSet Object from DbContext, explained in the next step.
Here we are using DbSet to use any class related to the database context in this way to create Base Repository :
using Microsoft.EntityFrameworkCore;
using WebApiCustomers.Data;
namespace WebApiCustomers.Repositories;
public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
private readonly CustomerDemoDbContext _context;
private readonly DbSet<TEntity> _dbset;
public BaseRepository(CustomerDemoDbContext context)
{
_context = context;
_dbset = _context.Set<TEntity>();
}
public async Task AddAsync(TEntity entity) => await _dbset.AddAsync(entity);
public async Task<IEnumerable<TEntity>> GetAllAsync() => await _dbset.ToListAsync();
public async Task<TEntity?> GetAsync(int? id) => await _dbset.FindAsync(id);
public async Task DeleteAsync(int id)
{
var dataToDelete = await _dbset.FindAsync(id);
_dbset.Remove(dataToDelete);
}
public async Task UpdateAsync(TEntity entity)
{
await Task.Run(()=> _dbset.Attach(entity));
_context.Entry(entity).State = EntityState.Modified;
}
public async Task SaveAsync()
{
await _context.SaveChangesAsync();
}
}
Now we have to create the ICustomerRepository because sometimes you have to add additional methods, and you can define them on this interface.
public interface ICustomerRepository: IRepository<Customer>
{
//Define your Additional Signature methods here
}
using Microsoft.EntityFrameworkCore;
using WebApiCustomers.Data;
namespace WebApiCustomers.Repositories;
public class CustomerRepository : BaseRepository<Customer>, ICustomerRepository
{
private readonly CustomerDemoDbContext _context;
public CustomerRepository(CustomerDemoDbContext context) : base(context) => _context = context;
}
This Repository Pattern complete their function with one table but in real scenarios maybe you have to work with more entities, now we need to go, to the program and inject our repository in this case CustomerRepository.
To accomplish that, we have to add this code, after the line
builder.Services.AddControllers();
For the Generic Repository
builder.Services.AddTransient(typeof(IRepository<>),typeof(BaseRepository<>));
For the Customer Repository
builder.Services.AddTransient<ICustomerRepository, CustomerRepository>();
Creating Dto and Mapping Dto With Models using AutoMapper Nuget Package.
Why Use Dtos ?, the purpose of using Dtos is to use classes to expose only the fields you need, it is not safer to expose all columns from our table than we created I, classes, with only needed columns.
Firstly install two AutoMapper Nuget Package for Map ours Dto to Entities
· AutoMapper
· AutoMapper.Extensions.Microsoft.DependencyInjection
And then with need to add this line on un program.cs file
builder.Services.AddAutoMapper(Assembly.GetExecutingAssembly());
After this, we must define the profile class, where I can define all mapping that we need it
And then we can start to Add our profile class for all necessary Dependency Injection that we need, like this.
using AutoMapper;
using WebApiCustomers.Data;
using WebApiCustomers.Dtos;
namespace WebApiCustomers.Profiles
{
public class CustomerProfile : Profile
{
public CustomerProfile()
{
CreateMap<Customer, CustomerReadDto>();
CreateMap<CustomerCreateDto, Customer>().
ForMember(m => m.CreatedAt, o => o.MapFrom(s => DateTime.Now.Date));
}
}
}
On the second line, CreateMap specifies a default value for the CreatedAt column, because we are taking de date from the system, not from the user.
Now we go back to the controller and inject two objects ICustomerRepository and IMapper, you constructor class should look like this:
private readonly ICustomerRepository _customerRepository;
private readonly IMapper _mapper;
public CustomersController(ICustomerRepository customerRepository,IMapper mapper)
{
_customerRepository = customerRepository;
_mapper = mapper;
}
Creating HTTP methods for CRUD Operations
Now are need to defined each HTTP method defined on the previous table
Get /api/customers
Here we are to define the action for a get method /api/customers and we have to return array of customers like this:
[HttpGet]
public async Task<IActionResult> GetAll()
{
var listCustomers = await _customerRepository.GetAllAsync();
var result = _mapper.Map<List<CustomerReadDto>>(listCustomers);
return Ok(result);
}
We have three lines of code :
List of customers :
var listCustomers = await _customerRepository.GetAllAsync();
Mapping list of Customers to CustomerReadDto :
var result = _mapper.Map<List<CustomerReadDto>>(listCustomers);
Return Response:
return Ok(result);
Get /api/customers/{id}
Here we are to define the action for a get method /api/customers/{id} and we have to return a CustomerReadDto object on, here we don't have to explain the following methods because are too similar :
[HttpGet]
public async Task<IActionResult> Get(int id)
{
var customerItem = await _customerRepository.GetAsync(id);
var result = _mapper.Map<CustomerReadDto>(customerItem);
return Ok(result);
}
POST /api/customers
Here we are to define the action for a POST method /api/customers like this;
[HttpPost]
public async Task<IActionResult> PostCustomer([FromBody]CustomerCreateDto customerCreateDto)
{
var customerToInsert = _mapper.Map<Customer>(customerCreateDto);
await _customerRepository.AddAsync(customerToInsert);
await _customerRepository.SaveAsync();
return CreatedAtAction("Get",
new { id = customerToInsert.Id }, customerToInsert);
}
PUT /api/customers/{id}
Here we are to define the action for a PUT method /api/customers/{id} like this;
[HttpPut("{id}")]
public async Task<IActionResult> UpdateCustomer(int id, [FromBody] CustomerUpdateDto customerUpdateDto)
{
if (id != customerUpdateDto.Id) return BadRequest();
var customerToUpdate = _mapper.Map<Customer>.(customerUpdateDto);
await _customerRepository.UpdateAsync(customerToUpdate);
await _customerRepository.SaveAsync();
return NoContent();
}
Delete /api/customers/{id}
Here we are to define the action for a Delete method /api/customers/{id} like this;
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteCustomer(int id)
{
await _customerRepository.DeleteAsync(id);
await _customerRepository.SaveAsync();
return NoContent();
}
Swagger
This tool enables development across the entire API lifecycle, from design and documentation to testing and deployment.
Swagger is integrated to the project in a easy way in our services by default is enabled in our project.
If you defined the project following all steps, you could use the swagger page to test all your HTTP methods:
Conclusion
We learned about these topics.
- Dependency Injection
- Repository Pattern
- AutoMapper
- Create a web API project
- Create Generic Repository for CRUD Operations
If you enjoyed this article, please subscribe and follow this series about Building clean web API Net Core 6.
Top comments (1)
Wow!!! You are great man!!!