Skip to content

Commit

Permalink
Add ControllerWebAPI project
Browse files Browse the repository at this point in the history
  • Loading branch information
Capella87 committed Feb 2, 2025
1 parent faa60b4 commit bc302a0
Show file tree
Hide file tree
Showing 10 changed files with 478 additions and 0 deletions.
32 changes: 32 additions & 0 deletions ControllerWebAPI/ControllerWebAPI.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.*" />
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server" Version="9.0.*">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.*" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.*" />
<PackageReference Include="Microsoft.OpenApi" Version="1.6.*" />
<PackageReference Include="Scalar.AspNetCore" Version="2.0.9" />
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.*" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.*" />
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.*" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
</ItemGroup>

<ItemGroup>
<Folder Include="Dtos\" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions ControllerWebAPI/ControllerWebAPI.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@ControllerWebAPI_HostAddress = http://localhost:5170

GET {{ControllerWebAPI_HostAddress}}/weatherforecast/
Accept: application/json

###
28 changes: 28 additions & 0 deletions ControllerWebAPI/Game.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace ControllerWebAPI.Models;

public class Game
{
public required string Id { get; set; }

public required string Name { get; set; }

public DateOnly? ReleaseDate { get; set; }

public IEnumerable<string>? Genres { get; set; }

public string? Publisher { get; set; }

public string? Developer { get; set; }

public string? Description { get; set; }

public Game(string Id, string Name)
{
this.Id = Id;
this.Name = Name;
}

public Game()
{
}
}
64 changes: 64 additions & 0 deletions ControllerWebAPI/GameController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Microsoft.AspNetCore.Mvc;

using ControllerWebAPI.Models;
using ControllerWebAPI.Services;
using Microsoft.AspNetCore.Mvc.Infrastructure;

namespace ControllerWebAPI.Controllers;

[ApiController]
// Controller is a class that handles HTTP requests, and it is a collection of actions.
// ControllerBase is a base class for an MVC controller without view support. So, it is used to create Web APIs.
public class GameController : ControllerBase
{
private readonly GameService _gameService;

public GameController(GameService gameService)
{
_gameService = gameService;
}

[HttpGet("/games")]
public async Task<IEnumerable<Game>> Index()
{
return await Task.FromResult(_gameService.Games);
}


// These are all actions
[HttpGet("game/{id}")]
public async Task<ActionResult<Game>> Show(string id)
{
var game = _gameService.GetGameById(id);
// If found
return game != null ? await Task.FromResult(game) : NotFound();
}

[HttpPost("/game")]
public async Task<ActionResult<Game>> Add([FromBody] Game newGame)
{
if (!ModelState.IsValid) return BadRequest(new ValidationProblemDetails(ModelState));

if (_gameService.Games.Any(_games => _games.Id == newGame.Id))
{
// Return bad request by Problem method with ProblemDetails type
// If Problem method is invoked with ProblemDetails related parameters,
// it will return a decent ProblemDetails object with supplied parameters and default values for other properties.
return Problem(detail: $"Game with id {newGame.Id} already exists.", statusCode: StatusCodes.Status400BadRequest);
}

_gameService.AddGame(newGame);
return await Task.FromResult(CreatedAtAction(nameof(Show), new { id = newGame.Id }, newGame));
}

[HttpDelete("/game/{id}")]
public async Task<ActionResult> Delete([FromRoute] string id)
{
var result = _gameService.GetGameById(id);
if (result == null)
return NotFound();
_gameService.DeleteGame(id);

return await Task.FromResult(NoContent());
}
}
33 changes: 33 additions & 0 deletions ControllerWebAPI/GameService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace ControllerWebAPI.Services;

using ControllerWebAPI;
using ControllerWebAPI.Models;

public class GameService
{
private readonly List<Game> _games =
[
new Game { Id = "aoe2", Name = "Age of Empires 2: Definitive Edition" },
new Game { Id = "aoe3", Name = "Age of Empires 3: Definitive Edition" },
new Game { Id = "aom", Name = "Age of Mythology: Retold" },
new Game { Id = "sdv", Name = "Stardew Valley" },
new Game { Id = "ts3", Name = "The Sims 3" },
new Game { Id = "ts2", Name = "The Sims 2" },
new Game { Id = "ets", Name = "Euro Truck Simulator 2" },
new Game { Id = "f4", Name = "Fallout 4" },
new Game { Id = "f3", Name = "Fallout 3" },
new Game { Id = "fnv", Name = "Fallout: New Vegas" },
new Game { Id = "tropico4", Name = "Tropico 4" },
new Game { Id = "halo", Name = "Halo: Master Chief Collection" },
];

public IEnumerable<Game> Games => _games;

public IEnumerable<Game> GetAllGames() => _games;

public Game? GetGameById(string id) => _games.FirstOrDefault(g => g.Id == id);

public void AddGame(Game newGame) => _games.Add(newGame);

public void DeleteGame(string id) => _games.Remove(_games.First(g => g.Id == id));
}
131 changes: 131 additions & 0 deletions ControllerWebAPI/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using Microsoft.AspNetCore.Builder;
using ControllerWebAPI;
using System.Text.Json;
using Serilog;
using Serilog.Events;
using ControllerWebAPI.Controllers;
using ControllerWebAPI.Services;
using System.Diagnostics;

var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddEnvironmentVariables()
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Development"}.json",
optional: true, reloadOnChange: true)
.Build();

Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();

try
{
Log.Information("Starting the host...");
var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddConfiguration(configuration);
builder.Services.AddSerilog();

// Routing configuration
builder.Services.Configure<RouteOptions>(o =>
{
o.LowercaseUrls = true;
o.AppendTrailingSlash = true;
o.LowercaseQueryStrings = true;
});

// JSON configuration
builder.Services.ConfigureHttpJsonOptions(o =>
{
o.SerializerOptions.AllowTrailingCommas = false;
o.SerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip;
o.SerializerOptions.PropertyNameCaseInsensitive = true;
});

// Enable Scope Validation always (By default, it is only enabled in development)
builder.Host.UseDefaultServiceProvider(o =>
{
o.ValidateScopes = true;
o.ValidateOnBuild = true;
});

builder.Services.AddProblemDetails(config =>
{
config.CustomizeProblemDetails = (ctx) =>
{
ctx.ProblemDetails.Type ??= "https://tools.ietf.org/html/rfc7231#section-6.5.4";
if (!ctx.ProblemDetails.Extensions.ContainsKey("traceId"))
{
var tId = Activity.Current?.Id ?? ctx.HttpContext.TraceIdentifier;
ctx.ProblemDetails.Extensions.Add(new KeyValuePair<string, object?>("traceId", tId));
}
};
});
builder.Services.AddAntiforgery();
if (builder.Environment.IsDevelopment())
{
builder.Services.AddHealthChecks();
}


// Add services to the container.
builder.Services.AddSingleton<GameService, GameService>();

// AddControllers and MapControllers for MVC Web API.
builder.Services.AddControllers();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApi();

var app = builder.Build();
app.UseRouting();
app.UseSerilogRequestLogging((opts) =>
{
opts.MessageTemplate = "{Protocol} {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
opts.GetMessageTemplateProperties = (HttpContext httpContext, string requestPath, double elapsedMs, int statusCode) =>
[
new LogEventProperty("Protocol", new ScalarValue(httpContext.Request.Protocol)),
new LogEventProperty("RequestMethod", new ScalarValue(httpContext.Request.Method)),
new LogEventProperty("RequestPath", new ScalarValue(requestPath)),
new LogEventProperty("StatusCode", new ScalarValue(statusCode)),
new LogEventProperty("Elapsed", new ScalarValue(elapsedMs))
];
});

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}

app.UseHttpsRedirection();
app.UseCors();
app.UseAntiforgery();
app.UseStatusCodePages();
app.UseAuthorization();
app.MapControllers();

app.MapGet("/", () => "Hello World!");

app.Run();
}
catch (Exception ex) when (ex is not HostAbortedException && ex.Source != "Microsoft.EntityFrameworkCore.Design")
{
Log.Fatal(ex, "The WebApplication host terminated unexpectedly...");
}
catch (HostAbortedException ex) when (ex.Source != "Microsoft.EntityFrameworkCore.Design")
{
Log.Fatal(ex, "The WebApplication host is aborted...");
}
finally
{
Log.Information("Closing the logger...");
Log.CloseAndFlush();
}
23 changes: 23 additions & 0 deletions ControllerWebAPI/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"profiles": {
"ControllerWebAPI-Dev": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:20621;http://localhost:10327",
"dotnetRunMessages": true
},
"ControllerWebAPI-Production": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:5929;http://localhost:5950"
}
},
"$schema": "http://json.schemastore.org/launchsettings.json"
}
Loading

0 comments on commit bc302a0

Please sign in to comment.