first commit
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.OpenApi;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Configurations
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the Bearer security requirement only to operations that have [Authorize] attributes.
|
||||
/// </summary>
|
||||
public class AuthorizeCheckOperationFilter : IOperationFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies the Bearer security requirement to Swagger operations with [Authorize] attributes.
|
||||
/// </summary>
|
||||
/// <param name="operation">The OpenAPI operation to modify.</param>
|
||||
/// <param name="context">The operation filter context containing method metadata.</param>
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
// Check if [Authorize] is applied at the method or class level
|
||||
var hasAuthorize =
|
||||
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any()
|
||||
|| context
|
||||
.MethodInfo.DeclaringType?.GetCustomAttributes(true)
|
||||
.OfType<AuthorizeAttribute>()
|
||||
.Any() == true;
|
||||
|
||||
// If there's no [Authorize] attribute, skip adding the security requirement
|
||||
if (!hasAuthorize)
|
||||
return;
|
||||
|
||||
// Add security requirement (shows the lock icon)
|
||||
// In Microsoft.OpenApi 2.x, use OpenApiSecuritySchemeReference instead of OpenApiSecurityScheme with nested Reference
|
||||
if (operation is OpenApiOperation openApiOperation)
|
||||
{
|
||||
openApiOperation.Security ??= new List<OpenApiSecurityRequirement>();
|
||||
openApiOperation.Security.Add(
|
||||
new OpenApiSecurityRequirement
|
||||
{
|
||||
{ new OpenApiSecuritySchemeReference("Bearer", null), new List<string>() }
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration options for the Fixed Window Rate Limiter.
|
||||
/// </summary>
|
||||
public class RateLimiterConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of permits that can be leased per window.
|
||||
/// Default value is 60 requests.
|
||||
/// </summary>
|
||||
public int PermitLimit { get; set; } = 60;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time window in seconds for rate limiting.
|
||||
/// Default value is 60 seconds (1 minute).
|
||||
/// </summary>
|
||||
public int WindowSeconds { get; set; } = 60;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of requests that can be queued when the permit limit is exceeded.
|
||||
/// A value of 0 means no queuing (default).
|
||||
/// </summary>
|
||||
public int QueueLimit { get; set; } = 0;
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
using System.Net.Mime;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Models;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Services;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("players")]
|
||||
[Produces("application/json")]
|
||||
public class PlayerController(
|
||||
IPlayerService playerService,
|
||||
ILogger<PlayerController> logger,
|
||||
IValidator<PlayerRequestModel> validator
|
||||
) : ControllerBase
|
||||
{
|
||||
private const string NotFoundTitle = "Not Found";
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* HTTP POST
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Player
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerRequestModel</param>
|
||||
/// <response code="201">Created</response>
|
||||
/// <response code="409">Conflict</response>
|
||||
/// <response code="422">Unprocessable Entity</response>
|
||||
[HttpPost(Name = "Create")]
|
||||
[Consumes(MediaTypeNames.Application.Json)]
|
||||
[ProducesResponseType<PlayerResponseModel>(StatusCodes.Status201Created)]
|
||||
[ProducesResponseType<ProblemDetails>(StatusCodes.Status409Conflict)]
|
||||
[ProducesResponseType<ProblemDetails>(StatusCodes.Status422UnprocessableEntity)]
|
||||
public async Task<IResult> PostAsync([FromBody] PlayerRequestModel player)
|
||||
{
|
||||
// Use the "Create" rule set, which includes BeUniqueSquadNumber.
|
||||
var validation = await validator.ValidateAsync(
|
||||
player,
|
||||
options => options.IncludeRuleSets("Create")
|
||||
);
|
||||
|
||||
if (!validation.IsValid)
|
||||
{
|
||||
var errors = validation
|
||||
.Errors.GroupBy(e => e.PropertyName)
|
||||
.ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray());
|
||||
|
||||
logger.LogWarning("POST /players validation failed: {@Errors}", errors);
|
||||
return TypedResults.Problem(
|
||||
new HttpValidationProblemDetails(errors)
|
||||
{
|
||||
Status = StatusCodes.Status422UnprocessableEntity,
|
||||
Detail = "See the errors field for details.",
|
||||
Instance = HttpContext?.Request?.Path.ToString(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (await playerService.RetrieveBySquadNumberAsync(player.SquadNumber) != null)
|
||||
{
|
||||
logger.LogWarning(
|
||||
"POST /players failed: Player with Squad Number {SquadNumber} already exists",
|
||||
player.SquadNumber
|
||||
);
|
||||
return TypedResults.Conflict(
|
||||
new ProblemDetails
|
||||
{
|
||||
Type = "https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409",
|
||||
Title = "Conflict",
|
||||
Status = StatusCodes.Status409Conflict,
|
||||
Detail = $"Player with Squad Number '{player.SquadNumber}' already exists.",
|
||||
Instance = HttpContext?.Request?.Path.ToString(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var result = await playerService.CreateAsync(player);
|
||||
|
||||
logger.LogInformation("POST /players created: {@Player}", result);
|
||||
return TypedResults.CreatedAtRoute(
|
||||
routeName: "RetrieveBySquadNumber",
|
||||
routeValues: new { squadNumber = result.Dorsal },
|
||||
value: result
|
||||
);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* HTTP GET
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all Players
|
||||
/// </summary>
|
||||
/// <response code="200">OK</response>
|
||||
[HttpGet(Name = "Retrieve")]
|
||||
[ProducesResponseType<PlayerResponseModel>(StatusCodes.Status200OK)]
|
||||
public async Task<IResult> GetAsync()
|
||||
{
|
||||
var players = await playerService.RetrieveAsync();
|
||||
|
||||
logger.LogInformation("GET /players retrieved {Count} player(s)", players.Count);
|
||||
|
||||
return TypedResults.Ok(players);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a Player by its internal Id (GUID)
|
||||
/// </summary>
|
||||
/// <param name="id">The internal Id (GUID) of the Player</param>
|
||||
/// <response code="200">OK</response>
|
||||
/// <response code="404">Not Found</response>
|
||||
[Authorize]
|
||||
[HttpGet("{id:Guid}", Name = "RetrieveById")]
|
||||
[ProducesResponseType<PlayerResponseModel>(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
|
||||
public async Task<IResult> GetByIdAsync([FromRoute] Guid id)
|
||||
{
|
||||
var player = await playerService.RetrieveByIdAsync(id);
|
||||
if (player != null)
|
||||
{
|
||||
logger.LogInformation("GET /players/{Id} retrieved: {@Player}", id, player);
|
||||
return TypedResults.Ok(player);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("GET /players/{Id} not found", id);
|
||||
return TypedResults.Problem(
|
||||
statusCode: StatusCodes.Status404NotFound,
|
||||
title: NotFoundTitle,
|
||||
detail: $"Player with Id '{id}' was not found.",
|
||||
instance: HttpContext?.Request?.Path.ToString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a Player by its Squad Number
|
||||
/// </summary>
|
||||
/// <param name="squadNumber">The Squad Number of the Player</param>
|
||||
/// <response code="200">OK</response>
|
||||
/// <response code="404">Not Found</response>
|
||||
[HttpGet("squadNumber/{squadNumber:int}", Name = "RetrieveBySquadNumber")]
|
||||
[ProducesResponseType<PlayerResponseModel>(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
|
||||
public async Task<IResult> GetBySquadNumberAsync([FromRoute] int squadNumber)
|
||||
{
|
||||
var player = await playerService.RetrieveBySquadNumberAsync(squadNumber);
|
||||
if (player != null)
|
||||
{
|
||||
logger.LogInformation(
|
||||
"GET /players/squadNumber/{SquadNumber} retrieved: {@Player}",
|
||||
squadNumber,
|
||||
player
|
||||
);
|
||||
return TypedResults.Ok(player);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("GET /players/squadNumber/{SquadNumber} not found", squadNumber);
|
||||
return TypedResults.Problem(
|
||||
statusCode: StatusCodes.Status404NotFound,
|
||||
title: NotFoundTitle,
|
||||
detail: $"Player with Squad Number '{squadNumber}' was not found.",
|
||||
instance: HttpContext?.Request?.Path.ToString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* HTTP PUT
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
/// <summary>
|
||||
/// Updates (entirely) a Player by its Squad Number
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="player">The PlayerRequestModel</param>
|
||||
/// <param name="squadNumber">The Squad Number of the Player</param>
|
||||
/// <response code="204">No Content</response>
|
||||
/// <response code="400">Bad Request (route/body squad number mismatch)</response>
|
||||
/// <response code="404">Not Found</response>
|
||||
/// <response code="422">Unprocessable Entity (field validation failure)</response>
|
||||
[HttpPut("squadNumber/{squadNumber:int}", Name = "Update")]
|
||||
[Consumes(MediaTypeNames.Application.Json)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType<ProblemDetails>(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType<ProblemDetails>(StatusCodes.Status422UnprocessableEntity)]
|
||||
public async Task<IResult> PutAsync(
|
||||
[FromRoute] int squadNumber,
|
||||
[FromBody] PlayerRequestModel player
|
||||
)
|
||||
{
|
||||
// Use the "Update" rule set, which omits BeUniqueSquadNumber.
|
||||
// The player being updated already exists in the database, so a
|
||||
// uniqueness check on its own squad number would always fail.
|
||||
var validation = await validator.ValidateAsync(
|
||||
player,
|
||||
options => options.IncludeRuleSets("Update")
|
||||
);
|
||||
if (!validation.IsValid)
|
||||
{
|
||||
var errors = validation
|
||||
.Errors.GroupBy(e => e.PropertyName)
|
||||
.ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray());
|
||||
|
||||
logger.LogWarning(
|
||||
"PUT /players/squadNumber/{SquadNumber} validation failed: {@Errors}",
|
||||
squadNumber,
|
||||
errors
|
||||
);
|
||||
return TypedResults.Problem(
|
||||
new HttpValidationProblemDetails(errors)
|
||||
{
|
||||
Status = StatusCodes.Status422UnprocessableEntity,
|
||||
Detail = "See the errors field for details.",
|
||||
Instance = HttpContext?.Request?.Path.ToString(),
|
||||
}
|
||||
);
|
||||
}
|
||||
if (player.SquadNumber != squadNumber)
|
||||
{
|
||||
logger.LogWarning(
|
||||
"PutAsync squad number mismatch: route {SquadNumber} != body {PlayerSquadNumber}",
|
||||
squadNumber,
|
||||
player.SquadNumber
|
||||
);
|
||||
return TypedResults.Problem(
|
||||
statusCode: StatusCodes.Status400BadRequest,
|
||||
title: "Bad Request",
|
||||
detail: "Squad number in the route does not match squad number in the request body.",
|
||||
instance: HttpContext?.Request?.Path.ToString()
|
||||
);
|
||||
}
|
||||
if (await playerService.RetrieveBySquadNumberAsync(squadNumber) == null)
|
||||
{
|
||||
logger.LogWarning("PUT /players/squadNumber/{SquadNumber} not found", squadNumber);
|
||||
return TypedResults.Problem(
|
||||
statusCode: StatusCodes.Status404NotFound,
|
||||
title: NotFoundTitle,
|
||||
detail: $"Player with Squad Number '{squadNumber}' was not found.",
|
||||
instance: HttpContext?.Request?.Path.ToString()
|
||||
);
|
||||
}
|
||||
await playerService.UpdateAsync(player);
|
||||
// Sanitize user-provided player data before logging to prevent log forging
|
||||
var sanitizedPlayerString = player
|
||||
.ToString()
|
||||
?.Replace(Environment.NewLine, string.Empty)
|
||||
.Replace("\r", string.Empty)
|
||||
.Replace("\n", string.Empty);
|
||||
logger.LogInformation(
|
||||
"PUT /players/squadNumber/{SquadNumber} updated: {Player}",
|
||||
squadNumber,
|
||||
sanitizedPlayerString
|
||||
);
|
||||
return TypedResults.NoContent();
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* HTTP DELETE
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a Player by its Squad Number
|
||||
/// </summary>
|
||||
/// <param name="squadNumber">The Squad Number of the Player</param>
|
||||
/// <response code="204">No Content</response>
|
||||
/// <response code="404">Not Found</response>
|
||||
[HttpDelete("squadNumber/{squadNumber:int}", Name = "Delete")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType<ProblemDetails>(StatusCodes.Status404NotFound)]
|
||||
public async Task<IResult> DeleteAsync([FromRoute] int squadNumber)
|
||||
{
|
||||
if (await playerService.RetrieveBySquadNumberAsync(squadNumber) == null)
|
||||
{
|
||||
logger.LogWarning("DELETE /players/squadNumber/{SquadNumber} not found", squadNumber);
|
||||
return TypedResults.Problem(
|
||||
statusCode: StatusCodes.Status404NotFound,
|
||||
title: "Not Found",
|
||||
detail: $"Player with Squad Number '{squadNumber}' was not found.",
|
||||
instance: HttpContext?.Request?.Path.ToString()
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
await playerService.DeleteAsync(squadNumber);
|
||||
logger.LogInformation("DELETE /players/squadNumber/{SquadNumber} deleted", squadNumber);
|
||||
return TypedResults.NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Models;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Utilities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the EF Core database context for a Player entity.
|
||||
/// Inherits from <see cref="DbContext"/> and provides a bridge between the entity and the database.
|
||||
/// </summary>
|
||||
public class PlayerDbContext(DbContextOptions<PlayerDbContext> options) : DbContext(options)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="DbSet{TEntity}"/> representing a collection of Player entities.
|
||||
/// <see cref="DbSet{TEntity}"/> corresponds to a table in the database, allowing CRUD operations and LINQ queries.
|
||||
/// </summary>
|
||||
public DbSet<Player> Players => Set<Player>();
|
||||
|
||||
/// <summary>
|
||||
/// Configures the model for the Player entity.
|
||||
/// This method is called by the runtime to configure the model for the context.
|
||||
/// </summary>
|
||||
/// <param name="modelBuilder">The model builder.</param>
|
||||
/// <remarks>
|
||||
/// This method is used to configure the model and relationships using the Fluent API.
|
||||
/// It is called when the model for a derived context is being created.
|
||||
/// </remarks>
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<Player>(entity =>
|
||||
{
|
||||
entity.HasKey(player => player.Id);
|
||||
entity.Property(player => player.Id).ValueGeneratedOnAdd();
|
||||
entity.HasIndex(player => player.SquadNumber).IsUnique();
|
||||
entity.HasData(PlayerData.MakeStarting11WithId());
|
||||
entity.HasData(PlayerData.GetSubstitutesWithId());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Label="Development dependencies">
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="10.0.2" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.9" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Label="Runtime dependencies">
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.9" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2" />
|
||||
<PackageReference Include="AutoMapper" Version="[16.1.1,17.0.0)" />
|
||||
<PackageReference Include="FluentValidation" Version="12.1.1" />
|
||||
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="10.0.1" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SatelliteResourceLanguages>en-US</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration class
|
||||
/// https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/enumeration-classes-over-enum-types
|
||||
/// </summary>
|
||||
public abstract class Enumeration
|
||||
{
|
||||
public int Id { get; private set; }
|
||||
|
||||
public string Text { get; private set; }
|
||||
|
||||
protected Enumeration(int id, string name) => (Id, Text) = (id, name);
|
||||
|
||||
public override string ToString() => Text;
|
||||
|
||||
public static IEnumerable<T> GetAll<T>()
|
||||
where T : Enumeration =>
|
||||
typeof(T)
|
||||
.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
|
||||
.Select(f => f.GetValue(null))
|
||||
.Cast<T>();
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is not Enumeration enumeration)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var areSameType = GetType().Equals(obj.GetType());
|
||||
var areSameValue = Id.Equals(enumeration.Id);
|
||||
|
||||
return areSameType && areSameValue;
|
||||
}
|
||||
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
|
||||
public int CompareTo(object other) => Id.CompareTo(((Enumeration)other).Id);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Enums;
|
||||
|
||||
public class Position : Enumeration
|
||||
{
|
||||
public string Abbr { get; private set; }
|
||||
|
||||
public static readonly Position Goalkeeper = new(1, "Goalkeeper", "GK");
|
||||
public static readonly Position RightBack = new(2, "Right-Back", "RB");
|
||||
public static readonly Position LeftBack = new(3, "Left-Back", "LB");
|
||||
public static readonly Position CentreBack = new(4, "Centre-Back", "CB");
|
||||
public static readonly Position DefensiveMidfield = new(5, "Defensive Midfield", "DM");
|
||||
public static readonly Position CentralMidfield = new(6, "Central Midfield", "CM");
|
||||
public static readonly Position RightWinger = new(7, "Right Winger", "RW");
|
||||
public static readonly Position AttackingMidfield = new(8, "Attacking Midfield", "AM");
|
||||
public static readonly Position CentreForward = new(9, "Centre-Forward", "CF");
|
||||
public static readonly Position SecondStriker = new(10, "Second Striker", "SS");
|
||||
public static readonly Position LeftWinger = new(11, "Left Winger", "LW");
|
||||
|
||||
private Position(int id, string name, string abbr)
|
||||
: base(id, name) => Abbr = abbr;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a Position object based on the abbreviation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method searches through all the Position objects and returns the one
|
||||
/// that matches the provided abbreviation. If no match is found, it returns null.
|
||||
/// </remarks>
|
||||
/// <param name="abbr">The abbreviation of the Position.</param>
|
||||
/// <returns>
|
||||
/// A Position object if found; otherwise, null.
|
||||
/// </returns>
|
||||
public static Position? FromAbbr(string abbr) =>
|
||||
GetAll<Position>().FirstOrDefault(position => position.Abbr == abbr);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a Position object based on the ID.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method searches through all the Position objects and returns the one
|
||||
/// that matches the provided ID. If no match is found, it returns null.
|
||||
/// </remarks>
|
||||
/// <param name="id">The ID of the Position.</param>
|
||||
/// <returns>
|
||||
/// A Position object if found; otherwise, null.
|
||||
/// </returns>
|
||||
public static Position? FromId(int id) =>
|
||||
GetAll<Position>().FirstOrDefault(position => position.Id == id);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the provided abbreviation is valid.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method checks if the provided abbreviation is not null or empty and
|
||||
/// if it corresponds to a valid Position object.
|
||||
/// </remarks>
|
||||
/// <param name="abbr">The abbreviation to check.</param>
|
||||
/// <returns>
|
||||
/// True if the abbreviation is valid; otherwise, false.
|
||||
/// </returns>
|
||||
public static bool IsValidAbbr(string? abbr) =>
|
||||
!string.IsNullOrWhiteSpace(abbr) && FromAbbr(abbr!) is not null;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Middlewares;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for configuring middleware in the application pipeline.
|
||||
/// </summary>
|
||||
public static class MiddlewareExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds global exception handling middleware to the application pipeline.
|
||||
/// This middleware catches unhandled exceptions and returns RFC 7807 compliant error responses.
|
||||
/// </summary>
|
||||
/// <param name="app">The web application used to configure the HTTP pipeline, and routes.</param>
|
||||
/// <returns>The WebApplication object for method chaining.</returns>
|
||||
public static WebApplication UseExceptionHandling(this WebApplication app)
|
||||
{
|
||||
app.UseMiddleware<ExceptionMiddleware>();
|
||||
return app;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
using System.Threading.RateLimiting;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Configurations;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Data;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Mappings;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Migrations;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Repositories;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Services;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Utilities;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Validators;
|
||||
using FluentValidation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.OpenApi;
|
||||
using Serilog;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for IServiceCollection to encapsulate service configuration.
|
||||
/// </summary>
|
||||
public static partial class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds PlayerDbContext, selecting the database provider based on the
|
||||
/// <c>DATABASE_PROVIDER</c> environment variable (<c>sqlite</c> by default, <c>postgres</c>
|
||||
/// to opt in to PostgreSQL).
|
||||
/// </summary>
|
||||
/// <param name="services">The IServiceCollection instance.</param>
|
||||
/// <param name="environment">The web host environment.</param>
|
||||
/// <returns>The IServiceCollection for method chaining.</returns>
|
||||
public static IServiceCollection AddPlayerDbContext(
|
||||
this IServiceCollection services,
|
||||
IWebHostEnvironment environment
|
||||
)
|
||||
{
|
||||
services.AddDbContextPool<PlayerDbContext>(options =>
|
||||
{
|
||||
var provider = (Environment.GetEnvironmentVariable("DATABASE_PROVIDER") ?? "")
|
||||
.Trim()
|
||||
.ToLowerInvariant();
|
||||
|
||||
switch (provider)
|
||||
{
|
||||
case "postgres":
|
||||
var connectionString = Environment.GetEnvironmentVariable("DATABASE_URL");
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
throw new InvalidOperationException(
|
||||
"DATABASE_URL is required when DATABASE_PROVIDER=postgres."
|
||||
);
|
||||
options.UseNpgsql(connectionString, npgsql => npgsql.EnableRetryOnFailure());
|
||||
// Hand-crafted designer files cannot replicate Npgsql-injected runtime
|
||||
// annotations (Relational:MaxIdentifierLength, UseIdentityByDefaultColumn),
|
||||
// causing a false-positive PendingModelChangesWarning. Suppressed here;
|
||||
// BuildTargetModel is still populated so InsertData SQL generation works.
|
||||
options.ConfigureWarnings(w =>
|
||||
w.Ignore(RelationalEventId.PendingModelChangesWarning)
|
||||
);
|
||||
break;
|
||||
|
||||
case "sqlite":
|
||||
case "":
|
||||
var storagePath = Environment.GetEnvironmentVariable("STORAGE_PATH");
|
||||
var dataSource = !string.IsNullOrWhiteSpace(storagePath)
|
||||
? storagePath
|
||||
: Path.Combine(AppContext.BaseDirectory, "storage", "players-sqlite3.db");
|
||||
|
||||
var storageDir = Path.GetDirectoryName(dataSource);
|
||||
if (!string.IsNullOrWhiteSpace(storageDir))
|
||||
{
|
||||
Directory.CreateDirectory(storageDir);
|
||||
}
|
||||
options.UseSqlite($"Data Source={dataSource}");
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException(
|
||||
$"Unsupported DATABASE_PROVIDER value: '{provider}'. "
|
||||
+ "Valid values are 'sqlite' (default) and 'postgres'."
|
||||
);
|
||||
}
|
||||
|
||||
if (environment.IsDevelopment())
|
||||
{
|
||||
options.EnableSensitiveDataLogging();
|
||||
options.LogTo(Log.Logger.Information, LogLevel.Information);
|
||||
}
|
||||
|
||||
options.ReplaceService<IMigrationsAssembly, ProviderSpecificMigrationsAssembly>();
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a default CORS policy that allows any origin, method, and header,
|
||||
/// restricted to the Development environment.
|
||||
/// <br />
|
||||
/// <see href="https://learn.microsoft.com/en-us/aspnet/core/security/cors"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The permissive wildcard policy (AllowAnyOrigin, AllowAnyMethod, AllowAnyHeader)
|
||||
/// is intentional for local development, where Swagger UI and local frontends
|
||||
/// need unrestricted cross-origin access. No CORS policy is registered in
|
||||
/// Production or other environments, where the API is assumed to be consumed
|
||||
/// server-to-server or to sit behind a reverse proxy on the same origin, making
|
||||
/// CORS irrelevant. If a production frontend on a different domain is ever added,
|
||||
/// replace this with a restrictive named policy that enumerates specific allowed
|
||||
/// origins instead of using a wildcard.
|
||||
/// </remarks>
|
||||
/// <param name="services">The IServiceCollection instance.</param>
|
||||
/// <param name="environment">The web host environment.</param>
|
||||
/// <returns>The IServiceCollection for method chaining.</returns>
|
||||
public static IServiceCollection AddCorsDefaultPolicy(
|
||||
this IServiceCollection services,
|
||||
IWebHostEnvironment environment
|
||||
)
|
||||
{
|
||||
if (environment.IsDevelopment())
|
||||
{
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(corsBuilder =>
|
||||
{
|
||||
corsBuilder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// No CORS configured in Production or other environments
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds FluentValidation validators for Player models.
|
||||
/// <br />
|
||||
/// <see href="https://docs.fluentvalidation.net/en/latest/aspnet.html"/>
|
||||
/// </summary>
|
||||
/// <param name="services">The IServiceCollection instance.</param>
|
||||
/// <returns>The IServiceCollection for method chaining.</returns>
|
||||
public static IServiceCollection AddValidators(this IServiceCollection services)
|
||||
{
|
||||
services.AddValidatorsFromAssemblyContaining<PlayerRequestModelValidator>();
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up Swagger documentation generation and UI for the API.
|
||||
/// <br />
|
||||
/// <see href="https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle" />
|
||||
/// </summary>
|
||||
/// <param name="services">The IServiceCollection instance.</param>
|
||||
/// <param name="configuration">The application configuration.</param>
|
||||
/// <returns>The IServiceCollection for method chaining.</returns>
|
||||
public static IServiceCollection AddSwaggerConfiguration(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration
|
||||
)
|
||||
{
|
||||
services.AddSwaggerGen(options =>
|
||||
{
|
||||
var openApiInfo = configuration.GetSection("OpenApiInfo").Get<OpenApiInfo>();
|
||||
|
||||
options.SwaggerDoc("v1", openApiInfo);
|
||||
options.IncludeXmlComments(SwaggerUtilities.ConfigureXmlCommentsFilePath());
|
||||
options.AddSecurityDefinition("Bearer", SwaggerUtilities.ConfigureSecurityDefinition());
|
||||
options.OperationFilter<AuthorizeCheckOperationFilter>();
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the PlayerService with the DI container.
|
||||
/// <br />
|
||||
/// <see href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection"/>
|
||||
/// </summary>
|
||||
/// <param name="services">The IServiceCollection instance.</param>
|
||||
/// <returns>The IServiceCollection for method chaining.</returns>
|
||||
public static IServiceCollection RegisterPlayerService(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IPlayerService, PlayerService>();
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds AutoMapper configuration for Player mappings.
|
||||
/// <br />
|
||||
/// <see href="https://docs.automapper.io/en/latest/Dependency-injection.html#asp-net-core"/>
|
||||
/// </summary>
|
||||
/// <param name="services">The IServiceCollection instance.</param>
|
||||
/// <returns>The IServiceCollection for method chaining.</returns>
|
||||
public static IServiceCollection AddMappings(this IServiceCollection services)
|
||||
{
|
||||
services.AddAutoMapper(config => config.AddProfile<PlayerMappingProfile>());
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers the PlayerRepository service with the DI container.
|
||||
/// <br />
|
||||
/// <see href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection"/>
|
||||
/// </summary>
|
||||
/// <param name="services">The IServiceCollection instance.</param>
|
||||
/// <returns>The IServiceCollection for method chaining.</returns>
|
||||
public static IServiceCollection RegisterPlayerRepository(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IPlayerRepository, PlayerRepository>();
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds rate limiting configuration with IP-based partitioning.
|
||||
/// <br />
|
||||
/// <see href="https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit"/>
|
||||
/// </summary>
|
||||
/// <param name="services">The IServiceCollection instance.</param>
|
||||
/// <param name="configuration">The application configuration instance.</param>
|
||||
/// <returns>The IServiceCollection for method chaining.</returns>
|
||||
public static IServiceCollection AddFixedWindowRateLimiter(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration
|
||||
)
|
||||
{
|
||||
var settings =
|
||||
configuration.GetSection("RateLimiter").Get<RateLimiterConfiguration>()
|
||||
?? new RateLimiterConfiguration();
|
||||
|
||||
services.AddRateLimiter(options =>
|
||||
{
|
||||
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(
|
||||
httpContext =>
|
||||
{
|
||||
var partitionKey = HttpContextUtilities.ExtractIpAddress(httpContext);
|
||||
|
||||
return RateLimitPartition.GetFixedWindowLimiter(
|
||||
partitionKey: partitionKey,
|
||||
factory: _ => new FixedWindowRateLimiterOptions
|
||||
{
|
||||
PermitLimit = settings.PermitLimit,
|
||||
Window = TimeSpan.FromSeconds(settings.WindowSeconds),
|
||||
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
|
||||
QueueLimit = settings.QueueLimit
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using AutoMapper;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Enums;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Models;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Mappings;
|
||||
|
||||
/// <summary>
|
||||
/// Mapping profile for Player.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class defines the mapping configuration between PlayerRequestModel and Player,
|
||||
/// and between Player and PlayerResponseModel.
|
||||
/// </remarks>
|
||||
public class PlayerMappingProfile : Profile
|
||||
{
|
||||
public PlayerMappingProfile()
|
||||
{
|
||||
// PlayerRequestModel → Player
|
||||
CreateMap<PlayerRequestModel, Player>()
|
||||
.ForMember(
|
||||
destination => destination.Position,
|
||||
options =>
|
||||
options.MapFrom(source =>
|
||||
Position.FromAbbr(source.AbbrPosition ?? string.Empty)
|
||||
)
|
||||
)
|
||||
.ForMember(destination => destination.Starting11, options => options.Ignore());
|
||||
|
||||
// Player → PlayerResponseModel
|
||||
CreateMap<Player, PlayerResponseModel>()
|
||||
.ForSourceMember(source => source.Id, options => options.DoNotValidate())
|
||||
.ForMember(
|
||||
destination => destination.FullName,
|
||||
options =>
|
||||
options.MapFrom(source =>
|
||||
$"{source.FirstName} {(string.IsNullOrWhiteSpace(source.MiddleName) ? "" : source.MiddleName + " ")}{source.LastName}".Trim()
|
||||
)
|
||||
)
|
||||
.ForMember(
|
||||
destination => destination.Birth,
|
||||
options =>
|
||||
options.MapFrom(source =>
|
||||
source.DateOfBirth.HasValue
|
||||
? $"{source.DateOfBirth.Value:MMMM d, yyyy}"
|
||||
: null
|
||||
)
|
||||
)
|
||||
.ForMember(
|
||||
destination => destination.Dorsal,
|
||||
options => options.MapFrom(source => source.SquadNumber)
|
||||
)
|
||||
.ForMember(
|
||||
destination => destination.Club,
|
||||
options => options.MapFrom(source => source.Team)
|
||||
)
|
||||
.ForMember(
|
||||
destination => destination.Starting11,
|
||||
options => options.MapFrom(source => source.Starting11 ? "Yes" : "No")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using System.Text.Json;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Middlewares;
|
||||
|
||||
/// <summary>
|
||||
/// Middleware for global exception handling with RFC 7807 Problem Details format.
|
||||
/// </summary>
|
||||
public class ExceptionMiddleware(
|
||||
RequestDelegate next,
|
||||
ILogger<ExceptionMiddleware> logger,
|
||||
IHostEnvironment environment
|
||||
)
|
||||
{
|
||||
private const string ProblemDetailsContentType = "application/problem+json";
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions =
|
||||
new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the middleware to handle exceptions globally.
|
||||
/// </summary>
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
await next(context);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
await HandleExceptionAsync(context, exception);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the exception and returns an RFC 7807 compliant error response.
|
||||
/// </summary>
|
||||
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
|
||||
{
|
||||
var (status, title) = MapExceptionToStatusCode(exception);
|
||||
|
||||
var problemDetails = new ProblemDetails
|
||||
{
|
||||
Type = $"https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{status}",
|
||||
Title = title,
|
||||
Status = status,
|
||||
Detail = GetExceptionDetail(exception),
|
||||
Instance = context.Request.Path
|
||||
};
|
||||
|
||||
// Add trace ID for request correlation
|
||||
problemDetails.Extensions["traceId"] = context.TraceIdentifier;
|
||||
|
||||
// codeql[cs/log-forging] Serilog structured logging automatically escapes control characters
|
||||
logger.LogError(
|
||||
exception,
|
||||
"Unhandled exception occurred. TraceId: {TraceId}, Path: {Path}, StatusCode: {StatusCode}",
|
||||
context.TraceIdentifier,
|
||||
context.Request.Path,
|
||||
status
|
||||
);
|
||||
|
||||
// Only modify response if headers haven't been sent yet
|
||||
if (!context.Response.HasStarted)
|
||||
{
|
||||
context.Response.StatusCode = status;
|
||||
context.Response.ContentType = ProblemDetailsContentType;
|
||||
|
||||
await context.Response.WriteAsync(
|
||||
JsonSerializer.Serialize(problemDetails, JsonOptions)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning(
|
||||
"Unable to write error response for TraceId: {TraceId}. Response has already started.",
|
||||
context.TraceIdentifier
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps exception types to appropriate HTTP status codes and titles.
|
||||
/// </summary>
|
||||
private static (int StatusCode, string Title) MapExceptionToStatusCode(Exception exception)
|
||||
{
|
||||
return exception switch
|
||||
{
|
||||
ValidationException => (StatusCodes.Status422UnprocessableEntity, "Validation Error"),
|
||||
ArgumentException
|
||||
or ArgumentNullException
|
||||
=> (StatusCodes.Status400BadRequest, "Bad Request"),
|
||||
InvalidOperationException => (StatusCodes.Status400BadRequest, "Invalid Operation"),
|
||||
DbUpdateConcurrencyException => (StatusCodes.Status409Conflict, "Concurrency Conflict"),
|
||||
OperationCanceledException => (StatusCodes.Status408RequestTimeout, "Request Timeout"),
|
||||
_ => (StatusCodes.Status500InternalServerError, "Internal Server Error")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the exception detail based on the environment.
|
||||
/// In Development: includes full exception details and stack trace.
|
||||
/// In Production: returns a generic message without sensitive information.
|
||||
/// </summary>
|
||||
private string GetExceptionDetail(Exception exception)
|
||||
{
|
||||
if (environment.IsDevelopment())
|
||||
{
|
||||
return $"{exception.Message}\n\nStack Trace:\n{exception.StackTrace}";
|
||||
}
|
||||
|
||||
return exception switch
|
||||
{
|
||||
ValidationException => exception.Message,
|
||||
ArgumentException => exception.Message,
|
||||
_ => "An unexpected error occurred while processing your request."
|
||||
};
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Migrations
|
||||
{
|
||||
[DbContext(typeof(PlayerDbContext))]
|
||||
[Migration("20260409141647_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.5");
|
||||
|
||||
modelBuilder.Entity("Dotnet.Samples.AspNetCore.WebApi.Models.Player", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AbbrPosition")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("DateOfBirth")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("League")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MiddleName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Position")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SquadNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Starting11")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Team")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SquadNumber")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Players");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Players",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
FirstName = table.Column<string>(type: "TEXT", nullable: true),
|
||||
MiddleName = table.Column<string>(type: "TEXT", nullable: true),
|
||||
LastName = table.Column<string>(type: "TEXT", nullable: true),
|
||||
DateOfBirth = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
SquadNumber = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Position = table.Column<string>(type: "TEXT", nullable: true),
|
||||
AbbrPosition = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Team = table.Column<string>(type: "TEXT", nullable: true),
|
||||
League = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Starting11 = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Players", x => x.Id);
|
||||
}
|
||||
);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Players_SquadNumber",
|
||||
table: "Players",
|
||||
column: "SquadNumber",
|
||||
unique: true
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(name: "Players");
|
||||
}
|
||||
}
|
||||
}
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Migrations
|
||||
{
|
||||
[DbContext(typeof(PlayerDbContext))]
|
||||
[Migration("20260409141707_SeedStarting11")]
|
||||
partial class SeedStarting11
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.5");
|
||||
|
||||
modelBuilder.Entity("Dotnet.Samples.AspNetCore.WebApi.Models.Player", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AbbrPosition")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("DateOfBirth")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("League")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MiddleName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Position")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SquadNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Starting11")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Team")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SquadNumber")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Players");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("01772c59-43f0-5d85-b913-c78e4e281452"),
|
||||
AbbrPosition = "GK",
|
||||
DateOfBirth = new DateTime(1992, 9, 1, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Damián",
|
||||
LastName = "Martínez",
|
||||
League = "Premier League",
|
||||
MiddleName = "Emiliano",
|
||||
Position = "Goalkeeper",
|
||||
SquadNumber = 23,
|
||||
Starting11 = true,
|
||||
Team = "Aston Villa FC"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("da31293b-4c7e-5e0f-a168-469ee29ecbc4"),
|
||||
AbbrPosition = "RB",
|
||||
DateOfBirth = new DateTime(1998, 4, 5, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Nahuel",
|
||||
LastName = "Molina",
|
||||
League = "La Liga",
|
||||
Position = "Right-Back",
|
||||
SquadNumber = 26,
|
||||
Starting11 = true,
|
||||
Team = "Altético Madrid"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("c096c69e-762b-5281-9290-bb9c167a24a0"),
|
||||
AbbrPosition = "CB",
|
||||
DateOfBirth = new DateTime(1998, 4, 26, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Cristian",
|
||||
LastName = "Romero",
|
||||
League = "Premier League",
|
||||
MiddleName = "Gabriel",
|
||||
Position = "Centre-Back",
|
||||
SquadNumber = 13,
|
||||
Starting11 = true,
|
||||
Team = "Tottenham Hotspur"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d5f7dd7a-1dcb-5960-ba27-e34865b63358"),
|
||||
AbbrPosition = "CB",
|
||||
DateOfBirth = new DateTime(1988, 2, 11, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Nicolás",
|
||||
LastName = "Otamendi",
|
||||
League = "Liga Portugal",
|
||||
MiddleName = "Hernán Gonzalo",
|
||||
Position = "Centre-Back",
|
||||
SquadNumber = 19,
|
||||
Starting11 = true,
|
||||
Team = "SL Benfica"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("2f6f90a0-9b9d-5023-96d2-a2aaf03143a6"),
|
||||
AbbrPosition = "LB",
|
||||
DateOfBirth = new DateTime(1992, 8, 30, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Nicolás",
|
||||
LastName = "Tagliafico",
|
||||
League = "Ligue 1",
|
||||
MiddleName = "Alejandro",
|
||||
Position = "Left-Back",
|
||||
SquadNumber = 3,
|
||||
Starting11 = true,
|
||||
Team = "Olympique Lyon"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("b5b46e79-929e-5ed2-949d-0d167109c022"),
|
||||
AbbrPosition = "RW",
|
||||
DateOfBirth = new DateTime(1988, 2, 13, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Ángel",
|
||||
LastName = "Di María",
|
||||
League = "Liga Portugal",
|
||||
MiddleName = "Fabián",
|
||||
Position = "Right Winger",
|
||||
SquadNumber = 11,
|
||||
Starting11 = true,
|
||||
Team = "SL Benfica"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0293b282-1da8-562e-998e-83849b417a42"),
|
||||
AbbrPosition = "CM",
|
||||
DateOfBirth = new DateTime(1994, 5, 23, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Rodrigo",
|
||||
LastName = "de Paul",
|
||||
League = "La Liga",
|
||||
MiddleName = "Javier",
|
||||
Position = "Central Midfield",
|
||||
SquadNumber = 7,
|
||||
Starting11 = true,
|
||||
Team = "Altético Madrid"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d3ba552a-dac3-588a-b961-1ea7224017fd"),
|
||||
AbbrPosition = "CM",
|
||||
DateOfBirth = new DateTime(2001, 1, 16, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Enzo",
|
||||
LastName = "Fernández",
|
||||
League = "Liga Portugal",
|
||||
MiddleName = "Jeremías",
|
||||
Position = "Central Midfield",
|
||||
SquadNumber = 24,
|
||||
Starting11 = true,
|
||||
Team = "SL Benfica"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("9613cae9-16ab-5b54-937e-3135123b9e0d"),
|
||||
AbbrPosition = "CM",
|
||||
DateOfBirth = new DateTime(1998, 12, 23, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Alexis",
|
||||
LastName = "Mac Allister",
|
||||
League = "Premier League",
|
||||
Position = "Central Midfield",
|
||||
SquadNumber = 20,
|
||||
Starting11 = true,
|
||||
Team = "Brighton & Hove Albion"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("acc433bf-d505-51fe-831e-45eb44c4d43c"),
|
||||
AbbrPosition = "RW",
|
||||
DateOfBirth = new DateTime(1987, 6, 23, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Lionel",
|
||||
LastName = "Messi",
|
||||
League = "Ligue 1",
|
||||
MiddleName = "Andrés",
|
||||
Position = "Right Winger",
|
||||
SquadNumber = 10,
|
||||
Starting11 = true,
|
||||
Team = "Paris Saint-Germain"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("38bae91d-8519-55a2-b30a-b9fe38849bfb"),
|
||||
AbbrPosition = "CF",
|
||||
DateOfBirth = new DateTime(2000, 1, 30, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Julián",
|
||||
LastName = "Álvarez",
|
||||
League = "Premier League",
|
||||
Position = "Centre-Forward",
|
||||
SquadNumber = 9,
|
||||
Starting11 = true,
|
||||
Team = "Manchester City"
|
||||
});
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SeedStarting11 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.InsertData(
|
||||
table: "Players",
|
||||
columns: new[]
|
||||
{
|
||||
"Id",
|
||||
"AbbrPosition",
|
||||
"DateOfBirth",
|
||||
"FirstName",
|
||||
"LastName",
|
||||
"League",
|
||||
"MiddleName",
|
||||
"Position",
|
||||
"SquadNumber",
|
||||
"Starting11",
|
||||
"Team"
|
||||
},
|
||||
values: new object[,]
|
||||
{
|
||||
{
|
||||
new Guid("01772c59-43f0-5d85-b913-c78e4e281452"),
|
||||
"GK",
|
||||
new DateTime(1992, 9, 1, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Damián",
|
||||
"Martínez",
|
||||
"Premier League",
|
||||
"Emiliano",
|
||||
"Goalkeeper",
|
||||
23,
|
||||
true,
|
||||
"Aston Villa FC"
|
||||
},
|
||||
{
|
||||
new Guid("0293b282-1da8-562e-998e-83849b417a42"),
|
||||
"CM",
|
||||
new DateTime(1994, 5, 23, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Rodrigo",
|
||||
"de Paul",
|
||||
"La Liga",
|
||||
"Javier",
|
||||
"Central Midfield",
|
||||
7,
|
||||
true,
|
||||
"Altético Madrid"
|
||||
},
|
||||
{
|
||||
new Guid("2f6f90a0-9b9d-5023-96d2-a2aaf03143a6"),
|
||||
"LB",
|
||||
new DateTime(1992, 8, 30, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Nicolás",
|
||||
"Tagliafico",
|
||||
"Ligue 1",
|
||||
"Alejandro",
|
||||
"Left-Back",
|
||||
3,
|
||||
true,
|
||||
"Olympique Lyon"
|
||||
},
|
||||
{
|
||||
new Guid("38bae91d-8519-55a2-b30a-b9fe38849bfb"),
|
||||
"CF",
|
||||
new DateTime(2000, 1, 30, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Julián",
|
||||
"Álvarez",
|
||||
"Premier League",
|
||||
null,
|
||||
"Centre-Forward",
|
||||
9,
|
||||
true,
|
||||
"Manchester City"
|
||||
},
|
||||
{
|
||||
new Guid("9613cae9-16ab-5b54-937e-3135123b9e0d"),
|
||||
"CM",
|
||||
new DateTime(1998, 12, 23, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Alexis",
|
||||
"Mac Allister",
|
||||
"Premier League",
|
||||
null,
|
||||
"Central Midfield",
|
||||
20,
|
||||
true,
|
||||
"Brighton & Hove Albion"
|
||||
},
|
||||
{
|
||||
new Guid("acc433bf-d505-51fe-831e-45eb44c4d43c"),
|
||||
"RW",
|
||||
new DateTime(1987, 6, 23, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Lionel",
|
||||
"Messi",
|
||||
"Ligue 1",
|
||||
"Andrés",
|
||||
"Right Winger",
|
||||
10,
|
||||
true,
|
||||
"Paris Saint-Germain"
|
||||
},
|
||||
{
|
||||
new Guid("b5b46e79-929e-5ed2-949d-0d167109c022"),
|
||||
"RW",
|
||||
new DateTime(1988, 2, 13, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Ángel",
|
||||
"Di María",
|
||||
"Liga Portugal",
|
||||
"Fabián",
|
||||
"Right Winger",
|
||||
11,
|
||||
true,
|
||||
"SL Benfica"
|
||||
},
|
||||
{
|
||||
new Guid("c096c69e-762b-5281-9290-bb9c167a24a0"),
|
||||
"CB",
|
||||
new DateTime(1998, 4, 26, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Cristian",
|
||||
"Romero",
|
||||
"Premier League",
|
||||
"Gabriel",
|
||||
"Centre-Back",
|
||||
13,
|
||||
true,
|
||||
"Tottenham Hotspur"
|
||||
},
|
||||
{
|
||||
new Guid("d3ba552a-dac3-588a-b961-1ea7224017fd"),
|
||||
"CM",
|
||||
new DateTime(2001, 1, 16, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Enzo",
|
||||
"Fernández",
|
||||
"Liga Portugal",
|
||||
"Jeremías",
|
||||
"Central Midfield",
|
||||
24,
|
||||
true,
|
||||
"SL Benfica"
|
||||
},
|
||||
{
|
||||
new Guid("d5f7dd7a-1dcb-5960-ba27-e34865b63358"),
|
||||
"CB",
|
||||
new DateTime(1988, 2, 11, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Nicolás",
|
||||
"Otamendi",
|
||||
"Liga Portugal",
|
||||
"Hernán Gonzalo",
|
||||
"Centre-Back",
|
||||
19,
|
||||
true,
|
||||
"SL Benfica"
|
||||
},
|
||||
{
|
||||
new Guid("da31293b-4c7e-5e0f-a168-469ee29ecbc4"),
|
||||
"RB",
|
||||
new DateTime(1998, 4, 5, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Nahuel",
|
||||
"Molina",
|
||||
"La Liga",
|
||||
null,
|
||||
"Right-Back",
|
||||
26,
|
||||
true,
|
||||
"Altético Madrid"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("01772c59-43f0-5d85-b913-c78e4e281452")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("0293b282-1da8-562e-998e-83849b417a42")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("2f6f90a0-9b9d-5023-96d2-a2aaf03143a6")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("38bae91d-8519-55a2-b30a-b9fe38849bfb")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("9613cae9-16ab-5b54-937e-3135123b9e0d")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("acc433bf-d505-51fe-831e-45eb44c4d43c")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("b5b46e79-929e-5ed2-949d-0d167109c022")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("c096c69e-762b-5281-9290-bb9c167a24a0")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("d3ba552a-dac3-588a-b961-1ea7224017fd")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("d5f7dd7a-1dcb-5960-ba27-e34865b63358")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("da31293b-4c7e-5e0f-a168-469ee29ecbc4")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+429
@@ -0,0 +1,429 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Migrations
|
||||
{
|
||||
[DbContext(typeof(PlayerDbContext))]
|
||||
[Migration("20260409141721_SeedSubstitutes")]
|
||||
partial class SeedSubstitutes
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.5");
|
||||
|
||||
modelBuilder.Entity("Dotnet.Samples.AspNetCore.WebApi.Models.Player", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AbbrPosition")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("DateOfBirth")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("League")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MiddleName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Position")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SquadNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Starting11")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Team")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SquadNumber")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Players");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("01772c59-43f0-5d85-b913-c78e4e281452"),
|
||||
AbbrPosition = "GK",
|
||||
DateOfBirth = new DateTime(1992, 9, 1, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Damián",
|
||||
LastName = "Martínez",
|
||||
League = "Premier League",
|
||||
MiddleName = "Emiliano",
|
||||
Position = "Goalkeeper",
|
||||
SquadNumber = 23,
|
||||
Starting11 = true,
|
||||
Team = "Aston Villa FC"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("da31293b-4c7e-5e0f-a168-469ee29ecbc4"),
|
||||
AbbrPosition = "RB",
|
||||
DateOfBirth = new DateTime(1998, 4, 5, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Nahuel",
|
||||
LastName = "Molina",
|
||||
League = "La Liga",
|
||||
Position = "Right-Back",
|
||||
SquadNumber = 26,
|
||||
Starting11 = true,
|
||||
Team = "Altético Madrid"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("c096c69e-762b-5281-9290-bb9c167a24a0"),
|
||||
AbbrPosition = "CB",
|
||||
DateOfBirth = new DateTime(1998, 4, 26, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Cristian",
|
||||
LastName = "Romero",
|
||||
League = "Premier League",
|
||||
MiddleName = "Gabriel",
|
||||
Position = "Centre-Back",
|
||||
SquadNumber = 13,
|
||||
Starting11 = true,
|
||||
Team = "Tottenham Hotspur"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d5f7dd7a-1dcb-5960-ba27-e34865b63358"),
|
||||
AbbrPosition = "CB",
|
||||
DateOfBirth = new DateTime(1988, 2, 11, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Nicolás",
|
||||
LastName = "Otamendi",
|
||||
League = "Liga Portugal",
|
||||
MiddleName = "Hernán Gonzalo",
|
||||
Position = "Centre-Back",
|
||||
SquadNumber = 19,
|
||||
Starting11 = true,
|
||||
Team = "SL Benfica"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("2f6f90a0-9b9d-5023-96d2-a2aaf03143a6"),
|
||||
AbbrPosition = "LB",
|
||||
DateOfBirth = new DateTime(1992, 8, 30, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Nicolás",
|
||||
LastName = "Tagliafico",
|
||||
League = "Ligue 1",
|
||||
MiddleName = "Alejandro",
|
||||
Position = "Left-Back",
|
||||
SquadNumber = 3,
|
||||
Starting11 = true,
|
||||
Team = "Olympique Lyon"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("b5b46e79-929e-5ed2-949d-0d167109c022"),
|
||||
AbbrPosition = "RW",
|
||||
DateOfBirth = new DateTime(1988, 2, 13, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Ángel",
|
||||
LastName = "Di María",
|
||||
League = "Liga Portugal",
|
||||
MiddleName = "Fabián",
|
||||
Position = "Right Winger",
|
||||
SquadNumber = 11,
|
||||
Starting11 = true,
|
||||
Team = "SL Benfica"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0293b282-1da8-562e-998e-83849b417a42"),
|
||||
AbbrPosition = "CM",
|
||||
DateOfBirth = new DateTime(1994, 5, 23, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Rodrigo",
|
||||
LastName = "de Paul",
|
||||
League = "La Liga",
|
||||
MiddleName = "Javier",
|
||||
Position = "Central Midfield",
|
||||
SquadNumber = 7,
|
||||
Starting11 = true,
|
||||
Team = "Altético Madrid"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d3ba552a-dac3-588a-b961-1ea7224017fd"),
|
||||
AbbrPosition = "CM",
|
||||
DateOfBirth = new DateTime(2001, 1, 16, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Enzo",
|
||||
LastName = "Fernández",
|
||||
League = "Liga Portugal",
|
||||
MiddleName = "Jeremías",
|
||||
Position = "Central Midfield",
|
||||
SquadNumber = 24,
|
||||
Starting11 = true,
|
||||
Team = "SL Benfica"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("9613cae9-16ab-5b54-937e-3135123b9e0d"),
|
||||
AbbrPosition = "CM",
|
||||
DateOfBirth = new DateTime(1998, 12, 23, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Alexis",
|
||||
LastName = "Mac Allister",
|
||||
League = "Premier League",
|
||||
Position = "Central Midfield",
|
||||
SquadNumber = 20,
|
||||
Starting11 = true,
|
||||
Team = "Brighton & Hove Albion"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("acc433bf-d505-51fe-831e-45eb44c4d43c"),
|
||||
AbbrPosition = "RW",
|
||||
DateOfBirth = new DateTime(1987, 6, 23, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Lionel",
|
||||
LastName = "Messi",
|
||||
League = "Ligue 1",
|
||||
MiddleName = "Andrés",
|
||||
Position = "Right Winger",
|
||||
SquadNumber = 10,
|
||||
Starting11 = true,
|
||||
Team = "Paris Saint-Germain"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("38bae91d-8519-55a2-b30a-b9fe38849bfb"),
|
||||
AbbrPosition = "CF",
|
||||
DateOfBirth = new DateTime(2000, 1, 30, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Julián",
|
||||
LastName = "Álvarez",
|
||||
League = "Premier League",
|
||||
Position = "Centre-Forward",
|
||||
SquadNumber = 9,
|
||||
Starting11 = true,
|
||||
Team = "Manchester City"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("5a9cd988-95e6-54c1-bc34-9aa08acca8d0"),
|
||||
AbbrPosition = "GK",
|
||||
DateOfBirth = new DateTime(1986, 10, 16, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Franco",
|
||||
LastName = "Armani",
|
||||
League = "Copa de la Liga",
|
||||
MiddleName = "Daniel",
|
||||
Position = "Goalkeeper",
|
||||
SquadNumber = 1,
|
||||
Starting11 = false,
|
||||
Team = "River Plate"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("c62f2ac1-41e8-5d34-b073-2ba0913d0e31"),
|
||||
AbbrPosition = "GK",
|
||||
DateOfBirth = new DateTime(1992, 5, 20, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Gerónimo",
|
||||
LastName = "Rulli",
|
||||
League = "Eredivisie",
|
||||
Position = "Goalkeeper",
|
||||
SquadNumber = 12,
|
||||
Starting11 = false,
|
||||
Team = "Ajax Amsterdam"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("5fdb10e8-38c0-5084-9a3f-b369a960b9c2"),
|
||||
AbbrPosition = "RB",
|
||||
DateOfBirth = new DateTime(1998, 1, 12, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Juan",
|
||||
LastName = "Foyth",
|
||||
League = "La Liga",
|
||||
MiddleName = "Marcos",
|
||||
Position = "Right-Back",
|
||||
SquadNumber = 2,
|
||||
Starting11 = false,
|
||||
Team = "Villarreal"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("bbd441f7-fcfb-5834-8468-2a9004b64c8c"),
|
||||
AbbrPosition = "RB",
|
||||
DateOfBirth = new DateTime(1997, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Gonzalo",
|
||||
LastName = "Montiel",
|
||||
League = "Premier League",
|
||||
MiddleName = "Ariel",
|
||||
Position = "Right-Back",
|
||||
SquadNumber = 4,
|
||||
Starting11 = false,
|
||||
Team = "Nottingham Forest"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d8bfea25-f189-5d5e-b3a5-ed89329b9f7c"),
|
||||
AbbrPosition = "CB",
|
||||
DateOfBirth = new DateTime(1991, 6, 27, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Germán",
|
||||
LastName = "Pezzella",
|
||||
League = "La Liga",
|
||||
MiddleName = "Alejo",
|
||||
Position = "Centre-Back",
|
||||
SquadNumber = 6,
|
||||
Starting11 = false,
|
||||
Team = "Real Betis Balompié"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("dca343a8-12e5-53d6-89a8-916b120a5ee4"),
|
||||
AbbrPosition = "LB",
|
||||
DateOfBirth = new DateTime(1991, 10, 28, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Marcos",
|
||||
LastName = "Acuña",
|
||||
League = "La Liga",
|
||||
MiddleName = "Javier",
|
||||
Position = "Left-Back",
|
||||
SquadNumber = 8,
|
||||
Starting11 = false,
|
||||
Team = "Sevilla FC"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("98306555-a466-5d18-804e-dc82175e697b"),
|
||||
AbbrPosition = "CB",
|
||||
DateOfBirth = new DateTime(1998, 1, 18, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Lisandro",
|
||||
LastName = "Martínez",
|
||||
League = "Premier League",
|
||||
Position = "Centre-Back",
|
||||
SquadNumber = 25,
|
||||
Starting11 = false,
|
||||
Team = "Manchester United"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("9d140400-196f-55d8-86e1-e0b96a375c83"),
|
||||
AbbrPosition = "DM",
|
||||
DateOfBirth = new DateTime(1994, 6, 29, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Leandro",
|
||||
LastName = "Paredes",
|
||||
League = "Serie A",
|
||||
MiddleName = "Daniel",
|
||||
Position = "Defensive Midfield",
|
||||
SquadNumber = 5,
|
||||
Starting11 = false,
|
||||
Team = "AS Roma"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d3b0e8e8-2c34-531a-b608-b24fed0ef986"),
|
||||
AbbrPosition = "CM",
|
||||
DateOfBirth = new DateTime(1998, 10, 5, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Exequiel",
|
||||
LastName = "Palacios",
|
||||
League = "Bundesliga",
|
||||
MiddleName = "Alejandro",
|
||||
Position = "Central Midfield",
|
||||
SquadNumber = 14,
|
||||
Starting11 = false,
|
||||
Team = "Bayer 04 Leverkusen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7cc8d527-56a2-58bd-9528-2618fc139d30"),
|
||||
AbbrPosition = "LW",
|
||||
DateOfBirth = new DateTime(1988, 2, 15, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Alejandro",
|
||||
LastName = "Gómez",
|
||||
League = "Serie A",
|
||||
MiddleName = "Darío",
|
||||
Position = "Left Winger",
|
||||
SquadNumber = 17,
|
||||
Starting11 = false,
|
||||
Team = "AC Monza"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("191c82af-0c51-526a-b903-c3600b61b506"),
|
||||
AbbrPosition = "DM",
|
||||
DateOfBirth = new DateTime(1994, 4, 12, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Guido",
|
||||
LastName = "Rodríguez",
|
||||
League = "La Liga",
|
||||
Position = "Defensive Midfield",
|
||||
SquadNumber = 18,
|
||||
Starting11 = false,
|
||||
Team = "Real Betis Balompié"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("b1306b7b-a3a4-5f7c-90fd-dd5bdbed57ba"),
|
||||
AbbrPosition = "RW",
|
||||
DateOfBirth = new DateTime(1995, 3, 9, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Ángel",
|
||||
LastName = "Correa",
|
||||
League = "La Liga",
|
||||
MiddleName = "Martín",
|
||||
Position = "Right Winger",
|
||||
SquadNumber = 15,
|
||||
Starting11 = false,
|
||||
Team = "Atlético Madrid"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("ecec27e8-487b-5622-b116-0855020477ed"),
|
||||
AbbrPosition = "AM",
|
||||
DateOfBirth = new DateTime(2001, 4, 26, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Thiago",
|
||||
LastName = "Almada",
|
||||
League = "Major League Soccer",
|
||||
MiddleName = "Ezequiel",
|
||||
Position = "Attacking Midfield",
|
||||
SquadNumber = 16,
|
||||
Starting11 = false,
|
||||
Team = "Atlanta United FC"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7941cd7c-4df1-5952-97e8-1e7f5d08e8aa"),
|
||||
AbbrPosition = "SS",
|
||||
DateOfBirth = new DateTime(1993, 11, 15, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Paulo",
|
||||
LastName = "Dybala",
|
||||
League = "Serie A",
|
||||
MiddleName = "Exequiel",
|
||||
Position = "Second Striker",
|
||||
SquadNumber = 21,
|
||||
Starting11 = false,
|
||||
Team = "AS Roma"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("79c96f29-c59f-5f98-96b8-3a5946246624"),
|
||||
AbbrPosition = "CF",
|
||||
DateOfBirth = new DateTime(1997, 8, 22, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Lautaro",
|
||||
LastName = "Martínez",
|
||||
League = "Serie A",
|
||||
MiddleName = "Javier",
|
||||
Position = "Centre-Forward",
|
||||
SquadNumber = 22,
|
||||
Starting11 = false,
|
||||
Team = "Inter Milan"
|
||||
});
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SeedSubstitutes : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.InsertData(
|
||||
table: "Players",
|
||||
columns: new[]
|
||||
{
|
||||
"Id",
|
||||
"AbbrPosition",
|
||||
"DateOfBirth",
|
||||
"FirstName",
|
||||
"LastName",
|
||||
"League",
|
||||
"MiddleName",
|
||||
"Position",
|
||||
"SquadNumber",
|
||||
"Starting11",
|
||||
"Team"
|
||||
},
|
||||
values: new object[,]
|
||||
{
|
||||
{
|
||||
new Guid("191c82af-0c51-526a-b903-c3600b61b506"),
|
||||
"DM",
|
||||
new DateTime(1994, 4, 12, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Guido",
|
||||
"Rodríguez",
|
||||
"La Liga",
|
||||
null,
|
||||
"Defensive Midfield",
|
||||
18,
|
||||
false,
|
||||
"Real Betis Balompié"
|
||||
},
|
||||
{
|
||||
new Guid("5a9cd988-95e6-54c1-bc34-9aa08acca8d0"),
|
||||
"GK",
|
||||
new DateTime(1986, 10, 16, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Franco",
|
||||
"Armani",
|
||||
"Copa de la Liga",
|
||||
"Daniel",
|
||||
"Goalkeeper",
|
||||
1,
|
||||
false,
|
||||
"River Plate"
|
||||
},
|
||||
{
|
||||
new Guid("5fdb10e8-38c0-5084-9a3f-b369a960b9c2"),
|
||||
"RB",
|
||||
new DateTime(1998, 1, 12, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Juan",
|
||||
"Foyth",
|
||||
"La Liga",
|
||||
"Marcos",
|
||||
"Right-Back",
|
||||
2,
|
||||
false,
|
||||
"Villarreal"
|
||||
},
|
||||
{
|
||||
new Guid("7941cd7c-4df1-5952-97e8-1e7f5d08e8aa"),
|
||||
"SS",
|
||||
new DateTime(1993, 11, 15, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Paulo",
|
||||
"Dybala",
|
||||
"Serie A",
|
||||
"Exequiel",
|
||||
"Second Striker",
|
||||
21,
|
||||
false,
|
||||
"AS Roma"
|
||||
},
|
||||
{
|
||||
new Guid("79c96f29-c59f-5f98-96b8-3a5946246624"),
|
||||
"CF",
|
||||
new DateTime(1997, 8, 22, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Lautaro",
|
||||
"Martínez",
|
||||
"Serie A",
|
||||
"Javier",
|
||||
"Centre-Forward",
|
||||
22,
|
||||
false,
|
||||
"Inter Milan"
|
||||
},
|
||||
{
|
||||
new Guid("7cc8d527-56a2-58bd-9528-2618fc139d30"),
|
||||
"LW",
|
||||
new DateTime(1988, 2, 15, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Alejandro",
|
||||
"Gómez",
|
||||
"Serie A",
|
||||
"Darío",
|
||||
"Left Winger",
|
||||
17,
|
||||
false,
|
||||
"AC Monza"
|
||||
},
|
||||
{
|
||||
new Guid("98306555-a466-5d18-804e-dc82175e697b"),
|
||||
"CB",
|
||||
new DateTime(1998, 1, 18, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Lisandro",
|
||||
"Martínez",
|
||||
"Premier League",
|
||||
null,
|
||||
"Centre-Back",
|
||||
25,
|
||||
false,
|
||||
"Manchester United"
|
||||
},
|
||||
{
|
||||
new Guid("9d140400-196f-55d8-86e1-e0b96a375c83"),
|
||||
"DM",
|
||||
new DateTime(1994, 6, 29, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Leandro",
|
||||
"Paredes",
|
||||
"Serie A",
|
||||
"Daniel",
|
||||
"Defensive Midfield",
|
||||
5,
|
||||
false,
|
||||
"AS Roma"
|
||||
},
|
||||
{
|
||||
new Guid("b1306b7b-a3a4-5f7c-90fd-dd5bdbed57ba"),
|
||||
"RW",
|
||||
new DateTime(1995, 3, 9, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Ángel",
|
||||
"Correa",
|
||||
"La Liga",
|
||||
"Martín",
|
||||
"Right Winger",
|
||||
15,
|
||||
false,
|
||||
"Atlético Madrid"
|
||||
},
|
||||
{
|
||||
new Guid("bbd441f7-fcfb-5834-8468-2a9004b64c8c"),
|
||||
"RB",
|
||||
new DateTime(1997, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Gonzalo",
|
||||
"Montiel",
|
||||
"Premier League",
|
||||
"Ariel",
|
||||
"Right-Back",
|
||||
4,
|
||||
false,
|
||||
"Nottingham Forest"
|
||||
},
|
||||
{
|
||||
new Guid("c62f2ac1-41e8-5d34-b073-2ba0913d0e31"),
|
||||
"GK",
|
||||
new DateTime(1992, 5, 20, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Gerónimo",
|
||||
"Rulli",
|
||||
"Eredivisie",
|
||||
null,
|
||||
"Goalkeeper",
|
||||
12,
|
||||
false,
|
||||
"Ajax Amsterdam"
|
||||
},
|
||||
{
|
||||
new Guid("d3b0e8e8-2c34-531a-b608-b24fed0ef986"),
|
||||
"CM",
|
||||
new DateTime(1998, 10, 5, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Exequiel",
|
||||
"Palacios",
|
||||
"Bundesliga",
|
||||
"Alejandro",
|
||||
"Central Midfield",
|
||||
14,
|
||||
false,
|
||||
"Bayer 04 Leverkusen"
|
||||
},
|
||||
{
|
||||
new Guid("d8bfea25-f189-5d5e-b3a5-ed89329b9f7c"),
|
||||
"CB",
|
||||
new DateTime(1991, 6, 27, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Germán",
|
||||
"Pezzella",
|
||||
"La Liga",
|
||||
"Alejo",
|
||||
"Centre-Back",
|
||||
6,
|
||||
false,
|
||||
"Real Betis Balompié"
|
||||
},
|
||||
{
|
||||
new Guid("dca343a8-12e5-53d6-89a8-916b120a5ee4"),
|
||||
"LB",
|
||||
new DateTime(1991, 10, 28, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Marcos",
|
||||
"Acuña",
|
||||
"La Liga",
|
||||
"Javier",
|
||||
"Left-Back",
|
||||
8,
|
||||
false,
|
||||
"Sevilla FC"
|
||||
},
|
||||
{
|
||||
new Guid("ecec27e8-487b-5622-b116-0855020477ed"),
|
||||
"AM",
|
||||
new DateTime(2001, 4, 26, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Thiago",
|
||||
"Almada",
|
||||
"Major League Soccer",
|
||||
"Ezequiel",
|
||||
"Attacking Midfield",
|
||||
16,
|
||||
false,
|
||||
"Atlanta United FC"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("191c82af-0c51-526a-b903-c3600b61b506")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("5a9cd988-95e6-54c1-bc34-9aa08acca8d0")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("5fdb10e8-38c0-5084-9a3f-b369a960b9c2")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("7941cd7c-4df1-5952-97e8-1e7f5d08e8aa")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("79c96f29-c59f-5f98-96b8-3a5946246624")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("7cc8d527-56a2-58bd-9528-2618fc139d30")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("98306555-a466-5d18-804e-dc82175e697b")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("9d140400-196f-55d8-86e1-e0b96a375c83")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("b1306b7b-a3a4-5f7c-90fd-dd5bdbed57ba")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("bbd441f7-fcfb-5834-8468-2a9004b64c8c")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("c62f2ac1-41e8-5d34-b073-2ba0913d0e31")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("d3b0e8e8-2c34-531a-b608-b24fed0ef986")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("d8bfea25-f189-5d5e-b3a5-ed89329b9f7c")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("dca343a8-12e5-53d6-89a8-916b120a5ee4")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("ecec27e8-487b-5622-b116-0855020477ed")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+68
@@ -0,0 +1,68 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Migrations.Npgsql
|
||||
{
|
||||
[DbContext(typeof(PlayerDbContext))]
|
||||
[Migration("20260409151000_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.7");
|
||||
|
||||
modelBuilder.Entity("Dotnet.Samples.AspNetCore.WebApi.Models.Player", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AbbrPosition")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime?>("DateOfBirth")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("League")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MiddleName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Position")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("SquadNumber")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("Starting11")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Team")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SquadNumber")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Players");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Migrations.Npgsql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Players",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
FirstName = table.Column<string>(type: "text", nullable: true),
|
||||
MiddleName = table.Column<string>(type: "text", nullable: true),
|
||||
LastName = table.Column<string>(type: "text", nullable: true),
|
||||
DateOfBirth = table.Column<DateTime>(
|
||||
type: "timestamp with time zone",
|
||||
nullable: true
|
||||
),
|
||||
SquadNumber = table.Column<int>(type: "integer", nullable: false),
|
||||
Position = table.Column<string>(type: "text", nullable: true),
|
||||
AbbrPosition = table.Column<string>(type: "text", nullable: true),
|
||||
Team = table.Column<string>(type: "text", nullable: true),
|
||||
League = table.Column<string>(type: "text", nullable: true),
|
||||
Starting11 = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Players", x => x.Id);
|
||||
}
|
||||
);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Players_SquadNumber",
|
||||
table: "Players",
|
||||
column: "SquadNumber",
|
||||
unique: true
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(name: "Players");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+68
@@ -0,0 +1,68 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Migrations.Npgsql
|
||||
{
|
||||
[DbContext(typeof(PlayerDbContext))]
|
||||
[Migration("20260409151100_SeedStarting11")]
|
||||
partial class SeedStarting11
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.7");
|
||||
|
||||
modelBuilder.Entity("Dotnet.Samples.AspNetCore.WebApi.Models.Player", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AbbrPosition")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime?>("DateOfBirth")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("League")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MiddleName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Position")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("SquadNumber")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("Starting11")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Team")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SquadNumber")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Players");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Migrations.Npgsql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SeedStarting11 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.InsertData(
|
||||
table: "Players",
|
||||
columns: new[]
|
||||
{
|
||||
"Id",
|
||||
"AbbrPosition",
|
||||
"DateOfBirth",
|
||||
"FirstName",
|
||||
"LastName",
|
||||
"League",
|
||||
"MiddleName",
|
||||
"Position",
|
||||
"SquadNumber",
|
||||
"Starting11",
|
||||
"Team"
|
||||
},
|
||||
values: new object[,]
|
||||
{
|
||||
{
|
||||
new Guid("01772c59-43f0-5d85-b913-c78e4e281452"),
|
||||
"GK",
|
||||
new DateTime(1992, 9, 1, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Damián",
|
||||
"Martínez",
|
||||
"Premier League",
|
||||
"Emiliano",
|
||||
"Goalkeeper",
|
||||
23,
|
||||
true,
|
||||
"Aston Villa FC"
|
||||
},
|
||||
{
|
||||
new Guid("0293b282-1da8-562e-998e-83849b417a42"),
|
||||
"CM",
|
||||
new DateTime(1994, 5, 23, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Rodrigo",
|
||||
"de Paul",
|
||||
"La Liga",
|
||||
"Javier",
|
||||
"Central Midfield",
|
||||
7,
|
||||
true,
|
||||
"Altético Madrid"
|
||||
},
|
||||
{
|
||||
new Guid("2f6f90a0-9b9d-5023-96d2-a2aaf03143a6"),
|
||||
"LB",
|
||||
new DateTime(1992, 8, 30, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Nicolás",
|
||||
"Tagliafico",
|
||||
"Ligue 1",
|
||||
"Alejandro",
|
||||
"Left-Back",
|
||||
3,
|
||||
true,
|
||||
"Olympique Lyon"
|
||||
},
|
||||
{
|
||||
new Guid("38bae91d-8519-55a2-b30a-b9fe38849bfb"),
|
||||
"CF",
|
||||
new DateTime(2000, 1, 30, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Julián",
|
||||
"Álvarez",
|
||||
"Premier League",
|
||||
null,
|
||||
"Centre-Forward",
|
||||
9,
|
||||
true,
|
||||
"Manchester City"
|
||||
},
|
||||
{
|
||||
new Guid("9613cae9-16ab-5b54-937e-3135123b9e0d"),
|
||||
"CM",
|
||||
new DateTime(1998, 12, 23, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Alexis",
|
||||
"Mac Allister",
|
||||
"Premier League",
|
||||
null,
|
||||
"Central Midfield",
|
||||
20,
|
||||
true,
|
||||
"Brighton & Hove Albion"
|
||||
},
|
||||
{
|
||||
new Guid("acc433bf-d505-51fe-831e-45eb44c4d43c"),
|
||||
"RW",
|
||||
new DateTime(1987, 6, 23, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Lionel",
|
||||
"Messi",
|
||||
"Ligue 1",
|
||||
"Andrés",
|
||||
"Right Winger",
|
||||
10,
|
||||
true,
|
||||
"Paris Saint-Germain"
|
||||
},
|
||||
{
|
||||
new Guid("b5b46e79-929e-5ed2-949d-0d167109c022"),
|
||||
"RW",
|
||||
new DateTime(1988, 2, 13, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Ángel",
|
||||
"Di María",
|
||||
"Liga Portugal",
|
||||
"Fabián",
|
||||
"Right Winger",
|
||||
11,
|
||||
true,
|
||||
"SL Benfica"
|
||||
},
|
||||
{
|
||||
new Guid("c096c69e-762b-5281-9290-bb9c167a24a0"),
|
||||
"CB",
|
||||
new DateTime(1998, 4, 26, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Cristian",
|
||||
"Romero",
|
||||
"Premier League",
|
||||
"Gabriel",
|
||||
"Centre-Back",
|
||||
13,
|
||||
true,
|
||||
"Tottenham Hotspur"
|
||||
},
|
||||
{
|
||||
new Guid("d3ba552a-dac3-588a-b961-1ea7224017fd"),
|
||||
"CM",
|
||||
new DateTime(2001, 1, 16, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Enzo",
|
||||
"Fernández",
|
||||
"Liga Portugal",
|
||||
"Jeremías",
|
||||
"Central Midfield",
|
||||
24,
|
||||
true,
|
||||
"SL Benfica"
|
||||
},
|
||||
{
|
||||
new Guid("d5f7dd7a-1dcb-5960-ba27-e34865b63358"),
|
||||
"CB",
|
||||
new DateTime(1988, 2, 11, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Nicolás",
|
||||
"Otamendi",
|
||||
"Liga Portugal",
|
||||
"Hernán Gonzalo",
|
||||
"Centre-Back",
|
||||
19,
|
||||
true,
|
||||
"SL Benfica"
|
||||
},
|
||||
{
|
||||
new Guid("da31293b-4c7e-5e0f-a168-469ee29ecbc4"),
|
||||
"RB",
|
||||
new DateTime(1998, 4, 5, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Nahuel",
|
||||
"Molina",
|
||||
"La Liga",
|
||||
null,
|
||||
"Right-Back",
|
||||
26,
|
||||
true,
|
||||
"Altético Madrid"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("01772c59-43f0-5d85-b913-c78e4e281452")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("0293b282-1da8-562e-998e-83849b417a42")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("2f6f90a0-9b9d-5023-96d2-a2aaf03143a6")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("38bae91d-8519-55a2-b30a-b9fe38849bfb")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("9613cae9-16ab-5b54-937e-3135123b9e0d")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("acc433bf-d505-51fe-831e-45eb44c4d43c")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("b5b46e79-929e-5ed2-949d-0d167109c022")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("c096c69e-762b-5281-9290-bb9c167a24a0")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("d3ba552a-dac3-588a-b961-1ea7224017fd")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("d5f7dd7a-1dcb-5960-ba27-e34865b63358")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("da31293b-4c7e-5e0f-a168-469ee29ecbc4")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+68
@@ -0,0 +1,68 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Migrations.Npgsql
|
||||
{
|
||||
[DbContext(typeof(PlayerDbContext))]
|
||||
[Migration("20260409151200_SeedSubstitutes")]
|
||||
partial class SeedSubstitutes
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.7");
|
||||
|
||||
modelBuilder.Entity("Dotnet.Samples.AspNetCore.WebApi.Models.Player", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AbbrPosition")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime?>("DateOfBirth")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("League")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MiddleName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Position")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("SquadNumber")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("Starting11")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Team")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SquadNumber")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Players");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Migrations.Npgsql
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SeedSubstitutes : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.InsertData(
|
||||
table: "Players",
|
||||
columns: new[]
|
||||
{
|
||||
"Id",
|
||||
"AbbrPosition",
|
||||
"DateOfBirth",
|
||||
"FirstName",
|
||||
"LastName",
|
||||
"League",
|
||||
"MiddleName",
|
||||
"Position",
|
||||
"SquadNumber",
|
||||
"Starting11",
|
||||
"Team"
|
||||
},
|
||||
values: new object[,]
|
||||
{
|
||||
{
|
||||
new Guid("191c82af-0c51-526a-b903-c3600b61b506"),
|
||||
"DM",
|
||||
new DateTime(1994, 4, 12, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Guido",
|
||||
"Rodríguez",
|
||||
"La Liga",
|
||||
null,
|
||||
"Defensive Midfield",
|
||||
18,
|
||||
false,
|
||||
"Real Betis Balompié"
|
||||
},
|
||||
{
|
||||
new Guid("5a9cd988-95e6-54c1-bc34-9aa08acca8d0"),
|
||||
"GK",
|
||||
new DateTime(1986, 10, 16, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Franco",
|
||||
"Armani",
|
||||
"Copa de la Liga",
|
||||
"Daniel",
|
||||
"Goalkeeper",
|
||||
1,
|
||||
false,
|
||||
"River Plate"
|
||||
},
|
||||
{
|
||||
new Guid("5fdb10e8-38c0-5084-9a3f-b369a960b9c2"),
|
||||
"RB",
|
||||
new DateTime(1998, 1, 12, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Juan",
|
||||
"Foyth",
|
||||
"La Liga",
|
||||
"Marcos",
|
||||
"Right-Back",
|
||||
2,
|
||||
false,
|
||||
"Villarreal"
|
||||
},
|
||||
{
|
||||
new Guid("7941cd7c-4df1-5952-97e8-1e7f5d08e8aa"),
|
||||
"SS",
|
||||
new DateTime(1993, 11, 15, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Paulo",
|
||||
"Dybala",
|
||||
"Serie A",
|
||||
"Exequiel",
|
||||
"Second Striker",
|
||||
21,
|
||||
false,
|
||||
"AS Roma"
|
||||
},
|
||||
{
|
||||
new Guid("79c96f29-c59f-5f98-96b8-3a5946246624"),
|
||||
"CF",
|
||||
new DateTime(1997, 8, 22, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Lautaro",
|
||||
"Martínez",
|
||||
"Serie A",
|
||||
"Javier",
|
||||
"Centre-Forward",
|
||||
22,
|
||||
false,
|
||||
"Inter Milan"
|
||||
},
|
||||
{
|
||||
new Guid("7cc8d527-56a2-58bd-9528-2618fc139d30"),
|
||||
"LW",
|
||||
new DateTime(1988, 2, 15, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Alejandro",
|
||||
"Gómez",
|
||||
"Serie A",
|
||||
"Darío",
|
||||
"Left Winger",
|
||||
17,
|
||||
false,
|
||||
"AC Monza"
|
||||
},
|
||||
{
|
||||
new Guid("98306555-a466-5d18-804e-dc82175e697b"),
|
||||
"CB",
|
||||
new DateTime(1998, 1, 18, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Lisandro",
|
||||
"Martínez",
|
||||
"Premier League",
|
||||
null,
|
||||
"Centre-Back",
|
||||
25,
|
||||
false,
|
||||
"Manchester United"
|
||||
},
|
||||
{
|
||||
new Guid("9d140400-196f-55d8-86e1-e0b96a375c83"),
|
||||
"DM",
|
||||
new DateTime(1994, 6, 29, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Leandro",
|
||||
"Paredes",
|
||||
"Serie A",
|
||||
"Daniel",
|
||||
"Defensive Midfield",
|
||||
5,
|
||||
false,
|
||||
"AS Roma"
|
||||
},
|
||||
{
|
||||
new Guid("b1306b7b-a3a4-5f7c-90fd-dd5bdbed57ba"),
|
||||
"RW",
|
||||
new DateTime(1995, 3, 9, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Ángel",
|
||||
"Correa",
|
||||
"La Liga",
|
||||
"Martín",
|
||||
"Right Winger",
|
||||
15,
|
||||
false,
|
||||
"Atlético Madrid"
|
||||
},
|
||||
{
|
||||
new Guid("bbd441f7-fcfb-5834-8468-2a9004b64c8c"),
|
||||
"RB",
|
||||
new DateTime(1997, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Gonzalo",
|
||||
"Montiel",
|
||||
"Premier League",
|
||||
"Ariel",
|
||||
"Right-Back",
|
||||
4,
|
||||
false,
|
||||
"Nottingham Forest"
|
||||
},
|
||||
{
|
||||
new Guid("c62f2ac1-41e8-5d34-b073-2ba0913d0e31"),
|
||||
"GK",
|
||||
new DateTime(1992, 5, 20, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Gerónimo",
|
||||
"Rulli",
|
||||
"Eredivisie",
|
||||
null,
|
||||
"Goalkeeper",
|
||||
12,
|
||||
false,
|
||||
"Ajax Amsterdam"
|
||||
},
|
||||
{
|
||||
new Guid("d3b0e8e8-2c34-531a-b608-b24fed0ef986"),
|
||||
"CM",
|
||||
new DateTime(1998, 10, 5, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Exequiel",
|
||||
"Palacios",
|
||||
"Bundesliga",
|
||||
"Alejandro",
|
||||
"Central Midfield",
|
||||
14,
|
||||
false,
|
||||
"Bayer 04 Leverkusen"
|
||||
},
|
||||
{
|
||||
new Guid("d8bfea25-f189-5d5e-b3a5-ed89329b9f7c"),
|
||||
"CB",
|
||||
new DateTime(1991, 6, 27, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Germán",
|
||||
"Pezzella",
|
||||
"La Liga",
|
||||
"Alejo",
|
||||
"Centre-Back",
|
||||
6,
|
||||
false,
|
||||
"Real Betis Balompié"
|
||||
},
|
||||
{
|
||||
new Guid("dca343a8-12e5-53d6-89a8-916b120a5ee4"),
|
||||
"LB",
|
||||
new DateTime(1991, 10, 28, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Marcos",
|
||||
"Acuña",
|
||||
"La Liga",
|
||||
"Javier",
|
||||
"Left-Back",
|
||||
8,
|
||||
false,
|
||||
"Sevilla FC"
|
||||
},
|
||||
{
|
||||
new Guid("ecec27e8-487b-5622-b116-0855020477ed"),
|
||||
"AM",
|
||||
new DateTime(2001, 4, 26, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
"Thiago",
|
||||
"Almada",
|
||||
"Major League Soccer",
|
||||
"Ezequiel",
|
||||
"Attacking Midfield",
|
||||
16,
|
||||
false,
|
||||
"Atlanta United FC"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("191c82af-0c51-526a-b903-c3600b61b506")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("5a9cd988-95e6-54c1-bc34-9aa08acca8d0")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("5fdb10e8-38c0-5084-9a3f-b369a960b9c2")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("7941cd7c-4df1-5952-97e8-1e7f5d08e8aa")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("79c96f29-c59f-5f98-96b8-3a5946246624")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("7cc8d527-56a2-58bd-9528-2618fc139d30")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("98306555-a466-5d18-804e-dc82175e697b")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("9d140400-196f-55d8-86e1-e0b96a375c83")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("b1306b7b-a3a4-5f7c-90fd-dd5bdbed57ba")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("bbd441f7-fcfb-5834-8468-2a9004b64c8c")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("c62f2ac1-41e8-5d34-b073-2ba0913d0e31")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("d3b0e8e8-2c34-531a-b608-b24fed0ef986")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("d8bfea25-f189-5d5e-b3a5-ed89329b9f7c")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("dca343a8-12e5-53d6-89a8-916b120a5ee4")
|
||||
);
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "Players",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("ecec27e8-487b-5622-b116-0855020477ed")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Migrations
|
||||
{
|
||||
[DbContext(typeof(PlayerDbContext))]
|
||||
partial class PlayerDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.5");
|
||||
|
||||
modelBuilder.Entity("Dotnet.Samples.AspNetCore.WebApi.Models.Player", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AbbrPosition")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("DateOfBirth")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FirstName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LastName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("League")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("MiddleName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Position")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SquadNumber")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("Starting11")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Team")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SquadNumber")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Players");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("01772c59-43f0-5d85-b913-c78e4e281452"),
|
||||
AbbrPosition = "GK",
|
||||
DateOfBirth = new DateTime(1992, 9, 1, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Damián",
|
||||
LastName = "Martínez",
|
||||
League = "Premier League",
|
||||
MiddleName = "Emiliano",
|
||||
Position = "Goalkeeper",
|
||||
SquadNumber = 23,
|
||||
Starting11 = true,
|
||||
Team = "Aston Villa FC"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("da31293b-4c7e-5e0f-a168-469ee29ecbc4"),
|
||||
AbbrPosition = "RB",
|
||||
DateOfBirth = new DateTime(1998, 4, 5, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Nahuel",
|
||||
LastName = "Molina",
|
||||
League = "La Liga",
|
||||
Position = "Right-Back",
|
||||
SquadNumber = 26,
|
||||
Starting11 = true,
|
||||
Team = "Altético Madrid"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("c096c69e-762b-5281-9290-bb9c167a24a0"),
|
||||
AbbrPosition = "CB",
|
||||
DateOfBirth = new DateTime(1998, 4, 26, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Cristian",
|
||||
LastName = "Romero",
|
||||
League = "Premier League",
|
||||
MiddleName = "Gabriel",
|
||||
Position = "Centre-Back",
|
||||
SquadNumber = 13,
|
||||
Starting11 = true,
|
||||
Team = "Tottenham Hotspur"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d5f7dd7a-1dcb-5960-ba27-e34865b63358"),
|
||||
AbbrPosition = "CB",
|
||||
DateOfBirth = new DateTime(1988, 2, 11, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Nicolás",
|
||||
LastName = "Otamendi",
|
||||
League = "Liga Portugal",
|
||||
MiddleName = "Hernán Gonzalo",
|
||||
Position = "Centre-Back",
|
||||
SquadNumber = 19,
|
||||
Starting11 = true,
|
||||
Team = "SL Benfica"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("2f6f90a0-9b9d-5023-96d2-a2aaf03143a6"),
|
||||
AbbrPosition = "LB",
|
||||
DateOfBirth = new DateTime(1992, 8, 30, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Nicolás",
|
||||
LastName = "Tagliafico",
|
||||
League = "Ligue 1",
|
||||
MiddleName = "Alejandro",
|
||||
Position = "Left-Back",
|
||||
SquadNumber = 3,
|
||||
Starting11 = true,
|
||||
Team = "Olympique Lyon"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("b5b46e79-929e-5ed2-949d-0d167109c022"),
|
||||
AbbrPosition = "RW",
|
||||
DateOfBirth = new DateTime(1988, 2, 13, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Ángel",
|
||||
LastName = "Di María",
|
||||
League = "Liga Portugal",
|
||||
MiddleName = "Fabián",
|
||||
Position = "Right Winger",
|
||||
SquadNumber = 11,
|
||||
Starting11 = true,
|
||||
Team = "SL Benfica"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("0293b282-1da8-562e-998e-83849b417a42"),
|
||||
AbbrPosition = "CM",
|
||||
DateOfBirth = new DateTime(1994, 5, 23, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Rodrigo",
|
||||
LastName = "de Paul",
|
||||
League = "La Liga",
|
||||
MiddleName = "Javier",
|
||||
Position = "Central Midfield",
|
||||
SquadNumber = 7,
|
||||
Starting11 = true,
|
||||
Team = "Altético Madrid"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d3ba552a-dac3-588a-b961-1ea7224017fd"),
|
||||
AbbrPosition = "CM",
|
||||
DateOfBirth = new DateTime(2001, 1, 16, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Enzo",
|
||||
LastName = "Fernández",
|
||||
League = "Liga Portugal",
|
||||
MiddleName = "Jeremías",
|
||||
Position = "Central Midfield",
|
||||
SquadNumber = 24,
|
||||
Starting11 = true,
|
||||
Team = "SL Benfica"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("9613cae9-16ab-5b54-937e-3135123b9e0d"),
|
||||
AbbrPosition = "CM",
|
||||
DateOfBirth = new DateTime(1998, 12, 23, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Alexis",
|
||||
LastName = "Mac Allister",
|
||||
League = "Premier League",
|
||||
Position = "Central Midfield",
|
||||
SquadNumber = 20,
|
||||
Starting11 = true,
|
||||
Team = "Brighton & Hove Albion"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("acc433bf-d505-51fe-831e-45eb44c4d43c"),
|
||||
AbbrPosition = "RW",
|
||||
DateOfBirth = new DateTime(1987, 6, 23, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Lionel",
|
||||
LastName = "Messi",
|
||||
League = "Ligue 1",
|
||||
MiddleName = "Andrés",
|
||||
Position = "Right Winger",
|
||||
SquadNumber = 10,
|
||||
Starting11 = true,
|
||||
Team = "Paris Saint-Germain"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("38bae91d-8519-55a2-b30a-b9fe38849bfb"),
|
||||
AbbrPosition = "CF",
|
||||
DateOfBirth = new DateTime(2000, 1, 30, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Julián",
|
||||
LastName = "Álvarez",
|
||||
League = "Premier League",
|
||||
Position = "Centre-Forward",
|
||||
SquadNumber = 9,
|
||||
Starting11 = true,
|
||||
Team = "Manchester City"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("5a9cd988-95e6-54c1-bc34-9aa08acca8d0"),
|
||||
AbbrPosition = "GK",
|
||||
DateOfBirth = new DateTime(1986, 10, 16, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Franco",
|
||||
LastName = "Armani",
|
||||
League = "Copa de la Liga",
|
||||
MiddleName = "Daniel",
|
||||
Position = "Goalkeeper",
|
||||
SquadNumber = 1,
|
||||
Starting11 = false,
|
||||
Team = "River Plate"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("c62f2ac1-41e8-5d34-b073-2ba0913d0e31"),
|
||||
AbbrPosition = "GK",
|
||||
DateOfBirth = new DateTime(1992, 5, 20, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Gerónimo",
|
||||
LastName = "Rulli",
|
||||
League = "Eredivisie",
|
||||
Position = "Goalkeeper",
|
||||
SquadNumber = 12,
|
||||
Starting11 = false,
|
||||
Team = "Ajax Amsterdam"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("5fdb10e8-38c0-5084-9a3f-b369a960b9c2"),
|
||||
AbbrPosition = "RB",
|
||||
DateOfBirth = new DateTime(1998, 1, 12, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Juan",
|
||||
LastName = "Foyth",
|
||||
League = "La Liga",
|
||||
MiddleName = "Marcos",
|
||||
Position = "Right-Back",
|
||||
SquadNumber = 2,
|
||||
Starting11 = false,
|
||||
Team = "Villarreal"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("bbd441f7-fcfb-5834-8468-2a9004b64c8c"),
|
||||
AbbrPosition = "RB",
|
||||
DateOfBirth = new DateTime(1997, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Gonzalo",
|
||||
LastName = "Montiel",
|
||||
League = "Premier League",
|
||||
MiddleName = "Ariel",
|
||||
Position = "Right-Back",
|
||||
SquadNumber = 4,
|
||||
Starting11 = false,
|
||||
Team = "Nottingham Forest"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d8bfea25-f189-5d5e-b3a5-ed89329b9f7c"),
|
||||
AbbrPosition = "CB",
|
||||
DateOfBirth = new DateTime(1991, 6, 27, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Germán",
|
||||
LastName = "Pezzella",
|
||||
League = "La Liga",
|
||||
MiddleName = "Alejo",
|
||||
Position = "Centre-Back",
|
||||
SquadNumber = 6,
|
||||
Starting11 = false,
|
||||
Team = "Real Betis Balompié"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("dca343a8-12e5-53d6-89a8-916b120a5ee4"),
|
||||
AbbrPosition = "LB",
|
||||
DateOfBirth = new DateTime(1991, 10, 28, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Marcos",
|
||||
LastName = "Acuña",
|
||||
League = "La Liga",
|
||||
MiddleName = "Javier",
|
||||
Position = "Left-Back",
|
||||
SquadNumber = 8,
|
||||
Starting11 = false,
|
||||
Team = "Sevilla FC"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("98306555-a466-5d18-804e-dc82175e697b"),
|
||||
AbbrPosition = "CB",
|
||||
DateOfBirth = new DateTime(1998, 1, 18, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Lisandro",
|
||||
LastName = "Martínez",
|
||||
League = "Premier League",
|
||||
Position = "Centre-Back",
|
||||
SquadNumber = 25,
|
||||
Starting11 = false,
|
||||
Team = "Manchester United"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("9d140400-196f-55d8-86e1-e0b96a375c83"),
|
||||
AbbrPosition = "DM",
|
||||
DateOfBirth = new DateTime(1994, 6, 29, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Leandro",
|
||||
LastName = "Paredes",
|
||||
League = "Serie A",
|
||||
MiddleName = "Daniel",
|
||||
Position = "Defensive Midfield",
|
||||
SquadNumber = 5,
|
||||
Starting11 = false,
|
||||
Team = "AS Roma"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("d3b0e8e8-2c34-531a-b608-b24fed0ef986"),
|
||||
AbbrPosition = "CM",
|
||||
DateOfBirth = new DateTime(1998, 10, 5, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Exequiel",
|
||||
LastName = "Palacios",
|
||||
League = "Bundesliga",
|
||||
MiddleName = "Alejandro",
|
||||
Position = "Central Midfield",
|
||||
SquadNumber = 14,
|
||||
Starting11 = false,
|
||||
Team = "Bayer 04 Leverkusen"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7cc8d527-56a2-58bd-9528-2618fc139d30"),
|
||||
AbbrPosition = "LW",
|
||||
DateOfBirth = new DateTime(1988, 2, 15, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Alejandro",
|
||||
LastName = "Gómez",
|
||||
League = "Serie A",
|
||||
MiddleName = "Darío",
|
||||
Position = "Left Winger",
|
||||
SquadNumber = 17,
|
||||
Starting11 = false,
|
||||
Team = "AC Monza"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("191c82af-0c51-526a-b903-c3600b61b506"),
|
||||
AbbrPosition = "DM",
|
||||
DateOfBirth = new DateTime(1994, 4, 12, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Guido",
|
||||
LastName = "Rodríguez",
|
||||
League = "La Liga",
|
||||
Position = "Defensive Midfield",
|
||||
SquadNumber = 18,
|
||||
Starting11 = false,
|
||||
Team = "Real Betis Balompié"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("b1306b7b-a3a4-5f7c-90fd-dd5bdbed57ba"),
|
||||
AbbrPosition = "RW",
|
||||
DateOfBirth = new DateTime(1995, 3, 9, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Ángel",
|
||||
LastName = "Correa",
|
||||
League = "La Liga",
|
||||
MiddleName = "Martín",
|
||||
Position = "Right Winger",
|
||||
SquadNumber = 15,
|
||||
Starting11 = false,
|
||||
Team = "Atlético Madrid"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("ecec27e8-487b-5622-b116-0855020477ed"),
|
||||
AbbrPosition = "AM",
|
||||
DateOfBirth = new DateTime(2001, 4, 26, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Thiago",
|
||||
LastName = "Almada",
|
||||
League = "Major League Soccer",
|
||||
MiddleName = "Ezequiel",
|
||||
Position = "Attacking Midfield",
|
||||
SquadNumber = 16,
|
||||
Starting11 = false,
|
||||
Team = "Atlanta United FC"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("7941cd7c-4df1-5952-97e8-1e7f5d08e8aa"),
|
||||
AbbrPosition = "SS",
|
||||
DateOfBirth = new DateTime(1993, 11, 15, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Paulo",
|
||||
LastName = "Dybala",
|
||||
League = "Serie A",
|
||||
MiddleName = "Exequiel",
|
||||
Position = "Second Striker",
|
||||
SquadNumber = 21,
|
||||
Starting11 = false,
|
||||
Team = "AS Roma"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("79c96f29-c59f-5f98-96b8-3a5946246624"),
|
||||
AbbrPosition = "CF",
|
||||
DateOfBirth = new DateTime(1997, 8, 22, 0, 0, 0, 0, DateTimeKind.Utc),
|
||||
FirstName = "Lautaro",
|
||||
LastName = "Martínez",
|
||||
League = "Serie A",
|
||||
MiddleName = "Javier",
|
||||
Position = "Centre-Forward",
|
||||
SquadNumber = 22,
|
||||
Starting11 = false,
|
||||
Team = "Inter Milan"
|
||||
});
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
#pragma warning disable EF1001 // Internal EF Core API usage.
|
||||
using Microsoft.EntityFrameworkCore.Migrations.Internal;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Migrations;
|
||||
|
||||
/// <summary>
|
||||
/// Filters the available migrations to only those belonging to the active database provider's
|
||||
/// namespace, ensuring SQLite and PostgreSQL each apply their own migration set.
|
||||
/// </summary>
|
||||
#pragma warning disable CS9107 // currentContext is intentionally captured by both the subclass and the base class
|
||||
public class ProviderSpecificMigrationsAssembly(
|
||||
ICurrentDbContext currentContext,
|
||||
IDbContextOptions options,
|
||||
IMigrationsIdGenerator idGenerator,
|
||||
IDiagnosticsLogger<DbLoggerCategory.Migrations> logger
|
||||
) : MigrationsAssembly(currentContext, options, idGenerator, logger)
|
||||
#pragma warning restore CS9107
|
||||
{
|
||||
private const string SqliteNamespace = "Dotnet.Samples.AspNetCore.WebApi.Migrations";
|
||||
private const string NpgsqlNamespace = "Dotnet.Samples.AspNetCore.WebApi.Migrations.Npgsql";
|
||||
|
||||
public override IReadOnlyDictionary<string, TypeInfo> Migrations
|
||||
{
|
||||
get
|
||||
{
|
||||
var providerName = currentContext.Context.Database.ProviderName ?? string.Empty;
|
||||
var targetNamespace = providerName.Contains(
|
||||
"Sqlite",
|
||||
StringComparison.OrdinalIgnoreCase
|
||||
)
|
||||
? SqliteNamespace
|
||||
: NpgsqlNamespace;
|
||||
|
||||
return new SortedDictionary<string, TypeInfo>(
|
||||
base.Migrations.Where(m => m.Value.Namespace == targetNamespace)
|
||||
.ToDictionary(m => m.Key, m => m.Value)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore EF1001
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Model for Player entity.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class represents the Player entity in the database.
|
||||
/// </remarks>
|
||||
public class Player
|
||||
{
|
||||
/// <summary>
|
||||
/// The unique identifier for the Player.
|
||||
/// </summary>
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
/// <summary>
|
||||
/// The first name of the Player.
|
||||
/// </summary>
|
||||
public string? FirstName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The middle name of the Player, if any.
|
||||
/// </summary>
|
||||
public string? MiddleName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The last name of the Player.
|
||||
/// </summary>
|
||||
public string? LastName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date of birth of the Player.
|
||||
/// </summary>
|
||||
public DateTime? DateOfBirth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The squad number assigned to the Player.
|
||||
/// </summary>
|
||||
public int SquadNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The playing position of the Player.
|
||||
/// </summary>
|
||||
public string? Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The abbreviated form of the Player's position.
|
||||
/// </summary>
|
||||
public string? AbbrPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The team to which the Player belongs.
|
||||
/// </summary>
|
||||
public string? Team { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The league where the team plays.
|
||||
/// </summary>
|
||||
public string? League { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the Player is in the starting 11.
|
||||
/// </summary>
|
||||
public bool Starting11 { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Model for Player request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class is used to receive Player data from the client.
|
||||
/// The properties are decorated with validation attributes to ensure that
|
||||
/// the required fields are provided and that the data is in the correct format.
|
||||
/// </remarks>
|
||||
public class PlayerRequestModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The first name of the Player.
|
||||
/// </summary>
|
||||
public string? FirstName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The middle name of the Player, if any.
|
||||
/// </summary>
|
||||
public string? MiddleName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The last name of the Player.
|
||||
/// </summary>
|
||||
public string? LastName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The date of birth of the Player.
|
||||
/// </summary>
|
||||
public DateTime? DateOfBirth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The squad number assigned to the Player (required).
|
||||
/// </summary>
|
||||
public required int SquadNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The abbreviated form of the Player's position.
|
||||
/// </summary>
|
||||
public string? AbbrPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The team to which the Player belongs.
|
||||
/// </summary>
|
||||
public string? Team { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The league where the team plays.
|
||||
/// </summary>
|
||||
public string? League { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Model for Player response.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class is used to send Player data to the client.
|
||||
/// </remarks>
|
||||
public class PlayerResponseModel
|
||||
{
|
||||
/// <summary>
|
||||
/// The full name of the Player (combined first, middle, and last names).
|
||||
/// </summary>
|
||||
public string? FullName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The formatted birth date of the Player.
|
||||
/// </summary>
|
||||
public string? Birth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The squad number (dorsal) of the Player.
|
||||
/// </summary>
|
||||
public int Dorsal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The playing position of the Player.
|
||||
/// </summary>
|
||||
public string? Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The club (team) to which the Player belongs.
|
||||
/// </summary>
|
||||
public string? Club { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The league where the club plays.
|
||||
/// </summary>
|
||||
public string? League { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the Player is in the starting 11 (formatted as string).
|
||||
/// </summary>
|
||||
public string? Starting11 { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Data;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Serilog;
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Web Application
|
||||
* Registers all services into the DI container before the application is built.
|
||||
* Throughout this section, builder.Services refers to the ASP.NET Core
|
||||
* dependency injection (DI) container — not to be confused with the Services
|
||||
* subsection below, which registers our own application-level business logic.
|
||||
* https://learn.microsoft.com/en-us/aspnet/core/fundamentals/startup
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
/* Configurations ----------------------------------------------------------- */
|
||||
|
||||
builder
|
||||
.Configuration.SetBasePath(AppContext.BaseDirectory)
|
||||
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
||||
.AddEnvironmentVariables();
|
||||
|
||||
/* Logging ------------------------------------------------------------------ */
|
||||
|
||||
Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(builder.Configuration).CreateLogger();
|
||||
builder.Host.UseSerilog();
|
||||
|
||||
/* Infrastructure ----------------------------------------------------------- */
|
||||
|
||||
builder.Services.AddHealthChecks();
|
||||
builder.Services.AddCorsDefaultPolicy(builder.Environment);
|
||||
builder.Services.AddFixedWindowRateLimiter(builder.Configuration);
|
||||
|
||||
if (builder.Environment.IsDevelopment())
|
||||
{
|
||||
builder.Services.AddSwaggerConfiguration(builder.Configuration);
|
||||
}
|
||||
|
||||
/* Controllers -------------------------------------------------------------- */
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddValidators();
|
||||
|
||||
/* Services (Business Logic) ------------------------------------------------ */
|
||||
|
||||
builder.Services.RegisterPlayerService();
|
||||
builder.Services.AddMemoryCache();
|
||||
builder.Services.AddMappings();
|
||||
|
||||
/* Repositories ------------------------------------------------------------- */
|
||||
|
||||
builder.Services.RegisterPlayerRepository();
|
||||
|
||||
/* Data --------------------------------------------------------------------- */
|
||||
|
||||
builder.Services.AddPlayerDbContext(builder.Environment);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Database Migration
|
||||
* Applies pending EF Core migrations at startup, before the app accepts requests.
|
||||
* https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying#apply-migrations-at-runtime
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
await using (var scope = app.Services.CreateAsyncScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<PlayerDbContext>();
|
||||
await db.Database.MigrateAsync();
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Middlewares
|
||||
* Defines the order in which middleware components process each HTTP request.
|
||||
* https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware
|
||||
* https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware#middleware-order
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
// Replaces the default ASP.NET Core request logging with Serilog's structured
|
||||
// logging, emitting one log entry per request with timing, status code, and
|
||||
// other contextual properties.
|
||||
app.UseSerilogRequestLogging();
|
||||
|
||||
// Custom middleware that catches unhandled exceptions and returns a consistent
|
||||
// RFC 7807 Problem Details response instead of exposing a raw stack trace.
|
||||
app.UseExceptionHandling();
|
||||
|
||||
// Redirects all plain HTTP requests to HTTPS, enforcing transport security.
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
// DisableRateLimiting() exempts the health check endpoint from the global rate
|
||||
// limiter so that monitoring and orchestration systems can always assess
|
||||
// liveness and readiness without being throttled.
|
||||
app.MapHealthChecks("/health").DisableRateLimiting();
|
||||
|
||||
// Enforces the fixed-window rate limiting policy defined during service
|
||||
// registration, returning 429 Too Many Requests when the limit is exceeded.
|
||||
app.UseRateLimiter();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
// Only active in Development, where AddCorsDefaultPolicy registers a
|
||||
// permissive wildcard policy for Swagger UI and local frontends. No policy
|
||||
// exists in Production — the API is assumed to be consumed server-to-server
|
||||
// or behind a reverse proxy on the same origin, where CORS is not needed.
|
||||
// Must precede MapControllers so CORS headers are applied before any
|
||||
// endpoint executes, consistent with the standard middleware pipeline order.
|
||||
app.UseCors();
|
||||
|
||||
// Generates the OpenAPI JSON document consumed by Swagger UI.
|
||||
app.UseSwagger();
|
||||
|
||||
// Serves the interactive Swagger UI at /swagger, allowing manual
|
||||
// exploration and testing of the API endpoints during development.
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
// Routes incoming HTTP requests to the matching controller actions. Must come
|
||||
// after all middleware that needs to run before endpoint execution (CORS, rate
|
||||
// limiting, etc.).
|
||||
app.MapControllers();
|
||||
|
||||
await app.RunAsync();
|
||||
|
||||
public partial class Program
|
||||
{
|
||||
protected Program() { }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Dotnet.Samples.AspNetCore.WebApi": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:56530;http://localhost:56531"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// IPlayerRepository.cs
|
||||
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Models;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Provides specialized repository operations for Player entities.
|
||||
/// </summary>
|
||||
public interface IPlayerRepository : IRepository<Player>
|
||||
{
|
||||
/// <summary>
|
||||
/// Finds a Player in the repository by their Squad Number.
|
||||
/// </summary>
|
||||
/// <param name="squadNumber">The Squad Number of the Player to retrieve.</param>
|
||||
/// <returns>
|
||||
/// A Task representing the asynchronous operation,containing the Player
|
||||
/// if found, or null if no Player with the specified Squad Number exists.
|
||||
/// </returns>
|
||||
Task<Player?> FindBySquadNumberAsync(int squadNumber);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Provides generic repository operations for entities of type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The entity type managed by this repository.</typeparam>
|
||||
public interface IRepository<T>
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new entity to the repository.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to create.</param>
|
||||
/// <returns>A Task representing the asynchronous operation.</returns>
|
||||
Task AddAsync(T entity);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all entities from the repository.
|
||||
/// </summary>
|
||||
/// <returns>A Task representing the asynchronous operation,
|
||||
/// containing a list of all entities.</returns>
|
||||
Task<List<T>> GetAllAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Finds an entity on the repository by its unique identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The unique identifier of the entity to retrieve.</param>
|
||||
/// <returns>
|
||||
/// A ValueTask representing the asynchronous operation, containing the entity if found,
|
||||
/// or null if no entity with the specified ID exists.
|
||||
/// </returns>
|
||||
ValueTask<T?> FindByIdAsync(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing entity in the repository.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity with updated values.</param>
|
||||
/// <returns>A Task representing the asynchronous operation.</returns>
|
||||
Task UpdateAsync(T entity);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an entity from the repository by its unique identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The unique identifier of the entity to remove.</param>
|
||||
/// <returns>A Task representing the asynchronous operation.</returns>
|
||||
Task RemoveAsync(Guid id);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Data;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Repositories;
|
||||
|
||||
public sealed class PlayerRepository(PlayerDbContext dbContext)
|
||||
: Repository<Player>(dbContext),
|
||||
IPlayerRepository
|
||||
{
|
||||
public async Task<Player?> FindBySquadNumberAsync(int squadNumber) =>
|
||||
await _dbSet.FirstOrDefaultAsync(p => p.SquadNumber == squadNumber);
|
||||
|
||||
public async Task<bool> SquadNumberExistsAsync(int squadNumber)
|
||||
{
|
||||
return await dbContext.Players.AnyAsync(p => p.SquadNumber == squadNumber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Repositories;
|
||||
|
||||
public class Repository<T>(DbContext dbContext) : IRepository<T>
|
||||
where T : class
|
||||
{
|
||||
protected readonly DbSet<T> _dbSet = dbContext.Set<T>();
|
||||
|
||||
public async Task AddAsync(T entity)
|
||||
{
|
||||
await _dbSet.AddAsync(entity);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<List<T>> GetAllAsync() => await _dbSet.AsNoTracking().ToListAsync();
|
||||
|
||||
public async ValueTask<T?> FindByIdAsync(Guid id) => await _dbSet.FindAsync(id);
|
||||
|
||||
public async Task UpdateAsync(T entity)
|
||||
{
|
||||
_dbSet.Update(entity);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task RemoveAsync(Guid id)
|
||||
{
|
||||
var entity = await _dbSet.FindAsync(id);
|
||||
if (entity != null)
|
||||
{
|
||||
_dbSet.Remove(entity);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Models;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for managing Player entities in the repository.
|
||||
/// </summary>
|
||||
public interface IPlayerService
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new Player to the repository.
|
||||
/// </summary>
|
||||
/// <param name="playerRequestModel">The Player to create.</param>
|
||||
/// <returns>A Task representing the asynchronous operation,
|
||||
/// containing the created Player.</returns>
|
||||
public Task<PlayerResponseModel> CreateAsync(PlayerRequestModel playerRequestModel);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all players from the repository.
|
||||
/// </summary>
|
||||
/// <returns>A Task representing the asynchronous operation,
|
||||
/// containing a list of all players.</returns>
|
||||
public Task<List<PlayerResponseModel>> RetrieveAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a Player from the repository by its ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the Player to retrieve.</param>
|
||||
/// <returns>
|
||||
/// A Task representing the asynchronous operation, containing the Player if found,
|
||||
/// or null if not.
|
||||
/// </returns>
|
||||
public Task<PlayerResponseModel?> RetrieveByIdAsync(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a Player from the repository by its Squad Number.
|
||||
/// </summary>
|
||||
/// <param name="squadNumber">The Squad Number of the Player to retrieve.</param>
|
||||
/// <returns>
|
||||
/// A Task representing the asynchronous operation, containing the Player if found,
|
||||
/// or null if not.
|
||||
/// </returns>
|
||||
public Task<PlayerResponseModel?> RetrieveBySquadNumberAsync(int squadNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Updates (entirely) an existing Player in the repository.
|
||||
/// </summary>
|
||||
/// <param name="playerRequestModel">The Player to update.</param>
|
||||
/// <returns>A Task representing the asynchronous operation.</returns>
|
||||
public Task UpdateAsync(PlayerRequestModel playerRequestModel);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an existing Player from the repository.
|
||||
/// </summary>
|
||||
/// <param name="squadNumber">The Squad Number of the Player to delete.</param>
|
||||
/// <returns>A Task representing the asynchronous operation.</returns>
|
||||
public Task DeleteAsync(int squadNumber);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using AutoMapper;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Models;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Repositories;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Services;
|
||||
|
||||
public class PlayerService(
|
||||
IPlayerRepository playerRepository,
|
||||
ILogger<PlayerService> logger,
|
||||
IMemoryCache memoryCache,
|
||||
IMapper mapper,
|
||||
IHostEnvironment environment
|
||||
) : IPlayerService
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a MemoryCacheEntryOptions instance with Normal priority,
|
||||
/// SlidingExpiration of 10 minutes and AbsoluteExpiration of 1 hour.
|
||||
/// </summary>
|
||||
private static readonly MemoryCacheEntryOptions CacheEntryOptions =
|
||||
new MemoryCacheEntryOptions()
|
||||
.SetPriority(CacheItemPriority.Normal)
|
||||
.SetSlidingExpiration(TimeSpan.FromMinutes(10))
|
||||
.SetAbsoluteExpiration(TimeSpan.FromHours(1));
|
||||
|
||||
/// <summary>
|
||||
/// The key used to store the list of Players in the cache.
|
||||
/// </summary>
|
||||
private static readonly string CacheKey_RetrieveAsync = nameof(RetrieveAsync);
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Create
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
public async Task<PlayerResponseModel> CreateAsync(PlayerRequestModel playerRequestModel)
|
||||
{
|
||||
var player = mapper.Map<Player>(playerRequestModel);
|
||||
await playerRepository.AddAsync(player);
|
||||
logger.LogInformation("Player added to Repository: {Player}", player);
|
||||
memoryCache.Remove(CacheKey_RetrieveAsync);
|
||||
logger.LogInformation("Removed objects from Cache with Key: {Key}", CacheKey_RetrieveAsync);
|
||||
return mapper.Map<PlayerResponseModel>(player);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Retrieve
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
public async Task<List<PlayerResponseModel>> RetrieveAsync()
|
||||
{
|
||||
if (memoryCache.TryGetValue(CacheKey_RetrieveAsync, out List<PlayerResponseModel>? cached))
|
||||
{
|
||||
logger.LogInformation("Players retrieved from Cache");
|
||||
return cached!;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (environment.IsDevelopment())
|
||||
{
|
||||
await SimulateRepositoryDelayAsync();
|
||||
}
|
||||
var players = await playerRepository.GetAllAsync();
|
||||
logger.LogInformation("Players retrieved from Repository");
|
||||
var playerResponseModels = mapper.Map<List<PlayerResponseModel>>(players);
|
||||
using (var cacheEntry = memoryCache.CreateEntry(CacheKey_RetrieveAsync))
|
||||
{
|
||||
logger.LogInformation(
|
||||
"{Count} entries created in Cache with key: {Key}",
|
||||
playerResponseModels.Count,
|
||||
CacheKey_RetrieveAsync
|
||||
);
|
||||
cacheEntry.SetSize(playerResponseModels.Count);
|
||||
cacheEntry.Value = playerResponseModels;
|
||||
cacheEntry.SetOptions(CacheEntryOptions);
|
||||
}
|
||||
return playerResponseModels;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<PlayerResponseModel?> RetrieveByIdAsync(Guid id)
|
||||
{
|
||||
var player = await playerRepository.FindByIdAsync(id);
|
||||
return player is not null ? mapper.Map<PlayerResponseModel>(player) : null;
|
||||
}
|
||||
|
||||
public async Task<PlayerResponseModel?> RetrieveBySquadNumberAsync(int squadNumber)
|
||||
{
|
||||
var player = await playerRepository.FindBySquadNumberAsync(squadNumber);
|
||||
return player is not null ? mapper.Map<PlayerResponseModel>(player) : null;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Update
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
public async Task UpdateAsync(PlayerRequestModel playerRequestModel)
|
||||
{
|
||||
if (
|
||||
await playerRepository.FindBySquadNumberAsync(playerRequestModel.SquadNumber)
|
||||
is Player player
|
||||
)
|
||||
{
|
||||
mapper.Map(playerRequestModel, player);
|
||||
await playerRepository.UpdateAsync(player);
|
||||
logger.LogInformation("Player updated in Repository: {Player}", player);
|
||||
memoryCache.Remove(CacheKey_RetrieveAsync);
|
||||
logger.LogInformation(
|
||||
"Removed objects from Cache with Key: {Key}",
|
||||
CacheKey_RetrieveAsync
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Delete
|
||||
* ---------------------------------------------------------------------- */
|
||||
|
||||
public async Task DeleteAsync(int squadNumber)
|
||||
{
|
||||
if (await playerRepository.FindBySquadNumberAsync(squadNumber) is Player player)
|
||||
{
|
||||
await playerRepository.RemoveAsync(player.Id);
|
||||
logger.LogInformation(
|
||||
"Player with Id {SquadNumber} removed from Repository",
|
||||
squadNumber
|
||||
);
|
||||
memoryCache.Remove(CacheKey_RetrieveAsync);
|
||||
logger.LogInformation(
|
||||
"Removed objects from Cache with Key: {Key}",
|
||||
CacheKey_RetrieveAsync
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simulates a delay in the repository call to mimic a long-running operation.
|
||||
/// This is only used in the Development environment to simulate a delay
|
||||
/// in the repository call. In production, this method should not be called.
|
||||
/// </summary>
|
||||
/// <returns>A Task representing the asynchronous operation.</returns>
|
||||
private async Task SimulateRepositoryDelayAsync()
|
||||
{
|
||||
var milliseconds = new Random().Next(2600, 4200);
|
||||
logger.LogInformation(
|
||||
"Simulating a random delay of {Milliseconds} milliseconds...",
|
||||
milliseconds
|
||||
);
|
||||
await Task.Delay(milliseconds);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Net;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Utility class for HTTP context operations.
|
||||
/// </summary>
|
||||
public static class HttpContextUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// This method checks for the "X-Forwarded-For" and "X-Real-IP" headers,
|
||||
/// which are commonly used by proxies to forward the original client IP address.
|
||||
/// If these headers are not present or the IP address cannot be parsed,
|
||||
/// it falls back to the remote IP address from the connection.
|
||||
/// If no valid IP address can be determined, it returns "unknown".
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The HTTP context.</param>
|
||||
/// <returns>The client IP address or "unknown" if not available.</returns>
|
||||
public static string ExtractIpAddress(HttpContext httpContext)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(httpContext);
|
||||
|
||||
var headers = httpContext.Request.Headers;
|
||||
IPAddress? ipAddress;
|
||||
|
||||
if (headers.TryGetValue("X-Forwarded-For", out var xForwardedFor))
|
||||
{
|
||||
var clientIp = xForwardedFor
|
||||
.ToString()
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(clientIp) && IPAddress.TryParse(clientIp, out ipAddress))
|
||||
return ipAddress.ToString();
|
||||
}
|
||||
|
||||
if (
|
||||
headers.TryGetValue("X-Real-IP", out var xRealIp)
|
||||
&& IPAddress.TryParse(xRealIp.ToString(), out ipAddress)
|
||||
)
|
||||
{
|
||||
return ipAddress.ToString();
|
||||
}
|
||||
|
||||
return httpContext.Connection.RemoteIpAddress?.ToString() ?? $"unknown-{Guid.NewGuid()}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,917 @@
|
||||
using System.Text.Json;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Enums;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Models;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Provides static player data for database seeding and testing.
|
||||
/// Single source of truth for all player definitions.
|
||||
/// </summary>
|
||||
public static class PlayerData
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the starting 11 players without IDs (for EF Core auto-increment).
|
||||
/// Used for database migrations and seeding.
|
||||
/// </summary>
|
||||
/// <returns>List of 11 Player entities representing the starting lineup.</returns>
|
||||
public static List<Player> MakeStarting11()
|
||||
{
|
||||
return
|
||||
[
|
||||
new()
|
||||
{
|
||||
FirstName = "Damián",
|
||||
MiddleName = "Emiliano",
|
||||
LastName = "Martínez",
|
||||
DateOfBirth = new DateTime(1992, 9, 1, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 23,
|
||||
Position = Position.Goalkeeper.Text,
|
||||
AbbrPosition = Position.Goalkeeper.Abbr,
|
||||
Team = "Aston Villa FC",
|
||||
League = "Premier League",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Nahuel",
|
||||
LastName = "Molina",
|
||||
DateOfBirth = new DateTime(1998, 4, 5, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 26,
|
||||
Position = Position.RightBack.Text,
|
||||
AbbrPosition = Position.RightBack.Abbr,
|
||||
Team = "Altético Madrid",
|
||||
League = "La Liga",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Cristian",
|
||||
MiddleName = "Gabriel",
|
||||
LastName = "Romero",
|
||||
DateOfBirth = new DateTime(1998, 4, 26, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 13,
|
||||
Position = Position.CentreBack.Text,
|
||||
AbbrPosition = Position.CentreBack.Abbr,
|
||||
Team = "Tottenham Hotspur",
|
||||
League = "Premier League",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Nicolás",
|
||||
MiddleName = "Hernán Gonzalo",
|
||||
LastName = "Otamendi",
|
||||
DateOfBirth = new DateTime(1988, 2, 11, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 19,
|
||||
Position = Position.CentreBack.Text,
|
||||
AbbrPosition = Position.CentreBack.Abbr,
|
||||
Team = "SL Benfica",
|
||||
League = "Liga Portugal",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Nicolás",
|
||||
MiddleName = "Alejandro",
|
||||
LastName = "Tagliafico",
|
||||
DateOfBirth = new DateTime(1992, 8, 30, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 3,
|
||||
Position = Position.LeftBack.Text,
|
||||
AbbrPosition = Position.LeftBack.Abbr,
|
||||
Team = "Olympique Lyon",
|
||||
League = "Ligue 1",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Ángel",
|
||||
MiddleName = "Fabián",
|
||||
LastName = "Di María",
|
||||
DateOfBirth = new DateTime(1988, 2, 13, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 11,
|
||||
Position = Position.RightWinger.Text,
|
||||
AbbrPosition = Position.RightWinger.Abbr,
|
||||
Team = "SL Benfica",
|
||||
League = "Liga Portugal",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Rodrigo",
|
||||
MiddleName = "Javier",
|
||||
LastName = "de Paul",
|
||||
DateOfBirth = new DateTime(1994, 5, 23, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 7,
|
||||
Position = Position.CentralMidfield.Text,
|
||||
AbbrPosition = Position.CentralMidfield.Abbr,
|
||||
Team = "Altético Madrid",
|
||||
League = "La Liga",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Enzo",
|
||||
MiddleName = "Jeremías",
|
||||
LastName = "Fernández",
|
||||
DateOfBirth = new DateTime(2001, 1, 16, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 24,
|
||||
Position = Position.CentralMidfield.Text,
|
||||
AbbrPosition = Position.CentralMidfield.Abbr,
|
||||
Team = "SL Benfica",
|
||||
League = "Liga Portugal",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Alexis",
|
||||
LastName = "Mac Allister",
|
||||
DateOfBirth = new DateTime(1998, 12, 23, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 20,
|
||||
Position = Position.CentralMidfield.Text,
|
||||
AbbrPosition = Position.CentralMidfield.Abbr,
|
||||
Team = "Brighton & Hove Albion",
|
||||
League = "Premier League",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Lionel",
|
||||
MiddleName = "Andrés",
|
||||
LastName = "Messi",
|
||||
DateOfBirth = new DateTime(1987, 6, 23, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 10,
|
||||
Position = Position.RightWinger.Text,
|
||||
AbbrPosition = Position.RightWinger.Abbr,
|
||||
Team = "Paris Saint-Germain",
|
||||
League = "Ligue 1",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Julián",
|
||||
LastName = "Álvarez",
|
||||
DateOfBirth = new DateTime(2000, 1, 30, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 9,
|
||||
Position = Position.CentreForward.Text,
|
||||
AbbrPosition = Position.CentreForward.Abbr,
|
||||
Team = "Manchester City",
|
||||
League = "Premier League",
|
||||
Starting11 = true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a predefined starting eleven of players where each player has a fixed GUID identifier and full profile data.
|
||||
/// </summary>
|
||||
/// <returns>A list of 11 Player instances representing the starting lineup; each entry includes a predefined Id (Guid) and populated fields such as name, date of birth, squad number, position (and abbreviation), team, league, and Starting11 set to true.</returns>
|
||||
public static List<Player> MakeStarting11WithId()
|
||||
{
|
||||
return
|
||||
[
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("01772c59-43f0-5d85-b913-c78e4e281452"),
|
||||
FirstName = "Damián",
|
||||
MiddleName = "Emiliano",
|
||||
LastName = "Martínez",
|
||||
DateOfBirth = new DateTime(1992, 9, 1, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 23,
|
||||
Position = Position.Goalkeeper.Text,
|
||||
AbbrPosition = Position.Goalkeeper.Abbr,
|
||||
Team = "Aston Villa FC",
|
||||
League = "Premier League",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("da31293b-4c7e-5e0f-a168-469ee29ecbc4"),
|
||||
FirstName = "Nahuel",
|
||||
LastName = "Molina",
|
||||
DateOfBirth = new DateTime(1998, 4, 5, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 26,
|
||||
Position = Position.RightBack.Text,
|
||||
AbbrPosition = Position.RightBack.Abbr,
|
||||
Team = "Altético Madrid",
|
||||
League = "La Liga",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("c096c69e-762b-5281-9290-bb9c167a24a0"),
|
||||
FirstName = "Cristian",
|
||||
MiddleName = "Gabriel",
|
||||
LastName = "Romero",
|
||||
DateOfBirth = new DateTime(1998, 4, 26, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 13,
|
||||
Position = Position.CentreBack.Text,
|
||||
AbbrPosition = Position.CentreBack.Abbr,
|
||||
Team = "Tottenham Hotspur",
|
||||
League = "Premier League",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("d5f7dd7a-1dcb-5960-ba27-e34865b63358"),
|
||||
FirstName = "Nicolás",
|
||||
MiddleName = "Hernán Gonzalo",
|
||||
LastName = "Otamendi",
|
||||
DateOfBirth = new DateTime(1988, 2, 11, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 19,
|
||||
Position = Position.CentreBack.Text,
|
||||
AbbrPosition = Position.CentreBack.Abbr,
|
||||
Team = "SL Benfica",
|
||||
League = "Liga Portugal",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("2f6f90a0-9b9d-5023-96d2-a2aaf03143a6"),
|
||||
FirstName = "Nicolás",
|
||||
MiddleName = "Alejandro",
|
||||
LastName = "Tagliafico",
|
||||
DateOfBirth = new DateTime(1992, 8, 30, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 3,
|
||||
Position = Position.LeftBack.Text,
|
||||
AbbrPosition = Position.LeftBack.Abbr,
|
||||
Team = "Olympique Lyon",
|
||||
League = "Ligue 1",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("b5b46e79-929e-5ed2-949d-0d167109c022"),
|
||||
FirstName = "Ángel",
|
||||
MiddleName = "Fabián",
|
||||
LastName = "Di María",
|
||||
DateOfBirth = new DateTime(1988, 2, 13, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 11,
|
||||
Position = Position.RightWinger.Text,
|
||||
AbbrPosition = Position.RightWinger.Abbr,
|
||||
Team = "SL Benfica",
|
||||
League = "Liga Portugal",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("0293b282-1da8-562e-998e-83849b417a42"),
|
||||
FirstName = "Rodrigo",
|
||||
MiddleName = "Javier",
|
||||
LastName = "de Paul",
|
||||
DateOfBirth = new DateTime(1994, 5, 23, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 7,
|
||||
Position = Position.CentralMidfield.Text,
|
||||
AbbrPosition = Position.CentralMidfield.Abbr,
|
||||
Team = "Altético Madrid",
|
||||
League = "La Liga",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("d3ba552a-dac3-588a-b961-1ea7224017fd"),
|
||||
FirstName = "Enzo",
|
||||
MiddleName = "Jeremías",
|
||||
LastName = "Fernández",
|
||||
DateOfBirth = new DateTime(2001, 1, 16, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 24,
|
||||
Position = Position.CentralMidfield.Text,
|
||||
AbbrPosition = Position.CentralMidfield.Abbr,
|
||||
Team = "SL Benfica",
|
||||
League = "Liga Portugal",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("9613cae9-16ab-5b54-937e-3135123b9e0d"),
|
||||
FirstName = "Alexis",
|
||||
LastName = "Mac Allister",
|
||||
DateOfBirth = new DateTime(1998, 12, 23, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 20,
|
||||
Position = Position.CentralMidfield.Text,
|
||||
AbbrPosition = Position.CentralMidfield.Abbr,
|
||||
Team = "Brighton & Hove Albion",
|
||||
League = "Premier League",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("acc433bf-d505-51fe-831e-45eb44c4d43c"),
|
||||
FirstName = "Lionel",
|
||||
MiddleName = "Andrés",
|
||||
LastName = "Messi",
|
||||
DateOfBirth = new DateTime(1987, 6, 23, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 10,
|
||||
Position = Position.RightWinger.Text,
|
||||
AbbrPosition = Position.RightWinger.Abbr,
|
||||
Team = "Paris Saint-Germain",
|
||||
League = "Ligue 1",
|
||||
Starting11 = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("38bae91d-8519-55a2-b30a-b9fe38849bfb"),
|
||||
FirstName = "Julián",
|
||||
LastName = "Álvarez",
|
||||
DateOfBirth = new DateTime(2000, 1, 30, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 9,
|
||||
Position = Position.CentreForward.Text,
|
||||
AbbrPosition = Position.CentreForward.Abbr,
|
||||
Team = "Manchester City",
|
||||
League = "Premier League",
|
||||
Starting11 = true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a predefined list of 15 substitute players with full profile data but without Id values.
|
||||
/// </summary>
|
||||
/// <returns>A list of 15 Player instances representing substitute players; each entry includes populated fields such as name, date of birth, squad number, position (and abbreviation), team, league, and Starting11 set to false. Id values are not assigned.</returns>
|
||||
public static List<Player> GetSubstitutes()
|
||||
{
|
||||
return
|
||||
[
|
||||
new()
|
||||
{
|
||||
FirstName = "Franco",
|
||||
MiddleName = "Daniel",
|
||||
LastName = "Armani",
|
||||
DateOfBirth = new DateTime(1986, 10, 16, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 1,
|
||||
Position = Position.Goalkeeper.Text,
|
||||
AbbrPosition = Position.Goalkeeper.Abbr,
|
||||
Team = "River Plate",
|
||||
League = "Copa de la Liga",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Gerónimo",
|
||||
LastName = "Rulli",
|
||||
DateOfBirth = new DateTime(1992, 5, 20, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 12,
|
||||
Position = Position.Goalkeeper.Text,
|
||||
AbbrPosition = Position.Goalkeeper.Abbr,
|
||||
Team = "Ajax Amsterdam",
|
||||
League = "Eredivisie",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Juan",
|
||||
MiddleName = "Marcos",
|
||||
LastName = "Foyth",
|
||||
DateOfBirth = new DateTime(1998, 1, 12, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 2,
|
||||
Position = Position.RightBack.Text,
|
||||
AbbrPosition = Position.RightBack.Abbr,
|
||||
Team = "Villarreal",
|
||||
League = "La Liga",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Gonzalo",
|
||||
MiddleName = "Ariel",
|
||||
LastName = "Montiel",
|
||||
DateOfBirth = new DateTime(1997, 1, 1, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 4,
|
||||
Position = Position.RightBack.Text,
|
||||
AbbrPosition = Position.RightBack.Abbr,
|
||||
Team = "Nottingham Forest",
|
||||
League = "Premier League",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Germán",
|
||||
MiddleName = "Alejo",
|
||||
LastName = "Pezzella",
|
||||
DateOfBirth = new DateTime(1991, 6, 27, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 6,
|
||||
Position = Position.CentreBack.Text,
|
||||
AbbrPosition = Position.CentreBack.Abbr,
|
||||
Team = "Real Betis Balompié",
|
||||
League = "La Liga",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Marcos",
|
||||
MiddleName = "Javier",
|
||||
LastName = "Acuña",
|
||||
DateOfBirth = new DateTime(1991, 10, 28, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 8,
|
||||
Position = Position.LeftBack.Text,
|
||||
AbbrPosition = Position.LeftBack.Abbr,
|
||||
Team = "Sevilla FC",
|
||||
League = "La Liga",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Lisandro",
|
||||
LastName = "Martínez",
|
||||
DateOfBirth = new DateTime(1998, 1, 18, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 25,
|
||||
Position = Position.CentreBack.Text,
|
||||
AbbrPosition = Position.CentreBack.Abbr,
|
||||
Team = "Manchester United",
|
||||
League = "Premier League",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Leandro",
|
||||
MiddleName = "Daniel",
|
||||
LastName = "Paredes",
|
||||
DateOfBirth = new DateTime(1994, 6, 29, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 5,
|
||||
Position = Position.DefensiveMidfield.Text,
|
||||
AbbrPosition = Position.DefensiveMidfield.Abbr,
|
||||
Team = "AS Roma",
|
||||
League = "Serie A",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Exequiel",
|
||||
MiddleName = "Alejandro",
|
||||
LastName = "Palacios",
|
||||
DateOfBirth = new DateTime(1998, 10, 5, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 14,
|
||||
Position = Position.CentralMidfield.Text,
|
||||
AbbrPosition = Position.CentralMidfield.Abbr,
|
||||
Team = "Bayer 04 Leverkusen",
|
||||
League = "Bundesliga",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Alejandro",
|
||||
MiddleName = "Darío",
|
||||
LastName = "Gómez",
|
||||
DateOfBirth = new DateTime(1988, 2, 15, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 17,
|
||||
Position = Position.LeftWinger.Text,
|
||||
AbbrPosition = Position.LeftWinger.Abbr,
|
||||
Team = "AC Monza",
|
||||
League = "Serie A",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Guido",
|
||||
LastName = "Rodríguez",
|
||||
DateOfBirth = new DateTime(1994, 4, 12, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 18,
|
||||
Position = Position.DefensiveMidfield.Text,
|
||||
AbbrPosition = Position.DefensiveMidfield.Abbr,
|
||||
Team = "Real Betis Balompié",
|
||||
League = "La Liga",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Ángel",
|
||||
MiddleName = "Martín",
|
||||
LastName = "Correa",
|
||||
DateOfBirth = new DateTime(1995, 3, 9, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 15,
|
||||
Position = Position.RightWinger.Text,
|
||||
AbbrPosition = Position.RightWinger.Abbr,
|
||||
Team = "Atlético Madrid",
|
||||
League = "La Liga",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Thiago",
|
||||
MiddleName = "Ezequiel",
|
||||
LastName = "Almada",
|
||||
DateOfBirth = new DateTime(2001, 4, 26, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 16,
|
||||
Position = Position.AttackingMidfield.Text,
|
||||
AbbrPosition = Position.AttackingMidfield.Abbr,
|
||||
Team = "Atlanta United FC",
|
||||
League = "Major League Soccer",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Paulo",
|
||||
MiddleName = "Exequiel",
|
||||
LastName = "Dybala",
|
||||
DateOfBirth = new DateTime(1993, 11, 15, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 21,
|
||||
Position = Position.SecondStriker.Text,
|
||||
AbbrPosition = Position.SecondStriker.Abbr,
|
||||
Team = "AS Roma",
|
||||
League = "Serie A",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Lautaro",
|
||||
MiddleName = "Javier",
|
||||
LastName = "Martínez",
|
||||
DateOfBirth = new DateTime(1997, 8, 22, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 22,
|
||||
Position = Position.CentreForward.Text,
|
||||
AbbrPosition = Position.CentreForward.Abbr,
|
||||
Team = "Inter Milan",
|
||||
League = "Serie A",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
FirstName = "Giovani",
|
||||
LastName = "Lo Celso",
|
||||
DateOfBirth = new DateTime(1996, 4, 9, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 27,
|
||||
Position = Position.CentralMidfield.Text,
|
||||
AbbrPosition = Position.CentralMidfield.Abbr,
|
||||
Team = "Real Betis Balompié",
|
||||
League = "La Liga",
|
||||
Starting11 = false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a predefined list of 15 substitute players where each player has a fixed GUID identifier and full profile data.
|
||||
/// </summary>
|
||||
/// <returns>A list of 15 Player instances representing substitute players (squads 1–26); each entry includes a predefined Id (Guid) and populated fields such as name, date of birth, squad number, position (and abbreviation), team, league, and Starting11 set to false. Lo Celso (squad 27) is intentionally excluded — his squad number falls outside the seeded range so he can serve as the canonical Create/Delete fixture without conflicting with seeded data.</returns>
|
||||
public static List<Player> GetSubstitutesWithId()
|
||||
{
|
||||
return
|
||||
[
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("5a9cd988-95e6-54c1-bc34-9aa08acca8d0"),
|
||||
FirstName = "Franco",
|
||||
MiddleName = "Daniel",
|
||||
LastName = "Armani",
|
||||
DateOfBirth = new DateTime(1986, 10, 16, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 1,
|
||||
Position = Position.Goalkeeper.Text,
|
||||
AbbrPosition = Position.Goalkeeper.Abbr,
|
||||
Team = "River Plate",
|
||||
League = "Copa de la Liga",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("c62f2ac1-41e8-5d34-b073-2ba0913d0e31"),
|
||||
FirstName = "Gerónimo",
|
||||
LastName = "Rulli",
|
||||
DateOfBirth = new DateTime(1992, 5, 20, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 12,
|
||||
Position = Position.Goalkeeper.Text,
|
||||
AbbrPosition = Position.Goalkeeper.Abbr,
|
||||
Team = "Ajax Amsterdam",
|
||||
League = "Eredivisie",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("5fdb10e8-38c0-5084-9a3f-b369a960b9c2"),
|
||||
FirstName = "Juan",
|
||||
MiddleName = "Marcos",
|
||||
LastName = "Foyth",
|
||||
DateOfBirth = new DateTime(1998, 1, 12, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 2,
|
||||
Position = Position.RightBack.Text,
|
||||
AbbrPosition = Position.RightBack.Abbr,
|
||||
Team = "Villarreal",
|
||||
League = "La Liga",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("bbd441f7-fcfb-5834-8468-2a9004b64c8c"),
|
||||
FirstName = "Gonzalo",
|
||||
MiddleName = "Ariel",
|
||||
LastName = "Montiel",
|
||||
DateOfBirth = new DateTime(1997, 1, 1, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 4,
|
||||
Position = Position.RightBack.Text,
|
||||
AbbrPosition = Position.RightBack.Abbr,
|
||||
Team = "Nottingham Forest",
|
||||
League = "Premier League",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("d8bfea25-f189-5d5e-b3a5-ed89329b9f7c"),
|
||||
FirstName = "Germán",
|
||||
MiddleName = "Alejo",
|
||||
LastName = "Pezzella",
|
||||
DateOfBirth = new DateTime(1991, 6, 27, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 6,
|
||||
Position = Position.CentreBack.Text,
|
||||
AbbrPosition = Position.CentreBack.Abbr,
|
||||
Team = "Real Betis Balompié",
|
||||
League = "La Liga",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("dca343a8-12e5-53d6-89a8-916b120a5ee4"),
|
||||
FirstName = "Marcos",
|
||||
MiddleName = "Javier",
|
||||
LastName = "Acuña",
|
||||
DateOfBirth = new DateTime(1991, 10, 28, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 8,
|
||||
Position = Position.LeftBack.Text,
|
||||
AbbrPosition = Position.LeftBack.Abbr,
|
||||
Team = "Sevilla FC",
|
||||
League = "La Liga",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("98306555-a466-5d18-804e-dc82175e697b"),
|
||||
FirstName = "Lisandro",
|
||||
LastName = "Martínez",
|
||||
DateOfBirth = new DateTime(1998, 1, 18, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 25,
|
||||
Position = Position.CentreBack.Text,
|
||||
AbbrPosition = Position.CentreBack.Abbr,
|
||||
Team = "Manchester United",
|
||||
League = "Premier League",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("9d140400-196f-55d8-86e1-e0b96a375c83"),
|
||||
FirstName = "Leandro",
|
||||
MiddleName = "Daniel",
|
||||
LastName = "Paredes",
|
||||
DateOfBirth = new DateTime(1994, 6, 29, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 5,
|
||||
Position = Position.DefensiveMidfield.Text,
|
||||
AbbrPosition = Position.DefensiveMidfield.Abbr,
|
||||
Team = "AS Roma",
|
||||
League = "Serie A",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("d3b0e8e8-2c34-531a-b608-b24fed0ef986"),
|
||||
FirstName = "Exequiel",
|
||||
MiddleName = "Alejandro",
|
||||
LastName = "Palacios",
|
||||
DateOfBirth = new DateTime(1998, 10, 5, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 14,
|
||||
Position = Position.CentralMidfield.Text,
|
||||
AbbrPosition = Position.CentralMidfield.Abbr,
|
||||
Team = "Bayer 04 Leverkusen",
|
||||
League = "Bundesliga",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("7cc8d527-56a2-58bd-9528-2618fc139d30"),
|
||||
FirstName = "Alejandro",
|
||||
MiddleName = "Darío",
|
||||
LastName = "Gómez",
|
||||
DateOfBirth = new DateTime(1988, 2, 15, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 17,
|
||||
Position = Position.LeftWinger.Text,
|
||||
AbbrPosition = Position.LeftWinger.Abbr,
|
||||
Team = "AC Monza",
|
||||
League = "Serie A",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("191c82af-0c51-526a-b903-c3600b61b506"),
|
||||
FirstName = "Guido",
|
||||
LastName = "Rodríguez",
|
||||
DateOfBirth = new DateTime(1994, 4, 12, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 18,
|
||||
Position = Position.DefensiveMidfield.Text,
|
||||
AbbrPosition = Position.DefensiveMidfield.Abbr,
|
||||
Team = "Real Betis Balompié",
|
||||
League = "La Liga",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("b1306b7b-a3a4-5f7c-90fd-dd5bdbed57ba"),
|
||||
FirstName = "Ángel",
|
||||
MiddleName = "Martín",
|
||||
LastName = "Correa",
|
||||
DateOfBirth = new DateTime(1995, 3, 9, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 15,
|
||||
Position = Position.RightWinger.Text,
|
||||
AbbrPosition = Position.RightWinger.Abbr,
|
||||
Team = "Atlético Madrid",
|
||||
League = "La Liga",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("ecec27e8-487b-5622-b116-0855020477ed"),
|
||||
FirstName = "Thiago",
|
||||
MiddleName = "Ezequiel",
|
||||
LastName = "Almada",
|
||||
DateOfBirth = new DateTime(2001, 4, 26, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 16,
|
||||
Position = Position.AttackingMidfield.Text,
|
||||
AbbrPosition = Position.AttackingMidfield.Abbr,
|
||||
Team = "Atlanta United FC",
|
||||
League = "Major League Soccer",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("7941cd7c-4df1-5952-97e8-1e7f5d08e8aa"),
|
||||
FirstName = "Paulo",
|
||||
MiddleName = "Exequiel",
|
||||
LastName = "Dybala",
|
||||
DateOfBirth = new DateTime(1993, 11, 15, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 21,
|
||||
Position = Position.SecondStriker.Text,
|
||||
AbbrPosition = Position.SecondStriker.Abbr,
|
||||
Team = "AS Roma",
|
||||
League = "Serie A",
|
||||
Starting11 = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = Guid.Parse("79c96f29-c59f-5f98-96b8-3a5946246624"),
|
||||
FirstName = "Lautaro",
|
||||
MiddleName = "Javier",
|
||||
LastName = "Martínez",
|
||||
DateOfBirth = new DateTime(1997, 8, 22, 0, 0, 0, DateTimeKind.Utc),
|
||||
SquadNumber = 22,
|
||||
Position = Position.CentreForward.Text,
|
||||
AbbrPosition = Position.CentreForward.Abbr,
|
||||
Team = "Inter Milan",
|
||||
League = "Serie A",
|
||||
Starting11 = false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The purpose of this method is to demonstrate the capabilities of
|
||||
/// System.Text.Json.JsonSerializer
|
||||
/// https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/deserialization
|
||||
/// </summary>
|
||||
/// <returns>A list of Players.</returns>
|
||||
public static List<Player> MakeStarting11FromDeserializedJson()
|
||||
{
|
||||
var players = new List<Player>();
|
||||
|
||||
var json = """
|
||||
[{
|
||||
"firstName": "Damián",
|
||||
"middleName": "Emiliano",
|
||||
"lastName": "Martínez",
|
||||
"dateOfBirth": "1992-09-01T00:00:00.000Z",
|
||||
"squadNumber": 23,
|
||||
"position": "Goalkeeper",
|
||||
"abbrPosition": "GK",
|
||||
"team": "Aston Villa FC",
|
||||
"league": "Premier League",
|
||||
"starting11": true
|
||||
},
|
||||
{
|
||||
"firstName": "Nahuel",
|
||||
"middleName": null,
|
||||
"lastName": "Molina",
|
||||
"dateOfBirth": "1998-04-05T00:00:00.000Z",
|
||||
"squadNumber": 26,
|
||||
"position": "Right-Back",
|
||||
"abbrPosition": "RB",
|
||||
"team": "Atlético Madrid",
|
||||
"league": "La Liga",
|
||||
"starting11": true
|
||||
},
|
||||
{
|
||||
"firstName": "Cristian",
|
||||
"middleName": "Gabriel",
|
||||
"lastName": "Romero",
|
||||
"dateOfBirth": "1998-04-26T00:00:00.000Z",
|
||||
"squadNumber": 13,
|
||||
"position": "Centre-Back",
|
||||
"abbrPosition": "CB",
|
||||
"team": "Tottenham Hotspur",
|
||||
"league": "Premier League",
|
||||
"starting11": true
|
||||
},
|
||||
{
|
||||
"firstName": "Nicolás",
|
||||
"middleName": "Hernán Gonzalo",
|
||||
"lastName": "Otamendi",
|
||||
"dateOfBirth": "1988-02-11T00:00:00.000Z",
|
||||
"squadNumber": 19,
|
||||
"position": "Centre-Back",
|
||||
"abbrPosition": "CB",
|
||||
"team": "SL Benfica",
|
||||
"league": "Liga Portugal",
|
||||
"starting11": true
|
||||
},
|
||||
{
|
||||
"firstName": "Nicolás",
|
||||
"middleName": "Alejandro",
|
||||
"lastName": "Tagliafico",
|
||||
"dateOfBirth": "1992-08-30T00:00:00.000Z",
|
||||
"squadNumber": 3,
|
||||
"position": "Left-Back",
|
||||
"abbrPosition": "LB",
|
||||
"team": "Olympique Lyon",
|
||||
"league": "Ligue 1",
|
||||
"starting11": true
|
||||
},
|
||||
{
|
||||
"firstName": "Ángel",
|
||||
"middleName": "Fabián",
|
||||
"lastName": "Di María",
|
||||
"dateOfBirth": "1988-02-13T00:00:00.000Z",
|
||||
"squadNumber": 11,
|
||||
"position": "Right Winger",
|
||||
"abbrPosition": "RW",
|
||||
"team": "SL Benfica",
|
||||
"league": "Liga Portugal",
|
||||
"starting11": true
|
||||
},
|
||||
{
|
||||
"firstName": "Rodrigo",
|
||||
"middleName": "Javier",
|
||||
"lastName": "de Paul",
|
||||
"dateOfBirth": "1994-05-23T00:00:00.000Z",
|
||||
"squadNumber": 7,
|
||||
"position": "Central Midfield",
|
||||
"abbrPosition": "CM",
|
||||
"team": "Atlético Madrid",
|
||||
"league": "La Liga",
|
||||
"starting11": true
|
||||
},
|
||||
{
|
||||
"firstName": "Enzo",
|
||||
"middleName": "Jeremías",
|
||||
"lastName": "Fernández",
|
||||
"dateOfBirth": "2001-01-16T00:00:00.000Z",
|
||||
"squadNumber": 24,
|
||||
"position": "Central Midfield",
|
||||
"abbrPosition": "CM",
|
||||
"team": "SL Benfica",
|
||||
"league": "Liga Portugal",
|
||||
"starting11": true
|
||||
},
|
||||
{
|
||||
"firstName": "Alexis",
|
||||
"middleName": null,
|
||||
"lastName": "Mac Allister",
|
||||
"dateOfBirth": "1998-12-23T00:00:00.000Z",
|
||||
"squadNumber": 20,
|
||||
"position": "Central Midfield",
|
||||
"abbrPosition": "CM",
|
||||
"team": "Brighton & Hove Albion",
|
||||
"league": "Premier League",
|
||||
"starting11": true
|
||||
},
|
||||
{
|
||||
"firstName": "Lionel",
|
||||
"middleName": "Andrés",
|
||||
"lastName": "Messi",
|
||||
"dateOfBirth": "1987-06-23T00:00:00.000Z",
|
||||
"squadNumber": 10,
|
||||
"position": "Right Winger",
|
||||
"abbrPosition": "RW",
|
||||
"team": "Paris Saint-Germain",
|
||||
"league": "Ligue 1",
|
||||
"starting11": true
|
||||
},
|
||||
{
|
||||
"firstName": "Julián",
|
||||
"middleName": null,
|
||||
"lastName": "Álvarez",
|
||||
"dateOfBirth": "2000-01-30T00:00:00.000Z",
|
||||
"squadNumber": 9,
|
||||
"position": "Centre-Forward",
|
||||
"abbrPosition": "CF",
|
||||
"team": "Manchester City",
|
||||
"league": "Premier League",
|
||||
"starting11": true
|
||||
}]
|
||||
""";
|
||||
|
||||
var starting11 = JsonSerializer.Deserialize<List<Player>>(json, options);
|
||||
|
||||
if (starting11 != null && starting11.Count > 0)
|
||||
{
|
||||
players.AddRange(starting11);
|
||||
}
|
||||
|
||||
return players;
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions options =
|
||||
new() { PropertyNameCaseInsensitive = true, };
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.OpenApi;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Utility methods for Swagger/OpenAPI configuration.
|
||||
/// Contains reusable helper methods that create OpenAPI objects.
|
||||
/// </summary>
|
||||
public static class SwaggerUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves the path to the XML comments file generated from code
|
||||
/// documentation.
|
||||
/// This is used to enrich the Swagger UI with method summaries and remarks.
|
||||
/// </summary>
|
||||
/// <returns>Full file path to the XML documentation file.</returns>
|
||||
public static string ConfigureXmlCommentsFilePath()
|
||||
{
|
||||
var path = Path.Combine(
|
||||
AppContext.BaseDirectory,
|
||||
$"{Assembly.GetExecutingAssembly().GetName().Name}.xml"
|
||||
);
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
throw new FileNotFoundException("XML comments file not found.", path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the OpenAPI security definition for JWT Bearer authentication.
|
||||
/// This will show the padlock icon in Swagger UI and allow users to
|
||||
/// authenticate.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="OpenApiSecurityScheme"/> describing the Bearer token format.</returns>
|
||||
public static OpenApiSecurityScheme ConfigureSecurityDefinition()
|
||||
{
|
||||
return new OpenApiSecurityScheme
|
||||
{
|
||||
Name = "Authorization",
|
||||
Type = SecuritySchemeType.Http,
|
||||
Scheme = "bearer",
|
||||
BearerFormat = "JWT",
|
||||
In = ParameterLocation.Header,
|
||||
Description = "Enter your JWT token below. Example: Bearer {token}"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a global security requirement to the Swagger spec that applies
|
||||
/// the Bearer definition.
|
||||
/// Routes decorated with [Authorize] will be marked as protected in the UI.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="OpenApiSecurityRequirement"/> referencing the Bearer definition.</returns>
|
||||
public static OpenApiSecurityRequirement ConfigureSecurityRequirement()
|
||||
{
|
||||
return new OpenApiSecurityRequirement
|
||||
{
|
||||
{ new OpenApiSecuritySchemeReference("Bearer", null), new List<string>() }
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Enums;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Models;
|
||||
using Dotnet.Samples.AspNetCore.WebApi.Repositories;
|
||||
using FluentValidation;
|
||||
|
||||
namespace Dotnet.Samples.AspNetCore.WebApi.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// Validator for PlayerRequestModel.
|
||||
/// This class uses FluentValidation to define validation rules for the
|
||||
/// PlayerRequestModel.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Rules are organized into CRUD-named rule sets to make their intent explicit.
|
||||
/// This prevents <c>BeUniqueSquadNumber</c> from running on PUT requests, where
|
||||
/// the player's squad number already exists in the database by definition.
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <item><description>
|
||||
/// <c>"Create"</c> — used by <c>POST /players</c>; includes all rules plus
|
||||
/// the uniqueness check for <c>SquadNumber</c>.
|
||||
/// </description></item>
|
||||
/// <item><description>
|
||||
/// <c>"Update"</c> — used by <c>PUT /players/squadNumber/{n}</c>; same
|
||||
/// rules, but <c>BeUniqueSquadNumber</c> is intentionally omitted.
|
||||
/// </description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// Controllers pass <c>opts.IncludeRuleSets("Create")</c> or
|
||||
/// <c>opts.IncludeRuleSets("Update")</c> so that only the appropriate rule
|
||||
/// set runs for each operation.
|
||||
/// </remarks>
|
||||
public class PlayerRequestModelValidator : AbstractValidator<PlayerRequestModel>
|
||||
{
|
||||
private readonly IPlayerRepository _playerRepository;
|
||||
|
||||
public PlayerRequestModelValidator(
|
||||
IPlayerRepository playerRepository,
|
||||
TimeProvider? timeProvider = null
|
||||
)
|
||||
{
|
||||
_playerRepository = playerRepository;
|
||||
var clock = timeProvider ?? TimeProvider.System;
|
||||
|
||||
// "Create" rule set — POST /players
|
||||
// Includes BeUniqueSquadNumber to prevent duplicate squad numbers on insert.
|
||||
RuleSet(
|
||||
"Create",
|
||||
() =>
|
||||
{
|
||||
RuleFor(player => player.FirstName)
|
||||
.NotEmpty()
|
||||
.WithMessage("FirstName is required.");
|
||||
|
||||
RuleFor(player => player.LastName).NotEmpty().WithMessage("LastName is required.");
|
||||
|
||||
RuleFor(player => player.SquadNumber)
|
||||
.NotEmpty()
|
||||
.WithMessage("SquadNumber is required.")
|
||||
.GreaterThan(0)
|
||||
.WithMessage("SquadNumber must be greater than 0.")
|
||||
.MustAsync(BeUniqueSquadNumber)
|
||||
.WithMessage("SquadNumber must be unique.");
|
||||
|
||||
RuleFor(player => player.AbbrPosition)
|
||||
.NotEmpty()
|
||||
.WithMessage("AbbrPosition is required.")
|
||||
.Must(Position.IsValidAbbr)
|
||||
.WithMessage("AbbrPosition is invalid.");
|
||||
|
||||
When(
|
||||
player => player.DateOfBirth.HasValue,
|
||||
() =>
|
||||
{
|
||||
RuleFor(player => player.DateOfBirth)
|
||||
.Must(date => date!.Value.Date < clock.GetUtcNow().Date)
|
||||
.WithMessage("DateOfBirth must be a date in the past.")
|
||||
.Must(date =>
|
||||
date!.Value.Date
|
||||
>= new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)
|
||||
)
|
||||
.WithMessage("DateOfBirth must be on or after January 1, 1900.");
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// "Update" rule set — PUT /players/squadNumber/{n}
|
||||
// BeUniqueSquadNumber is intentionally omitted: on PUT the player being
|
||||
// updated already exists in the database, so the check would always fail.
|
||||
RuleSet(
|
||||
"Update",
|
||||
() =>
|
||||
{
|
||||
RuleFor(player => player.FirstName)
|
||||
.NotEmpty()
|
||||
.WithMessage("FirstName is required.");
|
||||
|
||||
RuleFor(player => player.LastName).NotEmpty().WithMessage("LastName is required.");
|
||||
|
||||
RuleFor(player => player.SquadNumber)
|
||||
.NotEmpty()
|
||||
.WithMessage("SquadNumber is required.")
|
||||
.GreaterThan(0)
|
||||
.WithMessage("SquadNumber must be greater than 0.");
|
||||
|
||||
RuleFor(player => player.AbbrPosition)
|
||||
.NotEmpty()
|
||||
.WithMessage("AbbrPosition is required.")
|
||||
.Must(Position.IsValidAbbr)
|
||||
.WithMessage("AbbrPosition is invalid.");
|
||||
|
||||
When(
|
||||
player => player.DateOfBirth.HasValue,
|
||||
() =>
|
||||
{
|
||||
RuleFor(player => player.DateOfBirth)
|
||||
.Must(date => date!.Value.Date < clock.GetUtcNow().Date)
|
||||
.WithMessage("DateOfBirth must be a date in the past.")
|
||||
.Must(date =>
|
||||
date!.Value.Date
|
||||
>= new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)
|
||||
)
|
||||
.WithMessage("DateOfBirth must be on or after January 1, 1900.");
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async Task<bool> BeUniqueSquadNumber(
|
||||
int squadNumber,
|
||||
CancellationToken cancellationToken
|
||||
) => (await _playerRepository.FindBySquadNumberAsync(squadNumber)) is null;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"Serilog": {
|
||||
"Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File"],
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Console",
|
||||
"Args": {
|
||||
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
|
||||
"outputTemplate": "{Timestamp:HH:mm:ss.fff zzz} [{Level}] {Message:lj}{NewLine}{Exception}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "File",
|
||||
"Args": {
|
||||
"path": "logs/log-.log",
|
||||
"rollingInterval": "Day",
|
||||
"outputTemplate": "{Timestamp:HH:mm:ss.fff zzz} [{Level}] {Message:lj}{NewLine}{Exception}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Enrich": ["FromLogContext"]
|
||||
},
|
||||
"OpenApiInfo": {
|
||||
"Version": "1.0.0",
|
||||
"Title": "Dotnet.Samples.AspNetCore.WebApi",
|
||||
"Description": "🧪 Proof of Concept for a Web API (Async) made with .NET 8 (LTS) and ASP.NET Core 8.0",
|
||||
"Contact": {
|
||||
"Name": "GitHub",
|
||||
"Url": "https://github.com/nanotaboada/Dotnet.Samples.AspNetCore.WebApi"
|
||||
},
|
||||
"License": {
|
||||
"Name": "MIT License",
|
||||
"Url": "https://opensource.org/license/mit"
|
||||
}
|
||||
},
|
||||
"RateLimiter": {
|
||||
"PermitLimit": 60,
|
||||
"WindowSeconds": 60,
|
||||
"QueueLimit": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"Serilog": {
|
||||
"Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File"],
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "Console",
|
||||
"Args": {
|
||||
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
|
||||
"outputTemplate": "{Timestamp:HH:mm:ss.fff zzz} [{Level}] {Message:lj}{NewLine}{Exception}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "File",
|
||||
"Args": {
|
||||
"path": "logs/log-.log",
|
||||
"rollingInterval": "Day",
|
||||
"outputTemplate": "{Timestamp:HH:mm:ss.fff zzz} [{Level}] {Message:lj}{NewLine}{Exception}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Enrich": ["FromLogContext"]
|
||||
},
|
||||
"OpenApiInfo": {
|
||||
"Version": "1.0.0",
|
||||
"Title": "Dotnet.Samples.AspNetCore.WebApi",
|
||||
"Description": "🧪 Proof of Concept for a Web API (Async) made with .NET 8 (LTS) and ASP.NET Core 8.0",
|
||||
"Contact": {
|
||||
"Name": "GitHub",
|
||||
"Url": "https://github.com/nanotaboada/Dotnet.Samples.AspNetCore.WebApi"
|
||||
},
|
||||
"License": {
|
||||
"Name": "MIT License",
|
||||
"Url": "https://opensource.org/license/mit"
|
||||
}
|
||||
},
|
||||
"RateLimiter": {
|
||||
"PermitLimit": 60,
|
||||
"WindowSeconds": 60,
|
||||
"QueueLimit": 0
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user