-
Notifications
You must be signed in to change notification settings - Fork 16
Description
Problem
Currently, the API only supports PUT operations for updating player data, which requires sending the entire player object even when only one or two fields need to be modified. This is inefficient and doesn't follow REST best practices for partial resource updates. Users need a way to update specific player fields without having to provide all player data.
Proposed Solution
Implement HTTP PATCH method endpoints to allow partial updates of player resources. The API should accept a PlayerPartialUpdateRequestModel and update only the fields provided in the request, leaving other fields unchanged.
Important constraint: Partial updates should allow modifying team-related data (position, squad number, team, league) but NOT personal details (FirstName, MiddleName, LastName, DateOfBirth). Personal information should only be modified via PUT (full update).
Endpoint to implement:
PATCH /players/squadnumber/{squadNumber}- Update specific fields of a player by squad number
Note: We use squad number (natural identifier) instead of internal ID, as squad numbers are unique and user-facing. The ID-based endpoint is admin-only.
Suggested Approach
1. Route/Controller Definition (Controllers/PlayerController.cs)
- Add
[HttpPatch("squadnumber/{squadNumber}")]endpoint withPatchBySquadNumberAsync(int squadNumber, PlayerPartialUpdateRequestModel model)method - Return
TypedResults.Ok()(200),TypedResults.NotFound()(404),TypedResults.BadRequest()(400), orTypedResults.Conflict()(409) - Use primary constructor for dependency injection
2. Service Layer (Services/PlayerService.cs)
- Create
Task<PlayerResponseModel?> PatchBySquadNumberAsync(int squadNumber, PlayerPartialUpdateRequestModel model)method - If updating SquadNumber field itself, validate new squad number doesn't create duplicates
- Clear cache after successful updates (
_cache.Remove(CacheKey_RetrieveAsync)) - Use repository layer to fetch, update, and save entity
- Use AutoMapper to transform
PlayerPartialUpdateRequestModelto entity updates - Add structured logging with
ILogger<PlayerService>
3. Repository Layer (Repositories/PlayerRepository.cs)
- Reuse existing repository method
GetBySquadNumberAsyncandUpdateAsync - Apply partial updates to entity using AutoMapper or manual property mapping
- Ensure only non-null properties from request model are applied to entity
4. Model Definition (Models/)
- Refactor existing models to follow Request/Response pattern:
- Rename
PlayerRequestModeltoPlayerCreateRequestModel(for POST) - Create
PlayerUpdateRequestModelclass (for PUT - full replacement, all fields) - Create
PlayerPartialUpdateRequestModelclass with only team-related fields as nullable:public class PlayerPartialUpdateRequestModel { public int? SquadNumber { get; set; } public string? AbbrPosition { get; set; } public string? Team { get; set; } public string? League { get; set; } // Excludes: FirstName, MiddleName, LastName, DateOfBirth }
- Keep
PlayerResponseModelas is - already demonstrates data transformations:// Entity (Player) → Response (PlayerResponseModel) transformations: // - FirstName + MiddleName + LastName → FullName (concatenation) // - DateOfBirth (DateTime) → Birth (formatted string) // - SquadNumber → Dorsal (different naming) // - Team → Club (different naming) // - Starting11 (bool) → Starting11 ("Yes"/"No" string)
- Rename
- All models follow PascalCase with
Modelsuffix - AutoMapper handles all transformations (configured in
PlayerMappingProfile)
5. Validation (Validators/)
- Create
PlayerPartialUpdateRequestModelValidatorwith FluentValidation rules - Validate squad number range and uniqueness (async validator checking repository)
- Validate position abbreviation using
Position.FromAbbr()if position is provided - Register validator in
ServiceCollectionExtensions.AddValidation()
6. AutoMapper Configuration (Mappings/PlayerMappingProfile.cs)
- Add mapping from
PlayerPartialUpdateRequestModeltoPlayerentity - Configure to ignore null values (
Condition(src => src != null)) - Update existing mappings to reflect renamed models (
PlayerCreateRequestModel,PlayerUpdateRequestModel) - Verify existing
Player→PlayerResponseModelmapping demonstrates data transformations:FullNamecomputed fromFirstName + MiddleName + LastNameBirthformatted fromDateOfBirthDorsalmapped fromSquadNumberClubmapped fromTeamStarting11converted from bool to "Yes"/"No" string
- This mapping demonstrates AutoMapper's transformation capabilities
7. Testing (test/Dotnet.Samples.AspNetCore.WebApi.Tests/)
- Create
PlayerServiceTeststest methods for PATCH operations using Given-When-Then pattern - Create
PlayerControllerTeststest methods for PATCH endpoint - Test partial updates (single field, multiple fields)
- Test squad number uniqueness validation when updating to new squad number
- Test 404 response for non-existent player by squad number
- Test that personal details cannot be modified via PATCH
- Test cache invalidation after PATCH operations
- Use
PlayerFakesandPlayerMocksutilities for test data - Add
[Trait("Category", "Unit")]attributes - Follow AAA pattern (Arrange, Act, Assert)
8. Code Style
- Use primary constructors for dependency injection
- Format with CSharpier (opinionated style)
- Follow async/await patterns consistently (
async Task<T>return types) - Use
varfor local variables when type is obvious - Add XML documentation comments for public methods
- Use structured logging patterns with Serilog
Acceptance Criteria
- Models refactored to use Request/Response pattern:
-
PlayerResponseModelfor GET operations -
PlayerCreateRequestModelfor POST operations (renamed fromPlayerRequestModel) -
PlayerUpdateRequestModelfor PUT operations (new) -
PlayerPartialUpdateRequestModelfor PATCH operations (excludes personal details)
-
- PATCH endpoint
/players/squadnumber/{squadNumber}successfully updates team-related player fields - Personal details (FirstName, MiddleName, LastName, DateOfBirth) cannot be modified via PATCH
- Only fields provided in request body are updated (other fields remain unchanged)
- Squad number uniqueness is validated when updating to new squad number via FluentValidation
- Appropriate HTTP status codes returned (200 OK, 404 Not Found, 400 Bad Request, 409 Conflict)
- Cache is invalidated after successful PATCH operations
- All tests pass with
dotnet test - Test coverage includes new service and controller method
- Code formatted with CSharpier
- Swagger/OpenAPI documentation reflects new model names and PATCH endpoint
- Structured logging includes relevant context (squad numbers)