Get Into to Web API with Asp.Net Core

Omer Beden
Kodluyoruz
Published in
10 min readFeb 7, 2021

This is my first blog post about the Asp.net Core platform. I included this world due to Kodluyoruz Bootcamp. First of all, I would like to thanks them for providing such good things to us.

  • Mapping
  • Extensions
  • Validation
  • Entity Framework Core In-Memory
  • Testing requests with Postman

It has been asked us to choose a scenario that involved the above topics. Thereby, I decided to simulate a chess tournament platform to perform the above topics. Before started to above topics I would like to warn you, this is not a big project. It just includes the above topics and I just adapted these topics to my story. I’m not professional in creating web API’s yet so, the code that I wrote may seem not clean.

Models

Before deep into my model's classes, I would like to talk about what the models classes are. A model class can be a couple of things. It can be an entity class that represents our database table structure. Entity class just contains of couple of properties (Id,name…). Entity classes and also model classes don’t do any business logic , they only contain the data. Model classes also be ViewModel or RequestModel or DTO(data transfer object) etc. These classes responsible for transfer the data between project layers.

Simply, I have 2 main model called Tournament and Player. Other models just help to define these models. I thought that each tournament has multiple players and each player has Title. I looked up to wikipedia to see chess titles and I created each title as a class so that I can do some works according to that title.

public class Tournament
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public DateTime Date { get; set; }
[Required]
public string Location { get; set; }

[PlayersValidation(ErrorMessage = "Players count of the tournament must be even number")]
public List<Player> Players { get; set; }
}
public class Player
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public String LastName { get; set; }
[Required]
public int Elo { get; set; }
[Required]
public string Gender { get; set; }
[TitleValidation]
public Title Title { get; set; }

}

DTO (Data Transfer Object)

A dto class responsible for transfer the data between our project layer. In this blog post I use DTOs to handle busines logic. A dto classes can be totally the same as entity classes but I think most of the time we don’t need the whole entity classe’s properties that’s why we use DTO classes in other layers. For example, a view layer doesn’t need to contain log data, etc.

public class PlayerDTO
{
public int Id { get; set; }
public string Name { get; set; }
public String LastName { get; set; }
public int Elo { get; set; }
public Title Title { get; set; }
}
public class TournamentDTO
{
public int Id { get; set; }

public string Name { get; set; }

public DateTime Date { get; set; }

public string Location { get; set; }

public List<Player> Players { get; set; }
}

Mapping

Mapping is nothing sort off a matching the two object field to each other. For instance, I have a PlayerDTO and Player object. I assume that my database context accept object as Player object. So , I need to map PlayerDTO to Player.

For this problem , I wrote a extension method to map objects. Here is:

public static class MappingExtensions
{

public static List<PlayerDTO> ToPlayersDTOs(this List<Player> players)
{
List<PlayerDTO> playerDtos = new List<PlayerDTO>();

foreach (var player in players)
{
playerDtos.Add(new PlayerDTO()
{
Id = player.Id,
Title = player.Title,
Name = player.Name,
LastName = player.LastName,
Elo = player.Elo
});
}

return playerDtos;
}

public static List<Player> ToPlayers(this List<PlayerDTO> playerDtos)
{
List<Player> players = new List<Player>();

foreach (var playerDto in playerDtos)
{
players.Add(new Player
{
Id = playerDto.Id,
Title = playerDto.Title,
Name = playerDto.Name,
LastName = playerDto.LastName,
Elo = playerDto.Elo
});
}

return players;
}

public static Tournament ToTournament(this TournamentDTO tournamentDto)
{
Tournament tournament = new Tournament
{
Players = tournamentDto.Players,
Date = tournamentDto.Date,
Id = tournamentDto.Id,
Location = tournamentDto.Location,
Name = tournamentDto.Name
};
return tournament;
}
}

When we want to add some property to specific class , we can use extension methods. Thus, we can access that propety by using that specific class. For instance, the List<PlayerDTO> object in c# defaultly doesn’t contain ToPlayers method. But we can use it like built in method.

Extension methods need to be static and the class they in also be need to static. Also, we specify which classes extension that method by writing this keyword.

Validation

In every application we need validation. Validation is just checking. Basically, there are two types of validation. Server-side validation and client-side validation. I’m not going into these topics but I know that server-side validation must be done on the other hand client-side validation is an option but good to be done.

I wanted to control this validation in one place. Thereby, I use ActionFilterAttribute in .netCore.

public class ValidationActionFilter:ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}

Thus, I can handle validation result from one place.

Maybe you realized that there are some data annotations in Player and Tournament classes at the beginning of this post. I use validation for this. Let’s assume that a player wants to join the tournament and claims its Elo is 2300 but also claims his title is GrandMaster. I need to check this because you can’t be grandMaster when your elo under the 2500.

public class TitleValidation : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Player player = (Player)validationContext.ObjectInstance;


if (player.Title.Name == TitleEnum.GrandMaster.ToString() && player.Elo < 2500)
{
return new ValidationResult(FormatErrorMessage(validationContext.MemberName));
}

else if (player.Title.Name == TitleEnum.InternationalMaster.ToString() && player.Elo < 2400 && player.Elo >= 2500)
{
return new ValidationResult(FormatErrorMessage(validationContext.MemberName));
}

else if (player.Title.Name == TitleEnum.FideMaster.ToString() && player.Elo < 2300 && player.Elo >= 2400)
{
return new ValidationResult(FormatErrorMessage(validationContext.MemberName));
}

else if (player.Title.Name == TitleEnum.CandidateMaster.ToString() && player.Elo < 2200 && player.Elo >= 2300)
{
return new ValidationResult(FormatErrorMessage(validationContext.MemberName));
}

else if (player.Title.Name == TitleEnum.Noob.ToString() && player.Elo >= 2200)
{
return new ValidationResult(FormatErrorMessage(validationContext.MemberName));
}


return ValidationResult.Success;
}
}

I also create another validation for creating tournament fixtures. I wanted to match all players each other without leaving behind a player on the first tour. Thereby I need to validate player count is an even number.

[AttributeUsage(AttributeTargets.Property,AllowMultiple = true)]
public class PlayersValidation:ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Tournament tournament = (Tournament)validationContext.ObjectInstance;
List<PlayerDTO> players = tournament.Players.ToPlayersDTOs();

if (players.Count % 2 == 0)
{
return ValidationResult.Success;
}

return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}

Business

The business layer is responsible the application rules. I mean, how to data can be manipulated to meet requirements. You can manipulate the data in the business layer.

In this layer, I decided to create a tournament fixtures and match players with each other.

public class BasicFixture:IFixture
{

public Dictionary<PlayerDTO, PlayerDTO> Fixture { get; }
public BasicFixture()
{
Fixture = new Dictionary<PlayerDTO, PlayerDTO>();
}

public void MatchPlayers(List<PlayerDTO> playersDtos)
{
if (playersDtos.Count % 2==0)
{
for (int i = 0; i < playersDtos.Count-1; i = i + 2)
{
Fixture.Add(playersDtos[i], playersDtos[i + 1]);
}
}
}
}

In the above code, basically I create a BasicFixture and match players in order. There are a couple of fixture types in some tournaments. My fixture type is only a left-side fixture. I mean this:

other complicated fixtures can be implemented of course. Like this:

There is another thing I would like to mention. I wanted to generate Title’s shortcut on my own. For instance, if a player enter his title GrandMaster, API auto-generates its title’s shortcut “GM”. I search this on internet. Finally I found entity framework core automatically generates inheritance entities called TPT(table per type). But I couldn’t run it maybe the reason is that I used entityframeworkCore In-memory. I don’t know already.

As a solution to this, I create a generator class that generates Title’s shortcut automatically. Like this:

public class TitleShorcutGenerator
{
private TournamentDTO _tournamentDTO;

public TitleShorcutGenerator(TournamentDTO tournamentDto)
{
_tournamentDTO = tournamentDto;
}

public TournamentDTO generate()
{
foreach (var player in _tournamentDTO.Players)
{
if (player.Title.Name == TitleEnum.InternationalMaster.ToString())
{
player.Title = new InternationalMaster{Name = player.Title.Name, Id=player.Title.Id};
}

else if (player.Title.Name == TitleEnum.CandidateMaster.ToString())
{
player.Title = new CandidateMaster() { Name = player.Title.Name, Id = player.Title.Id };
}

else if (player.Title.Name == TitleEnum.FideMaster.ToString())
{
player.Title = new FideMaster() { Name = player.Title.Name, Id = player.Title.Id };
}

else if (player.Title.Name == TitleEnum.GrandMaster.ToString())
{
player.Title = new GrandMaster() { Name = player.Title.Name, Id = player.Title.Id };
}

else if (player.Title.Name == TitleEnum.Noob.ToString())
{
player.Title = new Noob() { Name = player.Title.Name, Id = player.Title.Id };
}
}

return _tournamentDTO;
}
}

I use this solution because I realized that when I input the Title object of Player, the program doesn’t created any inherited classes (GrandMaster etc). It’s only created abstract class Title.

Database Context

I use the Entity Framework Core In-Memory to perform database operations. Ef core can convert the entity classes to a database table. Ef core actually is a ORM (Object Relational Mapping) framework. We can treat entity classes that we descripte in the database context as database Table.

public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options)
{

}

public DbSet<Player> Players { get; set; }
public DbSet<Tournament> Tournaments { get; set; }
public DbSet<Title> Titles { get; set; }

//protected override void OnModelCreating(ModelBuilder modelBuilder)
//{
// modelBuilder.Entity<Title>()
// .HasDiscriminator<int>("discriminator")
// .HasValue<Title>(1)
// .HasValue<Noob>(2)
// .HasValue<GrandMaster>(3)
// .HasValue<InternationalMaster>(4)
// .HasValue<FideMaster>(5)
// .HasValue<CandidateMaster>(6);
//}
}

I tried to map the title class which is abstract class and its enheritances to database using entity framework core but I didn’t succeed. So I comment on those rows. I wrote a class for this operation above(TitleShortcutGenerator). I didn’t delete these rows so that you can realize that ef core can do that.

Controller

Controller is a place where all requests handle first. There is some action in the controller such as HttpGet and HttpPut etc. While developing API controller classes must name plural (I forgot this). The controller handle the requests and take an action according to that request.

[Route("api/[controller]")]
[ApiController]
public class TournamentController : ControllerBase
{
private DatabaseContext _databaseContext;

public TournamentController(DatabaseContext databaseContext)
{
_databaseContext = databaseContext;
}

[HttpGet()]
public IActionResult Get()
{

var result = _databaseContext.Tournaments.Include(t => t.Players)
.ThenInclude(p => p.Title).ToList();
return Ok(result);

}

[HttpPost]
[ValidationActionFilter]
public IActionResult Post([FromBody] TournamentDTO tournamentDto)
{

TitleShorcutGenerator shorcutGenerator = new TitleShorcutGenerator(tournamentDto);
shorcutGenerator.generate();
Tournament tournament = tournamentDto.ToTournament();

_databaseContext.Tournaments.Add(tournament);
_databaseContext.Players.AddRange(tournament.Players);
_databaseContext.SaveChanges();
return Ok();
}

[HttpGet()]
[Route("players")]
public IActionResult GetPlayers()
{
var players = _databaseContext.Players.Include(p => p.Title).ToList();
return Ok(players);
}

[HttpGet()]
[Route("Fixture")]
public IActionResult GetFixture()
{
var players = _databaseContext.Players.Include(p => p.Title).ToList();
var playersDTO = players.ToPlayersDTOs();

BasicFixture basicFixture = new BasicFixture();

basicFixture.MatchPlayers(playersDTO);

return Ok(basicFixture.Fixture.ToList());
}
}

We can the action request type by writing some attribute above that action. For instance , when GetFixture action run, we know that HttpGet request is called because we specify this by writing [HttpGet] above it.

Actually, the controller should not be like above. Because this code is not clean code. Since, it's all depents _databaseContext and CRUD operations made on controller actions. But my the purpose of this blog post is not how to write clean code. I just introduced some piece of an API.

Before later on with Postman, I would like to catch your attention to some controller actions. For example, in GetFixture() method we get some data from Players and included the player’s title because of the title as an object and save it to the player's variable. We need to map our players to players DTO bacause we need to players DTO variable to match players.

Startup Class

public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().ConfigureApiBehaviorOptions(options=>
{
options.SuppressModelStateInvalidFilter = true;
});

services.AddDbContext<DatabaseContext>(options => options.UseInMemoryDatabase(databaseName: "ChessAPI"));


}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}

In the Startup class, we do some configurations. We can configure our dependencies and database context in the ConfigureServices method. Also, we can configure additional packages like middle-wares in the Configure method. Middle-wares is software that stands between incoming requests and outgoing responses. Thus, middle-wares can handle the pipeline between the requests and responses. For example, Routing middleware routes the incoming request to specific controller.

Configure method actually responsible for HTTP request pipeline and it knows how to handle incoming and outgoing request.

Test API with Postman

Postman is such a good thing to test API and you can do more. I created 3 Get request and one Post request in postman. I add a tournament and 4 players like this:

{
"Name":"World Championship",
"Date":"2021-02-04",
"Location":"Turkey",
"Players":[
{"Name":"Ömer","LastName":"Beden","Elo":2175,"Gender":"Man",
"Title":{
"Name":"Noob"
}
},
{"Name":"Öner","LastName":"Beden","Elo":2450,"Gender":"Man",
"Title":{
"Name":"InternationalMaster"
}
},
{"Name":"Arif","LastName":"Gider","Elo":2350,"Gender":"Man",
"Title":{
"Name":"FideMaster"
}
},
{"Name":"Gizem","LastName":"Bulut","Elo":2260,"Gender":"Woman",
"Title":{
"Name":"CandidateMaster"
}
}
]
}

To write the above code, you need to create a post request and write the above code in Body-raw. Be sure that the file type is JSON. Thereby we can catch the request from controller post action parameters with [frombody] attribute.

We did not write “id” filed in the post method but when we send to get a request to API, we can obtain there is an “id” field and there is also a shortcut field. Entity framework core automatically creates a “field” in the databse because we create Id property in the Tournament model class. And remember that we write a class to generate shourcut for title this is why we see it.

Also, you can check my personal blog post omerbeden.com

--

--