From 60ffad2ea983e600ce598c19e2d2c637300b8a97 Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Sun, 28 Jun 2026 10:45:15 +0200 Subject: [PATCH] (feature): add redirect handling in Result.ToActionResult --- CHANGELOG.md | 9 +++++++++ README.md | 1 + assets/badges/coverage-branches.svg | 14 +++++++------- assets/badges/coverage-lines.svg | 8 ++++---- assets/badges/coverage-methods.svg | 8 ++++---- .../MaksIT.Results.Tests.csproj | 8 ++++---- .../ResultToActionResultTests.cs | 11 +++++++++++ src/MaksIT.Results/MaksIT.Results.csproj | 8 ++++---- src/MaksIT.Results/Mvc/RedirectResult.cs | 12 ++++++++++++ src/MaksIT.Results/Result.cs | 15 +++++++++++++-- 10 files changed, 69 insertions(+), 25 deletions(-) create mode 100644 src/MaksIT.Results/Mvc/RedirectResult.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 31bb445..8e4d8e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.3] - 2026-06-28 + +### Added +- `Result.ToActionResult()` now returns `RedirectResult` when the status code is a redirection (300–303, 307–308) and `Value` is a non-empty URL string. +- Added `MaksIT.Results.Mvc.RedirectResult` for HTTP redirect responses. + +### Changed +- Updated ASP.NET Core and Microsoft.Extensions package references. + ## [2.0.2] - 2026-06-02 ### Changed diff --git a/README.md b/README.md index 40cfeaa..2a3e694 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ public sealed record UserDto(Guid Id, string Name); - `Result` success: returns status-code-only response. - `Result` success with non-null `Value`: returns JSON body + status code. +- `Result` success with a redirection status code (300–303, 307–308) and a non-empty string `Value`: returns an HTTP redirect to that URL. - Any failure: returns RFC 7807-style `ProblemDetails` JSON with: - `status` = result status code - `title` = `"An error occurred"` diff --git a/assets/badges/coverage-branches.svg b/assets/badges/coverage-branches.svg index b41636a..c67365a 100644 --- a/assets/badges/coverage-branches.svg +++ b/assets/badges/coverage-branches.svg @@ -1,21 +1,21 @@ - - Branch Coverage: 65.4% + + Branch Coverage: 75% - + - - + + Branch Coverage - - 65.4% + + 75% diff --git a/assets/badges/coverage-lines.svg b/assets/badges/coverage-lines.svg index 916ab70..9f61820 100644 --- a/assets/badges/coverage-lines.svg +++ b/assets/badges/coverage-lines.svg @@ -1,5 +1,5 @@ - - Line Coverage: 17.6% + + Line Coverage: 19.8% @@ -15,7 +15,7 @@ Line Coverage - - 17.6% + + 19.8% diff --git a/assets/badges/coverage-methods.svg b/assets/badges/coverage-methods.svg index 8c5bdaa..c3f859c 100644 --- a/assets/badges/coverage-methods.svg +++ b/assets/badges/coverage-methods.svg @@ -1,5 +1,5 @@ - - Method Coverage: 17.8% + + Method Coverage: 19.9% @@ -15,7 +15,7 @@ Method Coverage - - 17.8% + + 19.9% diff --git a/src/MaksIT.Results.Tests/MaksIT.Results.Tests.csproj b/src/MaksIT.Results.Tests/MaksIT.Results.Tests.csproj index b1d7b32..9e6ed35 100644 --- a/src/MaksIT.Results.Tests/MaksIT.Results.Tests.csproj +++ b/src/MaksIT.Results.Tests/MaksIT.Results.Tests.csproj @@ -14,10 +14,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/MaksIT.Results.Tests/ResultToActionResultTests.cs b/src/MaksIT.Results.Tests/ResultToActionResultTests.cs index 266d323..9bdc022 100644 --- a/src/MaksIT.Results.Tests/ResultToActionResultTests.cs +++ b/src/MaksIT.Results.Tests/ResultToActionResultTests.cs @@ -45,4 +45,15 @@ public class ResultToActionResultTests { Assert.Equal((int)HttpStatusCode.OK, objectResult.StatusCode); Assert.Equal(value, objectResult.Value); } + + [Fact] + public void ToActionResult_WhenGenericSuccessWithRedirect_ReturnsRedirectResult() { + var result = Result.Found("https://example.com/continue"); + + var actionResult = result.ToActionResult(); + + Assert.IsType(actionResult); + var redirectResult = (RedirectResult)actionResult; + Assert.Equal("https://example.com/continue", redirectResult.Location); + } } diff --git a/src/MaksIT.Results/MaksIT.Results.csproj b/src/MaksIT.Results/MaksIT.Results.csproj index 7ad4bdf..4d28c13 100644 --- a/src/MaksIT.Results/MaksIT.Results.csproj +++ b/src/MaksIT.Results/MaksIT.Results.csproj @@ -8,7 +8,7 @@ MaksIT.Results - 2.0.2 + 2.0.3 Maksym Sadovnychyy MAKS-IT MaksIT.Results @@ -22,9 +22,9 @@ - - - + + + diff --git a/src/MaksIT.Results/Mvc/RedirectResult.cs b/src/MaksIT.Results/Mvc/RedirectResult.cs new file mode 100644 index 0000000..189195b --- /dev/null +++ b/src/MaksIT.Results/Mvc/RedirectResult.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; + +namespace MaksIT.Results.Mvc; + +public class RedirectResult(string location) : IActionResult { + public string Location { get; } = location; + + public Task ExecuteResultAsync(ActionContext context) { + context.HttpContext.Response.Redirect(Location); + return Task.CompletedTask; + } +} diff --git a/src/MaksIT.Results/Result.cs b/src/MaksIT.Results/Result.cs index 2f6b675..b281ce6 100644 --- a/src/MaksIT.Results/Result.cs +++ b/src/MaksIT.Results/Result.cs @@ -78,13 +78,24 @@ public partial class Result : Result { /// IActionResult that represents the HTTP response. public override IActionResult ToActionResult() { if (IsSuccess) { - if (Value is not null) { + if (IsRedirectionStatusCode(StatusCode) && Value is string location && !string.IsNullOrWhiteSpace(location)) + return new RedirectResult(location); + + if (Value is not null) return new ObjectResult(Value) { StatusCode = (int)StatusCode }; - } + return base.ToActionResult(); } else { return base.ToActionResult(); } } + + private static bool IsRedirectionStatusCode(HttpStatusCode statusCode) => + statusCode is HttpStatusCode.MultipleChoices + or HttpStatusCode.MovedPermanently + or HttpStatusCode.Found + or HttpStatusCode.SeeOther + or HttpStatusCode.TemporaryRedirect + or HttpStatusCode.PermanentRedirect; }