From 267438f1c1196992f7e9c8be3147c36d7c47bb72 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Tue, 15 Sep 2026 15:20:05 +0200 Subject: [PATCH] Web API return types: retarget net10.0, built-in OpenAPI, HttpResults controller Retargets aspnetcore-webapi/ReturnTypes from net6.0 to net10.0 and modernises it. - Swashbuckle.AspNetCore 6.2.3 removed; Microsoft.AspNetCore.OpenApi 10.0.12 added, with AddOpenApi() and MapOpenApi() replacing AddEndpointsApiExplorer/AddSwaggerGen and UseSwagger/UseSwaggerUI. - Test packages: Mvc.Testing 10.0.12, Test.Sdk 18.10.1, xunit 2.9.3, xunit.runner.visualstudio 4.0.0, coverlet.collector 10.0.1. - Fixes CS8602 in EmployeeIActionResultController.CreateAsync by replacing the employee.Name.Length guard with a pattern, and applies the same pattern in EmployeeActionResultController in place of the int? workaround. - EmployeeActionResultController.GetById returns ActionResult instead of ActionResult, and TryGetEmployee gains [NotNullWhen(true)] on the interface and the implementation so the implicit conversion stays warning free. - The 200 attribute uses the generic [ProducesResponseType] form. - New EmployeeHttpResultsController shows the fourth return type, Results through TypedResults. Build: 0 warnings, 0 errors. Tests: 7 passed, 0 failed, 0 skipped. --- .../APIReturnType/APIReturnType.csproj | 4 +- .../EmployeeActionResultController.cs | 7 ++-- .../EmployeeHttpResultsController.cs | 41 +++++++++++++++++++ .../EmployeeIActionResultController.cs | 4 +- .../APIReturnType/FakeRepository.cs | 6 ++- .../APIReturnType/IFakeRepository.cs | 6 ++- .../ReturnTypes/APIReturnType/Program.cs | 7 +--- .../ReturnTypes/Tests/Tests/Tests.csproj | 12 +++--- 8 files changed, 64 insertions(+), 23 deletions(-) create mode 100644 aspnetcore-webapi/ReturnTypes/APIReturnType/Controllers/EmployeeHttpResultsController.cs diff --git a/aspnetcore-webapi/ReturnTypes/APIReturnType/APIReturnType.csproj b/aspnetcore-webapi/ReturnTypes/APIReturnType/APIReturnType.csproj index 60bf9ead83..0268d75ddb 100644 --- a/aspnetcore-webapi/ReturnTypes/APIReturnType/APIReturnType.csproj +++ b/aspnetcore-webapi/ReturnTypes/APIReturnType/APIReturnType.csproj @@ -1,13 +1,13 @@ - net6.0 + net10.0 enable enable - + diff --git a/aspnetcore-webapi/ReturnTypes/APIReturnType/Controllers/EmployeeActionResultController.cs b/aspnetcore-webapi/ReturnTypes/APIReturnType/Controllers/EmployeeActionResultController.cs index 6d3b075464..35797e2c2c 100644 --- a/aspnetcore-webapi/ReturnTypes/APIReturnType/Controllers/EmployeeActionResultController.cs +++ b/aspnetcore-webapi/ReturnTypes/APIReturnType/Controllers/EmployeeActionResultController.cs @@ -16,7 +16,7 @@ public EmployeeActionResultController(IFakeRepository repository) [HttpGet("{id}")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public ActionResult GetById(int id) + public ActionResult GetById(int id) { if (!_repository.TryGetEmployee(id, out var employee)) { @@ -31,15 +31,14 @@ public EmployeeActionResultController(IFakeRepository repository) [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task> CreateAsync(Employee employee) { - int? employeeNameLength = employee?.Name?.Length; - if (employeeNameLength < 3 || employeeNameLength > 30) + if (employee.Name is not { Length: >= 3 and <= 30 }) { return BadRequest("Name should be between 3 and 30 characters."); } await _repository.AddEmployeeAsync(employee); - return CreatedAtAction(nameof(GetById), new { id = employee?.Id }, employee); + return CreatedAtAction(nameof(GetById), new { id = employee.Id }, employee); } } } diff --git a/aspnetcore-webapi/ReturnTypes/APIReturnType/Controllers/EmployeeHttpResultsController.cs b/aspnetcore-webapi/ReturnTypes/APIReturnType/Controllers/EmployeeHttpResultsController.cs new file mode 100644 index 0000000000..92f7c3116b --- /dev/null +++ b/aspnetcore-webapi/ReturnTypes/APIReturnType/Controllers/EmployeeHttpResultsController.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; + +namespace APIReturnType.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class EmployeeHttpResultsController : ControllerBase + { + public IFakeRepository _repository; + + public EmployeeHttpResultsController(IFakeRepository repository) + { + _repository = repository; + } + + [HttpGet("{id}")] + public Results> GetById(int id) + { + if (!_repository.TryGetEmployee(id, out var employee)) + { + return TypedResults.NotFound(); + } + + return TypedResults.Ok(employee); + } + + [HttpPost] + public async Task, Created>> CreateAsync(Employee employee) + { + if (employee.Name is not { Length: >= 3 and <= 30 }) + { + return TypedResults.BadRequest("Name should be between 3 and 30 characters."); + } + + await _repository.AddEmployeeAsync(employee); + + return TypedResults.Created($"/api/employeehttpresults/{employee.Id}", employee); + } + } +} diff --git a/aspnetcore-webapi/ReturnTypes/APIReturnType/Controllers/EmployeeIActionResultController.cs b/aspnetcore-webapi/ReturnTypes/APIReturnType/Controllers/EmployeeIActionResultController.cs index 067f5fb241..435b3a3f59 100644 --- a/aspnetcore-webapi/ReturnTypes/APIReturnType/Controllers/EmployeeIActionResultController.cs +++ b/aspnetcore-webapi/ReturnTypes/APIReturnType/Controllers/EmployeeIActionResultController.cs @@ -14,7 +14,7 @@ public EmployeeIActionResultController(IFakeRepository repository) } [HttpGet("{id}")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Employee))] + [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public IActionResult GetById(int id) { @@ -31,7 +31,7 @@ public IActionResult GetById(int id) [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task CreateAsync(Employee employee) { - if (employee.Name.Length < 3 || employee.Name.Length > 30) + if (employee.Name is not { Length: >= 3 and <= 30 }) { return BadRequest("Name should be between 3 and 30 characters."); } diff --git a/aspnetcore-webapi/ReturnTypes/APIReturnType/FakeRepository.cs b/aspnetcore-webapi/ReturnTypes/APIReturnType/FakeRepository.cs index 89aa95d847..639c66bf72 100644 --- a/aspnetcore-webapi/ReturnTypes/APIReturnType/FakeRepository.cs +++ b/aspnetcore-webapi/ReturnTypes/APIReturnType/FakeRepository.cs @@ -1,4 +1,6 @@ -namespace APIReturnType +using System.Diagnostics.CodeAnalysis; + +namespace APIReturnType { public class FakeRepository : IFakeRepository { @@ -31,7 +33,7 @@ public IEnumerable GetEmployees() return Employees; } - public bool TryGetEmployee(int id, out Employee? employee) + public bool TryGetEmployee(int id, [NotNullWhen(true)] out Employee? employee) { employee = GetEmployees().FirstOrDefault(e => e.Id == id); return employee != null; diff --git a/aspnetcore-webapi/ReturnTypes/APIReturnType/IFakeRepository.cs b/aspnetcore-webapi/ReturnTypes/APIReturnType/IFakeRepository.cs index 43110e675b..6a01cad6f7 100644 --- a/aspnetcore-webapi/ReturnTypes/APIReturnType/IFakeRepository.cs +++ b/aspnetcore-webapi/ReturnTypes/APIReturnType/IFakeRepository.cs @@ -1,10 +1,12 @@ -namespace APIReturnType +using System.Diagnostics.CodeAnalysis; + +namespace APIReturnType { public interface IFakeRepository { public IEnumerable GetEmployees(); - public bool TryGetEmployee(int id, out Employee? employee); + public bool TryGetEmployee(int id, [NotNullWhen(true)] out Employee? employee); public IEnumerable GetActiveEmployees(); diff --git a/aspnetcore-webapi/ReturnTypes/APIReturnType/Program.cs b/aspnetcore-webapi/ReturnTypes/APIReturnType/Program.cs index 5d43572f2b..47d08c73ec 100644 --- a/aspnetcore-webapi/ReturnTypes/APIReturnType/Program.cs +++ b/aspnetcore-webapi/ReturnTypes/APIReturnType/Program.cs @@ -6,9 +6,7 @@ // Add services to the container. builder.Services.AddControllers(); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +builder.Services.AddOpenApi(); builder.Services.AddSingleton(); @@ -17,8 +15,7 @@ // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { - app.UseSwagger(); - app.UseSwaggerUI(); + app.MapOpenApi(); } app.UseHttpsRedirection(); diff --git a/aspnetcore-webapi/ReturnTypes/Tests/Tests/Tests.csproj b/aspnetcore-webapi/ReturnTypes/Tests/Tests/Tests.csproj index 196a9e29bd..a25fabdc58 100644 --- a/aspnetcore-webapi/ReturnTypes/Tests/Tests/Tests.csproj +++ b/aspnetcore-webapi/ReturnTypes/Tests/Tests/Tests.csproj @@ -1,21 +1,21 @@ - net6.0 + net10.0 enable false - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all