DEV Community

Andrés Díaz
Andrés Díaz

Posted on • Edited on

How to Build a WEB API ASP.NET Core 6 (Part 1)

how to build web api asp.net core 6

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.

Image description

Prerequisites

  1. In this case, we are using Visual Studio 2022, Download here

  2. 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 1 Creating WebApi

Step 2

Step 2 Creating WebApi

Step 3

Step 3 Creating WebApi

Your solution should look like this:

Solution Clean WebAPI Customers

We remove all unnecessary files WeatherForecastController.cs and WeatherForecast.cs.

In this case, we choose the first option under API called API Controller-Empty.

Selected Controller Template

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; }
}


Enter fullscreen mode Exit fullscreen mode

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; }
}


Enter fullscreen mode Exit fullscreen mode


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; }
}


Enter fullscreen mode Exit fullscreen mode


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; }
}


Enter fullscreen mode Exit fullscreen mode

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
);


Enter fullscreen mode Exit fullscreen mode

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.

Nuget Package Microsoft.EntityFrameworkCore.SqlServer

Nuget Microsoft.EntityFrameworkCore.Tools

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


Enter fullscreen mode Exit fullscreen mode

On This approach, we create our DbContext using "First Database."

Your Data Folder should look like this.

Data Folder DbContext
Go to CustomerDemoDbContext, Copy the ConnectionString and define your connecting string on appsettings.json.



"ConnectionStrings": {

"CustomerDbConext": "Server=[ServerName];Database=CustomerDemoDb;Trusted_Connection=True;"

}


Enter fullscreen mode Exit fullscreen mode

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")));


Enter fullscreen mode Exit fullscreen mode

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();
}



Enter fullscreen mode Exit fullscreen mode

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();
    }
}



Enter fullscreen mode Exit fullscreen mode

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
}


Enter fullscreen mode Exit fullscreen mode


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;
}



Enter fullscreen mode Exit fullscreen mode

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));
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

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;
}


Enter fullscreen mode Exit fullscreen mode

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);

}


Enter fullscreen mode Exit fullscreen mode

We have three lines of code :

List of customers :



var listCustomers = await _customerRepository.GetAllAsync();


Enter fullscreen mode Exit fullscreen mode

Mapping list of Customers to CustomerReadDto :



var result = _mapper.Map<List<CustomerReadDto>>(listCustomers);


Enter fullscreen mode Exit fullscreen mode

Return Response:



return Ok(result);


Enter fullscreen mode Exit fullscreen mode

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);
}


Enter fullscreen mode Exit fullscreen mode

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);
}



Enter fullscreen mode Exit fullscreen mode

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();
}


Enter fullscreen mode Exit fullscreen mode

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();
}  


Enter fullscreen mode Exit fullscreen mode

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:

Swagger Http Methods

Conclusion

We learned about these topics.

  • Dependency Injection
  • Repository Pattern
  • AutoMapper
  • Create a web API project
  • Create Generic Repository for CRUD Operations

Source code WebApiCustomers

If you enjoyed this article, please subscribe and follow this series about Building clean web API Net Core 6.

Top comments (1)

Collapse
 
ghostbasenji profile image
GhostBasenji • Edited

Wow!!! You are great man!!!