Sample Web API using ASP .NET 8.0 (For Beginners)



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:

Base

Now, create an ASP .NET Core Web API project (type ASP.NET and you will see it). Name the project Base

Web Api

We need to install the Nuget packages. Start by right clicking Dependencies and clicking Manage Nuget Packages

Dependencies

Install the packages listed below one by one here

Packages

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)

Base

NewFolder

Add the following folders

  • Data
  • Dtos
  • Entities (Models actually)
  • Mappings
  • Validations

This shall now be the project structure

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