Manual Mapping .NET Web Api
Tags: dotnet, api, webdev, beginners
Published: 2024-01-22 05:17:14


##### 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
```bash
dotnet new webapi --use-controllers
```

##### Create the folder structure
```bash
mkdir Dto Dto/Requests Dto/Responses Mappers Repositories Models Data
```

##### Create a player model
<!-- ```bash
dotnet new classlib -n Player
``` -->

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

##### Install DbContext
The following packages are required to use Entity Framework Core with SQLite:
```bash
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
```


##### Create a PlayerContext class in Data folder
This class will be used to create the database and seed the data.
```csharp

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

##### In appsettings.json
```json
{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=players.db"
  }
}
```


##### In Program.cs
```csharp
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

```

##### 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.
```bash
dotnet ef migrations add InitialCreate
```

##### Update the database
```bash
dotnet ef database update
```

##### 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. 
```csharp
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; }
    }
}
```

##### Create a Data Transfer Object (DTO) in Dto/Responses folder
```csharp
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; }
    }
}
```

##### Create a ApiResponses class in Dto/Responses folder
```csharp
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;
        }
    }
}
```

##### Create a Mappers class in Mappers folder
```csharp
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
            };
        }
    }
}
```

##### Create a Repository class in Repositories folder
IPlayerRepository.cs
```csharp
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);
    }
}
```

PlayerRepository.cs
```csharp
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();
        }
    }
}
```

##### Create a Controller class in Controllers folder
```csharp
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);
        }
    }
}
```

##### Inject the repository in Program.cs
```csharp
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");

```




#### Run the application
```bash
dotnet run
```

#### 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.