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
Create the folder structure
mkdir Dto Dto/Requests Dto/Responses Mappers Repositories Models Data
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;
}
}
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
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
}
);
}
}
}
In appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=players.db"
}
}
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
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
Update the database
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.
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
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
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
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
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
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
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
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
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.
Top comments (0)