DEV Community

Cover image for Manual Mapping .NET Web Api
Abayomi Ogunnusi
Abayomi Ogunnusi

Posted on

Manual Mapping .NET Web Api

Manual Mapping .NET Web Api

The purpose of this project is to show how to manually map a .NET Web Api. You can use a library like AutoMapper to do the mapping for you, but I wanted to show how to do it manually so you can understand what is happening behind the scenes.

Technologies used:

  • .NET 8
  • Entity Framework Core
  • SQLite
  • AutoMapper
Create a new project
dotnet new webapi --use-controllers
Enter fullscreen mode Exit fullscreen mode
Create the folder structure
mkdir Dto Dto/Requests Dto/Responses Mappers Repositories Models Data
Enter fullscreen mode Exit fullscreen mode
Create a player model
namespace Player
{
    public class Player
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public int Level { get; set; }
        public DateTime Created { get; set; } = DateTime.Now;
    }
}
Enter fullscreen mode Exit fullscreen mode
Install DbContext

The following packages are required to use Entity Framework Core with SQLite:

dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
Enter fullscreen mode Exit fullscreen mode
Create a PlayerContext class in Data folder

This class will be used to create the database and seed the data.


namespace ApiMapper.Data
{
    public class PlayerContext : DbContext
    {
        private readonly IConfiguration _config;
        public PlayerContext(IConfiguration config)
        {
            _config = config;
        }
        public DbSet<Player> Players { get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite(_config.GetConnectionString("DefaultConnection"));
        }

        // Seed the database
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Player>().HasData(
                new Player
                {
                    Id = 1,
                    Name = "Player 1",
                    Level = 1,
                    Created = DateTime.Now
                },
                new Player
                {
                    Id = 2,
                    Name = "Player 2",
                    Level = 2,
                    Created = DateTime.Now
                },
                new Player
                {
                    Id = 3,
                    Name = "Player 3",
                    Level = 3,
                    Created = DateTime.Now
                },
                new Player
                {
                    Id = 4,
                    Name = "Player 4",
                    Level = 4,
                    Created = DateTime.Now
                },
                new Player
                {
                    Id = 5,
                    Name = "Player 5",
                    Level = 5,
                    Created = DateTime.Now
                }
            );
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
In appsettings.json
{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=players.db"
  }
}
Enter fullscreen mode Exit fullscreen mode
In Program.cs
var builder = WebApplication.CreateBuilder(args);


builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<PlayerContext>();
// builder.Services.AddScoped<IPlayerRepository, PlayerRepository>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run("https://localhost:5001"); // change the default port to 5001

Enter fullscreen mode Exit fullscreen mode
Create a migration

Migration is a way to keep track of changes to the database schema over time. It is a way to create a database schema from code.

dotnet ef migrations add InitialCreate
Enter fullscreen mode Exit fullscreen mode
Update the database
dotnet ef database update
Enter fullscreen mode Exit fullscreen mode
Create a Data Transfer Object (DTO) in Dto/Requests folder

Data Transfer Object (DTO) is an object that carries data between processes. DTOs are used to encapsulate data and send it from one subsystem of an application to another.

namespace Player.Dto.Requests
{
    public class CreatePlayerRequest
    {
        [Required(AllowEmptyStrings = false, ErrorMessage = "Name is required")]
        public string Name { get; set; } = string.Empty;
        [Required]
        public int Level { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode
Create a Data Transfer Object (DTO) in Dto/Responses folder
namespace Player.Dto.Responses
{
    public class PlayerResponse
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Level { get; set; }
        public DateTime Created { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode
Create a ApiResponses class in Dto/Responses folder
using System.Collections.Generic;

namespace Player.Dto.Responses
{
    public class ApiResponse
    {
        public bool Success { get; set; }
        public string Message { get; set; } = null!;
        public object? Data { get; set; }

        public ApiResponse(bool success, string message, object? data = null)
        {
            Success = success;
            Message = message;
            Data = data;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
Create a Mappers class in Mappers folder
using Player.Dto.Requests;
using Player.Dto.Responses;

namespace Player.Mappers
{
    public static class Mappers
    {
        // We want to map a Player entity to a PlayerResponse
        public static PlayerResponse ToResponse(this Player player)
        {
            return new PlayerResponse
            {
                Id = player.Id,
                Name = player.Name,
                Level = player.Level,
                Created = player.Created
            };
        }

        // We want to map a CreatePlayerRequest to a Player entity
        public static Player ToEntity(this CreatePlayerRequest request)
        {
            return new Player
            {
                Name = request.Name,
                Level = request.Level
            };
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
Create a Repository class in Repositories folder

IPlayerRepository.cs

using System.Collections.Generic;
using System.Threading.Tasks;
using Player.Dto.Requests;
using Player.Dto.Responses;

namespace Player.Repositories
{
    public interface IPlayerRepository
    {
        Task<IEnumerable<PlayerResponse>> GetPlayersAsync();
        Task<PlayerResponse> GetPlayerAsync(int id);
        Task<PlayerResponse> CreatePlayerAsync(CreatePlayerRequest request);
        Task<PlayerResponse> UpdatePlayerAsync(int id, CreatePlayerRequest request);
        Task<PlayerResponse> DeletePlayerAsync(int id);
    }
}
Enter fullscreen mode Exit fullscreen mode

PlayerRepository.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Player.Dto.Requests;
using Player.Dto.Responses;
using Player.Mappers;

namespace Player.Repositories
{
    public class PlayerRepository : IPlayerRepository
    {
        private readonly PlayerContext _context;

        public PlayerRepository(PlayerContext context)
        {
            _context = context;
        }

        public async Task<IEnumerable<PlayerResponse>> GetPlayersAsync()
        {
            var players = await _context.Players.ToListAsync();
            return players.Select(p => p.ToResponse());
        }

        public async Task<PlayerResponse> GetPlayerAsync(int id)
        {
            var player = await _context.Players.FindAsync(id);
            return player.ToResponse();
        }

        public async Task<PlayerResponse> CreatePlayerAsync(CreatePlayerRequest request)
        {
            var player = request.ToEntity();
            _context.Players.Add(player);
            await _context.SaveChangesAsync();
            return player.ToResponse();
        }

        public async Task<PlayerResponse> UpdatePlayerAsync(int id, CreatePlayerRequest request)
        {
            var player = await _context.Players.FindAsync(id);
            if (player == null)
            {
                throw new Exception("Player not found");
            }

            player.Name = request.Name;
            player.Level = request.Level;
            await _context.SaveChangesAsync();
            return player.ToResponse();
        }

        public async Task<PlayerResponse> DeletePlayerAsync(int id)
        {
            var player = await _context.Players.FindAsync(id);
            if (player == null)
            {
                throw new Exception("Player not found");
            }

            _context.Players.Remove(player);
            await _context.SaveChangesAsync();
            return player.ToResponse();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
Create a Controller class in Controllers folder
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Player.Dto.Requests;
using Player.Dto.Responses;
using Player.Repositories;

namespace Player.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class PlayerController : ControllerBase
    {
        private readonly IPlayerRepository _repository;

        public PlayerController(IPlayerRepository repository)
        {
            _repository = repository;
        }

        [HttpGet]
        public async Task<IEnumerable<PlayerResponse>> GetPlayersAsync()
        {
            return await _repository.GetPlayersAsync();
        }

        [HttpGet("{id}")]
        public async Task<PlayerResponse> GetPlayerAsync(int id)
        {
            return await _repository.GetPlayerAsync(id);
        }

        [HttpPost]
        public async Task<PlayerResponse> CreatePlayerAsync(CreatePlayerRequest request)
        {
            return await _repository.CreatePlayerAsync(request);
        }

        [HttpPut("{id}")]
        public async Task<PlayerResponse> UpdatePlayerAsync(int id, CreatePlayerRequest request)
        {
            return await _repository.UpdatePlayerAsync(id, request);
        }

        [HttpDelete("{id}")]
        public async Task<PlayerResponse> DeletePlayerAsync(int id)
        {
            return await _repository.DeletePlayerAsync(id);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
Inject the repository in Program.cs
using ApiMapper.Data;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<PlayerContext>();
builder.Services.AddScoped<IPlayerRepository, PlayerRepository>(); // Add this line

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run("https://localhost:5001");

Enter fullscreen mode Exit fullscreen mode

Run the application

dotnet run
Enter fullscreen mode Exit fullscreen mode

Conclusion

In this project we have seen how to manually map a .NET Web Api. You can use a library like AutoMapper to do the mapping for you, but I wanted to show how to do it manually so you can understand what is happening behind the scenes.

Top comments (0)