From 1d58da284e3bcc8e115aebd85fd556f6115266c3 Mon Sep 17 00:00:00 2001 From: William Tyler Gibbons Date: Thu, 1 Aug 2024 17:18:50 -0400 Subject: [PATCH 1/2] Add support for mapping Result.Created to a Created ActionResult with setting of the location header --- .../Services/WeatherService.cs | 13 +++++++++++++ .../ActionResultExtensions.cs | 11 +++++++++++ src/Ardalis.Result.AspNetCore/ResultConvention.cs | 3 ++- src/Ardalis.Result.AspNetCore/ResultStatusMap.cs | 1 + src/Ardalis.Result/IResult.cs | 1 + 5 files changed, 28 insertions(+), 1 deletion(-) diff --git a/sample/Ardalis.Result.Sample.Core/Services/WeatherService.cs b/sample/Ardalis.Result.Sample.Core/Services/WeatherService.cs index 3199f29..f4f13c9 100644 --- a/sample/Ardalis.Result.Sample.Core/Services/WeatherService.cs +++ b/sample/Ardalis.Result.Sample.Core/Services/WeatherService.cs @@ -56,6 +56,19 @@ public Result> GetForecast(ForecastRequestDto model ErrorMessage = "PostalCode is required" } }); } + + // Test value to return Created result with a location + if (model.PostalCode == "12345") + { + return Result>.Created( + Enumerable.Range(1, 1).Select(index => + new WeatherForecast + { + Date = DateTime.Now, + TemperatureC = 0, + Summary = Summaries[0] + }), "weatherforecast/12345"); + } // test value if (model.PostalCode == "55555") diff --git a/src/Ardalis.Result.AspNetCore/ActionResultExtensions.cs b/src/Ardalis.Result.AspNetCore/ActionResultExtensions.cs index 24b7121..35acaab 100644 --- a/src/Ardalis.Result.AspNetCore/ActionResultExtensions.cs +++ b/src/Ardalis.Result.AspNetCore/ActionResultExtensions.cs @@ -74,6 +74,17 @@ internal static ActionResult ToActionResult(this ControllerBase controller, IRes return typeof(Result).IsInstanceOfType(result) ? (ActionResult)controller.StatusCode(statusCode) : controller.StatusCode(statusCode, result.GetValue()); + case ResultStatus.Created: + if(string.IsNullOrEmpty(result.Location)) + return controller.Created((string?)null, result.GetValue()); + + var httpRequest = controller.HttpContext.Request; + var locationUri = new UriBuilder(httpRequest.Scheme, + httpRequest.Host.Host, + httpRequest.Host.Port ?? -1, + result.Location).Uri.AbsoluteUri; + + return controller.Created(locationUri, result.GetValue()); default: return resultStatusOptions.ResponseType == null ? (ActionResult)controller.StatusCode(statusCode) diff --git a/src/Ardalis.Result.AspNetCore/ResultConvention.cs b/src/Ardalis.Result.AspNetCore/ResultConvention.cs index 9bd2e75..c6dc81d 100644 --- a/src/Ardalis.Result.AspNetCore/ResultConvention.cs +++ b/src/Ardalis.Result.AspNetCore/ResultConvention.cs @@ -81,7 +81,8 @@ So we should avoid pairing 'void' Result with OK or Created var resultStatuses = attr?.ResultStatuses ?? _map.Keys; - foreach (var status in resultStatuses.Where(s => s != ResultStatus.Ok)) + foreach (var status in resultStatuses.Where(s => + s is not (ResultStatus.Ok or ResultStatus.Created))) { var info = _map[status]; AddProducesResponseTypeAttribute(action.Filters, (int)info.GetStatusCode(method), info.ResponseType); diff --git a/src/Ardalis.Result.AspNetCore/ResultStatusMap.cs b/src/Ardalis.Result.AspNetCore/ResultStatusMap.cs index ff492bc..1ca44e3 100644 --- a/src/Ardalis.Result.AspNetCore/ResultStatusMap.cs +++ b/src/Ardalis.Result.AspNetCore/ResultStatusMap.cs @@ -26,6 +26,7 @@ internal ResultStatusMap() public ResultStatusMap AddDefaultMap() { return For(ResultStatus.Ok, HttpStatusCode.OK) + .For(ResultStatus.Created, HttpStatusCode.Created) .For(ResultStatus.Error, (HttpStatusCode)422, resultStatusOptions => resultStatusOptions .With(UnprocessableEntity)) .For(ResultStatus.Forbidden, HttpStatusCode.Forbidden) diff --git a/src/Ardalis.Result/IResult.cs b/src/Ardalis.Result/IResult.cs index 0f7026f..67a73b1 100644 --- a/src/Ardalis.Result/IResult.cs +++ b/src/Ardalis.Result/IResult.cs @@ -10,5 +10,6 @@ public interface IResult IEnumerable ValidationErrors { get; } Type ValueType { get; } object GetValue(); + string Location { get; } } } From ddbb6a96e886bb130657a1d3d9c2cf14f0543609 Mon Sep 17 00:00:00 2001 From: William Tyler Gibbons Date: Thu, 1 Aug 2024 17:18:58 -0400 Subject: [PATCH 2/2] Add Result constructor tests --- .../ResultConstructor.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Ardalis.Result.UnitTests/ResultConstructor.cs b/tests/Ardalis.Result.UnitTests/ResultConstructor.cs index 44deaf6..c509050 100644 --- a/tests/Ardalis.Result.UnitTests/ResultConstructor.cs +++ b/tests/Ardalis.Result.UnitTests/ResultConstructor.cs @@ -345,4 +345,20 @@ public void InitializesStatusToNoContentForNoContentFactoryCall() Assert.True(result.IsSuccess); } + + [Fact] + public void InitializedIsSuccessTrueForCreatedFactoryCall() + { + var result = Result.Created(new object()); + + Assert.True(result.IsSuccess); + } + + [Fact] + public void InitializedIsSuccessTrueForCreatedWithLocationFactoryCall() + { + var result = Result.Created(new object(), "sample/endpoint"); + + Assert.True(result.IsSuccess); + } }