This content originally appeared on DEV Community and was authored by AWS
This is the github repo: Base
Let us start with the schema we will follow. We want to make the schema as simple as possible so:
Now, create an ASP .NET Core Web API project (type ASP.NET and you will see it). Name the project Base
We need to install the Nuget packages. Start by right clicking Dependencies
and clicking Manage Nuget Packages
Install the packages listed below one by one here
Packages to Install:
- AutoMapper
- FluentValidation
- FluentValidation.AspNetCore
- FluentValidation.DependencyInjectionExtensions
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.Design
- Microsoft.EntityFrameworkCore.Tools
- Microsoft.EntityFrameworkCore.SQLite
- Newtonsoft.Json
- Newtonsoft.Json.Bson
- Swashbuckle.AspNetCore (No need to install)
Now, we need to add some folders in our project. Right click the Base
project (Don’t click the one with the Visual Studio icon since we don’t want to add a folder on the project solution, we want to add a folder on the Web Api project)
Add the following folders
- Data
- Dtos
- Entities (Models actually)
- Mappings
- Validations
This shall now be the project structure
Create a class on the Entities
folder and name it Player.cs
namespace Base.Entities
{
public class Player
{
public int PlayerId { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public int TeamsTeamId { get; set; }
public Team? Team { get; set; }
}
}
Then another class on the Entities
folder called Team.cs
namespace Base.Entities
{
public class Team
{
public int TeamId { get; set; }
public string? TeamName { get; set; }
public int PowerLevel { get; set; }
public ICollection<Player>? Players { get; set; }
}
}
On the Dtos
folder. Add a class called PlayerDto.cs
using Newtonsoft.Json;
namespace Base.Dtos
{
public class PlayerDto
{
[JsonProperty("playerId")]
public int PlayerId { get; set; }
[JsonProperty("firstName")]
public string? FirstName { get; set; }
[JsonProperty("lastName")]
public string? LastName { get; set; }
[JsonProperty("teamName")]
public string? TeamName { get; set; }
[JsonIgnore]
public string? InternalNote { get; set; }
}
}
And another class called TeamDto.cs
using Newtonsoft.Json;
namespace Base.Dtos
{
public class TeamDto
{
[JsonProperty("teamId")]
public int TeamId { get; set; }
[JsonProperty("teamName")]
public string? TeamName { get; set; }
[JsonProperty("powerLevel")]
public int PowerLevel { get; set; }
[JsonIgnore]
public string? InternalNote { get; set; }
}
}
Next, we add a class on the Data
folder and call it ApplicationDbContext.cs
using Base.Entities;
using Microsoft.EntityFrameworkCore;
namespace Base.Data
{
public class ApplicationDbContext : DbContext
{
//Will represent the tables for Players and Teams
public DbSet<Player> Players { get; set; }
public DbSet<Team> Teams { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//This will create the interaction between the Player and Team. A one-to-many relationship
modelBuilder.Entity<Player>()
.HasOne(p => p.Team)
.WithMany(t => t.Players)
.HasForeignKey(p => p.TeamsTeamId);
base.OnModelCreating(modelBuilder);
}
}
}
Create a class on the Mappings
folder and name it MappingProfile.cs
using AutoMapper;
using Base.Dtos;
using Base.Entities;
namespace Base.Mappings
{
public class MappingProfile : Profile
{
public MappingProfile()
{
//Map the Player model/entity to PlayerDto
CreateMap<Player, PlayerDto>()
.ForMember(dest => dest.TeamName, opt => opt.MapFrom(src => src.Team.TeamName));
//Map the Team model to TeamDto
CreateMap<Team, TeamDto>();
//Map the PlayerDto to Player model
CreateMap<PlayerDto, Player>();
//Map the TeamDto to Team model
CreateMap<TeamDto, Team>();
}
}
}
Next, we add validators to add rules on our Dtos. Create a class called PlayerValidator.cs
on the Validations
folder
using Base.Dtos;
using FluentValidation;
public class PlayerValidator : AbstractValidator<PlayerDto>
{
public PlayerValidator()
{
//A rule where first name must not be empty
RuleFor(x => x.FirstName).NotEmpty();
//Rule where last name must not be empty
RuleFor(x => x.LastName).NotEmpty();
}
}
And a class called TeamValidator.cs
also on the Validations
folder
using Base.Dtos;
using FluentValidation;
public class TeamValidator : AbstractValidator<TeamDto>
{
public TeamValidator()
{
//Rule where team name must not be empty
RuleFor(x => x.TeamName).NotEmpty();
//Power leve must only be from 1 to 100
RuleFor(x => x.PowerLevel).InclusiveBetween(1, 100);
}
}
Next, add an api controller on the Controllers
folder and name it PlayersController.cs
using AutoMapper;
using Base.Data;
using Base.Dtos;
using Base.Entities;
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
[ApiController]
[Route("api/[controller]")]
public class PlayersController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly IMapper _mapper;
private readonly IValidator<PlayerDto> _validator;
public PlayersController(ApplicationDbContext context, IMapper mapper, IValidator<PlayerDto> validator)
{
//Dependency Injection for database access
_context = context;
//Automapper injection to allow entity and Dtos mapping
_mapper = mapper;
//Injection for validating player Dtos objects
_validator = validator;
}
[HttpGet]
public async Task<IActionResult> GetPlayers([FromQuery] int? playerId, [FromQuery] string? firstName, [FromQuery] string? lastName, [FromQuery] int? teamId, [FromQuery] string? teamName)
{
//Dynamic query on optional filter params
var query = _context.Players.Include(p => p.Team).AsQueryable();
if (playerId.HasValue)
query = query.Where(p => p.PlayerId == playerId.Value);
if (!string.IsNullOrEmpty(firstName))
query = query.Where(p => p.FirstName.Contains(firstName));
if (!string.IsNullOrEmpty(lastName))
query = query.Where(p => p.LastName.Contains(lastName));
if (teamId.HasValue)
query = query.Where(p => p.TeamsTeamId == teamId.Value);
if (!string.IsNullOrEmpty(teamName))
query = query.Where(p => p.Team.TeamName.Contains(teamName));
//Retrieve players and will map to Dtos
var players = await query.ToListAsync();
var playerDtos = _mapper.Map<List<PlayerDto>>(players);
return Ok(playerDtos);
}
[HttpPost]
public async Task<IActionResult> CreatePlayer([FromBody] PlayerDto playerDto)
{
//Adding the validations for PlayerDto
var validationResult = await _validator.ValidateAsync(playerDto);
if (!validationResult.IsValid)
{
//Error if fail
var errors = JsonConvert.SerializeObject(validationResult.Errors, Formatting.Indented);
return BadRequest(errors);
}
//If exist
var team = await _context.Teams.FirstOrDefaultAsync(t => t.TeamName == playerDto.TeamName);
if (team == null)
{
//Error if not
return BadRequest($"Team with name '{playerDto.TeamName}' does not exist.");
}
//Map the playerDto to Player model/entity
var player = _mapper.Map<Player>(playerDto);
player.TeamsTeamId = team.TeamId;
//Add new player to context
_context.Players.Add(player);
try
{
//Saving changes
await _context.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
//Again, if an error
return StatusCode(500, "An error occurred while saving changes: " + ex.InnerException?.Message);
}
return CreatedAtAction(nameof(GetPlayers), new { id = player.PlayerId }, playerDto);
}
}
Then create a class called TeamsController.cs
on the Controllers
folder
using AutoMapper;
using Base.Data;
using Base.Dtos;
using Base.Entities;
using FluentValidation;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
[ApiController]
[Route("api/[controller]")]
public class TeamsController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly IMapper _mapper;
private readonly IValidator<TeamDto> _validator;
public TeamsController(ApplicationDbContext context, IMapper mapper, IValidator<TeamDto> validator)
{
//Injecting ApplicationDbCOntext for database access
_context = context;
//The dependency injections for this is also same for PlayersController.cs
_mapper = mapper;
_validator = validator;
}
[HttpGet]
public async Task<IActionResult> GetTeams([FromQuery] string? teamName, [FromQuery] int? teamId)
{
//Dynamic query for optional filter params
var query = _context.Teams.AsQueryable();
if (!string.IsNullOrEmpty(teamName))
{
query = query.Where(t => t.TeamName.Contains(teamName));
}
if (teamId.HasValue)
{
query = query.Where(t => t.TeamId == teamId.Value);
}
//Retrieve the teams and map to Dtos
var teams = await query.ToListAsync();
var teamDtos = _mapper.Map<List<TeamDto>>(teams);
return Ok(teamDtos);
}
[HttpPost]
public async Task<IActionResult> CreateTeam([FromBody] TeamDto teamDto)
{
//Validating teamDto
var validationResult = await _validator.ValidateAsync(teamDto);
if (!validationResult.IsValid)
return BadRequest(validationResult.Errors);
//Map teamDto to the Team model
var team = _mapper.Map<Team>(teamDto);
//Add new team to the database
_context.Teams.Add(team);
//Save changes
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetTeams), new { id = team.TeamId }, teamDto);
}
}
Now, for the appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=Base.db"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
And lastly, for the Program.cs
using Base.Data;
using Base.Dtos;
using Base.Mappings;
using FluentValidation;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace Base
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
//Adding services
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddAutoMapper(typeof(MappingProfile));
builder.Services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.Formatting = Formatting.Indented;
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//Fluent Validations
builder.Services.AddScoped<IValidator<PlayerDto>, PlayerValidator>();
builder.Services.AddScoped<IValidator<TeamDto>, TeamValidator>();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
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();
}
}
}
For the sample, check Base.
This content originally appeared on DEV Community and was authored by AWS