From 76358d19e820a6533e69b47d5ef27e427808f492 Mon Sep 17 00:00:00 2001 From: Alex Klaus Date: Tue, 27 Dec 2022 17:29:36 +1000 Subject: [PATCH 1/6] [56] Added implicit conversion of (T, IDomainResult) to IDomainResult --- src/Common/DomainResultOfT.cs | 8 +++++++- .../DomainResultConversionExtensionsTests.cs | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Common/DomainResultOfT.cs b/src/Common/DomainResultOfT.cs index 4b0ce1e..c68f6ff 100644 --- a/src/Common/DomainResultOfT.cs +++ b/src/Common/DomainResultOfT.cs @@ -66,8 +66,14 @@ public bool TryGetValue([MaybeNullWhen(false)] out TValue value) /// Implicitly converts the specified to an /// /// The parameter for conversion - public static implicit operator DomainResult(TValue value) => new DomainResult(value); + public static implicit operator DomainResult(TValue value) => new (value); + /// + /// Implicitly converts a tuple to an + /// + /// The value and domain operation result for conversion + public static implicit operator DomainResult((TValue value, IDomainResult domainResult) domainResultWithValue) => new (domainResultWithValue.value, domainResultWithValue.domainResult); + // TODO: Consider to deprecate the extension methods in this class (below) in favour of ones in 'DomainResult' #region Extensions of 'IDomainResult' [STATIC, PUBLIC] ------------- diff --git a/tests/DomainResults.Tests/Common/DomainResultConversionExtensionsTests.cs b/tests/DomainResults.Tests/Common/DomainResultConversionExtensionsTests.cs index e14c16a..af43118 100644 --- a/tests/DomainResults.Tests/Common/DomainResultConversionExtensionsTests.cs +++ b/tests/DomainResults.Tests/Common/DomainResultConversionExtensionsTests.cs @@ -100,4 +100,18 @@ public async Task Errored_IDomainResultOfT_Task_Converts_To_IDomainResultOfV_Tas Assert.Equal("Bla", domainResultOfT.Errors.Single()); } #endregion + + #region Tests of converting (TValue, IDomainResult) tuple to IDomainResult + + [Fact] + public void IDomainResult_and_Value_Tuple_Implicitly_Converted_To_IDomainResultOfT() + { + var domainResult = DomainResult.Failed("Bla"); + DomainResult domainResultOfT = (default, domainResult); + + Assert.False(domainResultOfT.IsSuccess); + Assert.Equal("Bla", domainResultOfT.Errors.Single()); + Assert.Equal(0, domainResultOfT.Value); + } + #endregion } From 5d74e53fe5f2adafadfc871d470dc9ff1decd5ad Mon Sep 17 00:00:00 2001 From: Alex Klaus Date: Wed, 28 Dec 2022 12:44:14 +1000 Subject: [PATCH 2/6] Added type conversion info --- README.md | 21 +++++++++++++++++-- .../DomainResultConversionExtensions.cs | 3 +-- src/Common/DomainResultOfT.cs | 4 ++-- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4020807..000fcaf 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ Two tiny NuGet packages addressing challenges in the [ASP.NET Web API](https://d - [Basic use-case](#basic-use-case) - [Quick start](#quick-start) - ['DomainResult.Common' package. Returning result from Domain Layer method](#domainresultcommon-package-returning-result-from-domain-layer-method) - - [Examples (Domain)](#examples-domain) + - [Examples (Domain layer)](#examples-domain-layer) + - [Type conversion](#type-conversion) - ['DomainResult' package](#domainresult-package) - [Conversion to IActionResult](#conversion-to-iactionresult) - [Examples (IActionResult conversion)](#examples-iactionresult-conversion) @@ -154,7 +155,7 @@ It has **50+ static extension methods** to return a successful or unsuccessful r | `IDomainResult` | `Task>` | | `(T, IDomainResult)` | `Task<(T, IDomainResult)>` | -### Examples (Domain): +### Examples (Domain layer): ```cs // Successful result with no value @@ -184,6 +185,22 @@ _Notes_: - The `Task` suffix on the extension methods indicates that the returned type is wrapped in a `Task` (e.g. `SuccessTask()`, `FailedTask()`, `NotFoundTask()`, `UnauthorizedTask()`). - The `Failed()` and `NotFound()` methods take as input parameters: `string`, `string[]`. `Failed()` can also take [ValidationResult](https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.validationresult). +### Type conversion +Type conversion comes in handy for propagating errors from nested method calls, e.g. from `IDomainResult` to `IDomainResult`, or the other way around, etc. + +```cs +IDomainResult failedResult = IDomainResult.Failed("Ahh!"); + +IDomainResult resOfInt = failedResult.To(); // from IDomainResult to IDomainResult +IDomainResult resOfLong = resOfInt.To(); // from IDomainResult to IDomainResult + +DomainResult resFromTuple = (default, failedResult); // from IDomainResult to DomainResult + +Task failedResultTask = IDomainResult.FailedTask("Ahh!"); +Task> resOfInt = failedResultTask.To(); // from Task to Task> +``` +Note that returning [Tuple](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-tuples) types drastically simplifies type conversions. + ## 'DomainResult' package **Converts a `IDomainResult`-based object to various `IActionResult` and `IResult`-based types providing 40+ static extension methods.** diff --git a/src/Common/DomainResultConversionExtensions.cs b/src/Common/DomainResultConversionExtensions.cs index f7d3f57..ddfd923 100644 --- a/src/Common/DomainResultConversionExtensions.cs +++ b/src/Common/DomainResultConversionExtensions.cs @@ -17,8 +17,7 @@ public static IDomainResult To(this IDomainResult domainResult) } /// - /// Convert to a of (the domain operation result with a returned - /// value) + /// Convert to a of (the domain operation result with a returned value) /// /// Value type of returned by the domain operation public static async Task> To(this Task domainResult) diff --git a/src/Common/DomainResultOfT.cs b/src/Common/DomainResultOfT.cs index c68f6ff..70eee3a 100644 --- a/src/Common/DomainResultOfT.cs +++ b/src/Common/DomainResultOfT.cs @@ -69,11 +69,11 @@ public bool TryGetValue([MaybeNullWhen(false)] out TValue value) public static implicit operator DomainResult(TValue value) => new (value); /// - /// Implicitly converts a tuple to an + /// Implicitly converts a (TValue value, IDomainResult domainResult) tuple to an /// /// The value and domain operation result for conversion public static implicit operator DomainResult((TValue value, IDomainResult domainResult) domainResultWithValue) => new (domainResultWithValue.value, domainResultWithValue.domainResult); - + // TODO: Consider to deprecate the extension methods in this class (below) in favour of ones in 'DomainResult' #region Extensions of 'IDomainResult' [STATIC, PUBLIC] ------------- From deb7e5a25d7239ef1eb5201ef33c83961963cdb2 Mon Sep 17 00:00:00 2001 From: Alex Klaus Date: Wed, 28 Dec 2022 13:14:01 +1000 Subject: [PATCH 3/6] [59] Updated NuGet packages --- samples/WebApi/Examples.WebApi.csproj | 2 +- samples/WebApiMinimal/Examples.WebApiMinimal.csproj | 2 +- src/Directory.Build.props | 2 +- tests/DomainResults.Tests/DomainResults.Tests.csproj | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/samples/WebApi/Examples.WebApi.csproj b/samples/WebApi/Examples.WebApi.csproj index ecb7b1f..3468e04 100644 --- a/samples/WebApi/Examples.WebApi.csproj +++ b/samples/WebApi/Examples.WebApi.csproj @@ -6,7 +6,7 @@ - + diff --git a/samples/WebApiMinimal/Examples.WebApiMinimal.csproj b/samples/WebApiMinimal/Examples.WebApiMinimal.csproj index 838fc92..cb7a67e 100644 --- a/samples/WebApiMinimal/Examples.WebApiMinimal.csproj +++ b/samples/WebApiMinimal/Examples.WebApiMinimal.csproj @@ -11,6 +11,6 @@ - + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e84e1fc..0fbd735 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -42,7 +42,7 @@ - + diff --git a/tests/DomainResults.Tests/DomainResults.Tests.csproj b/tests/DomainResults.Tests/DomainResults.Tests.csproj index ad51e7c..cd52a12 100644 --- a/tests/DomainResults.Tests/DomainResults.Tests.csproj +++ b/tests/DomainResults.Tests/DomainResults.Tests.csproj @@ -7,17 +7,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive From 60631238c42a734fc8780c10d09fd57e5ba9179b Mon Sep 17 00:00:00 2001 From: Alex Klaus Date: Wed, 28 Dec 2022 18:32:09 +1000 Subject: [PATCH 4/6] [53] Added support of .NET 7 --- samples/Directory.Build.props | 2 +- src/Mvc/DomainResults.Mvc.csproj | 2 +- src/Mvc/IResult/DomainResultTo200OkResult.cs | 3 +- .../DomainResultTo204NoContentResult.cs | 3 +- src/Mvc/IResult/DomainResultToCustomResult.cs | 12 +- src/Mvc/IResult/DomainResultToResult.cs | 5 +- .../DomainResults.Tests.csproj | 2 +- .../Mvc/ActionResultConventionsTests.cs | 12 +- .../Mvc/ResultExtension.cs | 125 ++++++++++++++---- .../Mvc/To200OkResultDomainResultTests.cs | 14 +- .../Mvc/To200OkResultTupleValueTests.cs | 18 ++- .../Mvc/To4xxActionResultTests.cs | 10 +- .../Mvc/ToCustomActionResultSuccessTests.cs | 10 +- 13 files changed, 135 insertions(+), 83 deletions(-) diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props index ec51988..4f782c4 100644 --- a/samples/Directory.Build.props +++ b/samples/Directory.Build.props @@ -3,7 +3,7 @@ - net6.0 + net7.0 latest enable strict diff --git a/src/Mvc/DomainResults.Mvc.csproj b/src/Mvc/DomainResults.Mvc.csproj index 417747a..3d5f139 100644 --- a/src/Mvc/DomainResults.Mvc.csproj +++ b/src/Mvc/DomainResults.Mvc.csproj @@ -6,7 +6,7 @@ To be used in Web API projects to accompany 'DomainResult.Common' package used i - netcoreapp3.1;net5.0;net6.0 + netcoreapp3.1;net5.0;net6.0;net7.0 DomainResults.Mvc DomainResults.Mvc diff --git a/src/Mvc/IResult/DomainResultTo200OkResult.cs b/src/Mvc/IResult/DomainResultTo200OkResult.cs index fdd4977..4912ff7 100644 --- a/src/Mvc/IResult/DomainResultTo200OkResult.cs +++ b/src/Mvc/IResult/DomainResultTo200OkResult.cs @@ -7,7 +7,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http; -// ReSharper disable once CheckNamespace +// ReSharper disable InconsistentNaming + namespace DomainResults.Mvc; public static partial class DomainResultExtensions diff --git a/src/Mvc/IResult/DomainResultTo204NoContentResult.cs b/src/Mvc/IResult/DomainResultTo204NoContentResult.cs index 31c37d9..b50d56e 100644 --- a/src/Mvc/IResult/DomainResultTo204NoContentResult.cs +++ b/src/Mvc/IResult/DomainResultTo204NoContentResult.cs @@ -7,7 +7,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http; -// ReSharper disable once CheckNamespace +// ReSharper disable InconsistentNaming + namespace DomainResults.Mvc; public static partial class DomainResultExtensions diff --git a/src/Mvc/IResult/DomainResultToCustomResult.cs b/src/Mvc/IResult/DomainResultToCustomResult.cs index 14579bc..24d62cb 100644 --- a/src/Mvc/IResult/DomainResultToCustomResult.cs +++ b/src/Mvc/IResult/DomainResultToCustomResult.cs @@ -7,7 +7,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http; -// ReSharper disable once CheckNamespace +// ReSharper disable InconsistentNaming + namespace DomainResults.Mvc; public static partial class DomainResultExtensions @@ -30,7 +31,10 @@ public static IResult ToCustomResult(this (V, R) domainResult, Action? errorAction = null) where R : IDomainResultBase where TResult : IResult - => ToResult(domainResult.Item1, domainResult.Item2, errorAction, valueToResultFunc); + { + var (value, res) = domainResult; + return ToResult(value, res, errorAction, valueToResultFunc); + } /// /// Custom conversion of successful and unsuccessful domain results to types @@ -47,8 +51,8 @@ public static async Task ToCustomResult(this Task<(V, R) where R : IDomainResultBase where TResult : IResult { - var domainResult = await domainResultTask; - return ToResult(domainResult.Item1, domainResult.Item2, errorAction, valueToResultFunc); + var (value, res) = await domainResultTask; + return ToResult(value, res, errorAction, valueToResultFunc); } /// diff --git a/src/Mvc/IResult/DomainResultToResult.cs b/src/Mvc/IResult/DomainResultToResult.cs index 6d3e027..b260529 100644 --- a/src/Mvc/IResult/DomainResultToResult.cs +++ b/src/Mvc/IResult/DomainResultToResult.cs @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http; -// ReSharper disable once CheckNamespace +// ReSharper disable InconsistentNaming namespace DomainResults.Mvc; // @@ -44,7 +44,7 @@ private static IResult ToResult([AllowNull] V value, DomainOperationStatus.CriticalDependencyError => SadResult(HttpCodeConvention.CriticalDependencyErrorHttpCode, HttpCodeConvention.CriticalDependencyErrorProblemDetailsTitle, errorDetails, errorAction), DomainOperationStatus.Success => EqualityComparer.Default.Equals(value!, default!) - ? Results.NoContent() // No value, means returning HTTP status 204 + ? Results.NoContent() // No value, means returning HTTP status 204. For .NET 7 `Results.NoContent` will return `TypedResults.NoContent` : valueToResultFunc(value), _ => throw new ArgumentOutOfRangeException(), }; @@ -65,6 +65,7 @@ private static IResult SadResult(int statusCode, string title, R? errorDetail }; errorAction?.Invoke(problemDetails, errorDetails!); + // For .NET 7 `Results.Problem` will return `TypedResults.Problem` return Results.Problem( problemDetails.Detail, null, diff --git a/tests/DomainResults.Tests/DomainResults.Tests.csproj b/tests/DomainResults.Tests/DomainResults.Tests.csproj index ad51e7c..526be9a 100644 --- a/tests/DomainResults.Tests/DomainResults.Tests.csproj +++ b/tests/DomainResults.Tests/DomainResults.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1;net5.0;net6.0 + netcoreapp3.1;net5.0;net6.0;net7.0 false diff --git a/tests/DomainResults.Tests/Mvc/ActionResultConventionsTests.cs b/tests/DomainResults.Tests/Mvc/ActionResultConventionsTests.cs index aeae8b0..cafff5b 100644 --- a/tests/DomainResults.Tests/Mvc/ActionResultConventionsTests.cs +++ b/tests/DomainResults.Tests/Mvc/ActionResultConventionsTests.cs @@ -39,8 +39,7 @@ public void FailedHttpCode_Is_Honoured_in_Failed_Response_Test(int? failedHttpCo Assert.Equal(expectedFailedHttpCode, (actionRes.Value as ProblemDetails)!.Status); #if NET6_0_OR_GREATER // for the IResult (minimal API) conversion - res.AssertObjectResultType(); - Assert.Equal(expectedFailedHttpCode, res.GetProblemDetails()!.Status); + res.AssertObjectResultTypeWithProblemDetails(expectedFailedHttpCode); #endif HttpCodeConvention.FailedHttpCode = defaultValue; @@ -72,8 +71,7 @@ public void FailedProblemDetailsTitle_Is_Honoured_in_Error_Response_Test(string Assert.Equal(expectedFailedTitle, problemDetails!.Title); #if NET6_0_OR_GREATER // for the IResult (minimal API) conversion - res.AssertObjectResultType(); - Assert.Equal(expectedFailedTitle, res.GetProblemDetails()!.Title); + res.AssertObjectResultTypeWithProblemDetails(HttpCodeConvention.FailedHttpCode, expectedFailedTitle); #endif HttpCodeConvention.FailedProblemDetailsTitle = defaultValue; @@ -105,8 +103,7 @@ public void NotFoundHttpCode_Is_Honoured_in_NotFound_Response_Test(int? notFound Assert.Equal(expectedNotFoundHttpCode, (actionRes.Value as ProblemDetails)!.Status); #if NET6_0_OR_GREATER // for the IResult (minimal API) conversion - res.AssertObjectResultType(); - Assert.Equal(expectedNotFoundHttpCode, res.GetProblemDetails()!.Status); + res.AssertObjectResultTypeWithProblemDetails(expectedNotFoundHttpCode); #endif HttpCodeConvention.NotFoundHttpCode = defaultValue; @@ -137,8 +134,7 @@ public void NotFoundHttpDetailsTitle_Is_Honoured_in_NotFound_Response_Test(strin Assert.Equal(expectedNotFoundTitle, problemDetails!.Title); #if NET6_0_OR_GREATER // for the IResult (minimal API) conversion - res.AssertObjectResultType(); - Assert.Equal(expectedNotFoundTitle, res.GetProblemDetails()!.Title); + res.AssertObjectResultTypeWithProblemDetails(HttpCodeConvention.NotFoundHttpCode, expectedNotFoundTitle); #endif HttpCodeConvention.NotFoundProblemDetailsTitle = defaultValue; diff --git a/tests/DomainResults.Tests/Mvc/ResultExtension.cs b/tests/DomainResults.Tests/Mvc/ResultExtension.cs index 898cfe0..b442471 100644 --- a/tests/DomainResults.Tests/Mvc/ResultExtension.cs +++ b/tests/DomainResults.Tests/Mvc/ResultExtension.cs @@ -12,10 +12,102 @@ namespace DomainResults.Tests.Mvc; public static class ResultExtension { /// - /// Extracts from + /// Assert that the instance represent an internal 'ObjectResult' class with a 'ProblemDetails' + /// + public static void AssertObjectResultTypeWithProblemDetails(this IResult res, int expectedHttpStatus, string? expectedTitle = null, string? expectedDetail = null) + { + // Assert on the status code +#if NET6_0 + Assert.Equal("Microsoft.AspNetCore.Http.Result.ObjectResult", res.GetType().FullName); + // NOTE that we don't check Microsoft.AspNetCore.Http.Result.ObjectResult.StatusCode property as it always remains NULL. + Assert.Equal(expectedHttpStatus, res.GetProblemDetails()!.Status); +#elif NET7_0_OR_GREATER + Assert.Equal(expectedHttpStatus, (res as IStatusCodeHttpResult)?.StatusCode); + var problemDetails = (res as IValueHttpResult)?.Value as ProblemDetails; + Assert.Equal(expectedHttpStatus, problemDetails?.Status); +#endif + // Assert on the title if provided + if (!string.IsNullOrEmpty(expectedTitle)) +#if NET6_0 + Assert.Equal(expectedTitle, res.GetProblemDetails()!.Title); +#elif NET7_0_OR_GREATER + Assert.Equal(expectedTitle, problemDetails?.Title); +#endif + // Assert on the error details if provided + if (!string.IsNullOrEmpty(expectedDetail)) +#if NET6_0 + Assert.Equal(expectedDetail, res.GetProblemDetails()!.Detail); +#elif NET7_0_OR_GREATER + Assert.Equal(expectedDetail, problemDetails?.Detail); +#endif + } + + /// + /// Assert that the instance represent an internal 'OkObjectResult' class with correct value + /// + public static void AssertOkObjectResultTypeAndValue(this IResult res, TValue expectedValue) + { + // Assert on 200 OK response type +#if NET6_0 + Assert.Equal("Microsoft.AspNetCore.Http.Result.OkObjectResult", res.GetType().FullName); + Assert.Equal(200, res.GetPropValue("StatusCode")); +#elif NET7_0_OR_GREATER + var resTyped = res as Microsoft.AspNetCore.Http.HttpResults.Ok; + Assert.NotNull(resTyped); + Assert.Equal(200, resTyped!.StatusCode); +#endif + // Assert on the expected value +#if NET6_0 + Assert.Equal(expectedValue, res.GetPropValue()); +#elif NET7_0_OR_GREATER + Assert.Equal(expectedValue, resTyped.Value); +#endif + } + + /// + /// Assert that the instance represent an internal 'NoContentResult' class + /// + public static void AssertNoContentResultType(this IResult res) + { +#if NET6_0 + Assert.Equal("Microsoft.AspNetCore.Http.Result.NoContentResult", res.GetType().FullName); + Assert.Equal(204, res.GetPropValue("StatusCode")); +#elif NET7_0_OR_GREATER + var resTyped = res as Microsoft.AspNetCore.Http.HttpResults.NoContent; + Assert.NotNull(resTyped); + Assert.Equal(204, resTyped!.StatusCode); +#endif + } + + /// + /// Assert that the instance represent an internal 'CreatedResult' class /// - public static ProblemDetails? GetProblemDetails(this IResult res) => res.GetPropValue() as ProblemDetails; + public static void AssertCreatedResultTypeAndValueAndLocation(this IResult res, TValue expectedValue, string expectedLocation) + { + // Assert on 201 Created response type +#if NET6_0 + Assert.Equal("Microsoft.AspNetCore.Http.Result.CreatedResult", res.GetType().FullName); + Assert.Equal(201, res.GetPropValue("StatusCode")); +#elif NET7_0_OR_GREATER + var resTyped = res as Microsoft.AspNetCore.Http.HttpResults.Created; + Assert.NotNull(resTyped); + Assert.Equal(201, resTyped!.StatusCode); +#endif + // Assert on the expected value +#if NET6_0 + Assert.Equal(expectedValue, res.GetPropValue()); +#elif NET7_0_OR_GREATER + Assert.Equal(expectedValue, resTyped.Value); +#endif + // Assert on the expected location +#if NET6_0 + Assert.Equal(expectedLocation, res.GetPropValue("Location")); +#elif NET7_0_OR_GREATER + Assert.Equal(expectedLocation, resTyped.Location); +#endif + } +#if NET6_0 /// /// Extracts value from the specified property of instance /// @@ -25,35 +117,14 @@ public static class ResultExtension /// Gotta use reflection as Microsoft.AspNetCore.Http.Result.ObjectResult type is internal. /// Also, we can't use 'dynamic' casting due to https://stackoverflow.com/q/15016561/968003 /// - public static object? GetPropValue(this IResult res, string propName = "Value") + private static object? GetPropValue(this IResult res, string propName = "Value") => res.GetType().GetProperty(propName)?.GetValue(res); /// - /// Assert that the instance represent an internal 'ObjectResult' class - /// - public static void AssertObjectResultType(this IResult res) - => Assert.Equal("Microsoft.AspNetCore.Http.Result.ObjectResult", res.GetType().FullName); - - /// - /// Assert that the instance represent an internal 'OkObjectResult' class - /// - public static void AssertOkObjectResultType(this IResult res) - => Assert.Equal("Microsoft.AspNetCore.Http.Result.OkObjectResult", res.GetType().FullName); - - /// - /// Assert that the instance represent an internal 'NoContentResult' class - /// - public static void AssertNoContentResultType(this IResult res) - => Assert.Equal("Microsoft.AspNetCore.Http.Result.NoContentResult", res.GetType().FullName); - - /// - /// Assert that the instance represent an internal 'CreatedResult' class + /// Extracts from /// - public static void AssertCreatedResultType(this IResult res) - => Assert.Equal("Microsoft.AspNetCore.Http.Result.CreatedResult", res.GetType().FullName); - - // NOTE that we don't check Microsoft.AspNetCore.Http.Result.ObjectResult.StatusCode property as it always remains NULL. - // TODO: Double check if ObjectResult.StatusCode remains NULL in .NET 7 as well. + private static ProblemDetails? GetProblemDetails(this IResult res) => res.GetPropValue() as ProblemDetails; +#endif } #endif diff --git a/tests/DomainResults.Tests/Mvc/To200OkResultDomainResultTests.cs b/tests/DomainResults.Tests/Mvc/To200OkResultDomainResultTests.cs index bd5e51c..965b9d1 100644 --- a/tests/DomainResults.Tests/Mvc/To200OkResultDomainResultTests.cs +++ b/tests/DomainResults.Tests/Mvc/To200OkResultDomainResultTests.cs @@ -40,11 +40,8 @@ public void DomainResult_With_Value_Converted_ToIResult_Test(IDomainResu // WHEN convert a value to IResult var res = domainValue.ToResult(); - // THEN the response type is correct - res.AssertOkObjectResultType(); - - // and value remains there - Assert.Equal(domainValue.Value!, res.GetPropValue()); + // THEN the response type OK with correct value + res.AssertOkObjectResultTypeAndValue(domainValue.Value!); } #endif public static readonly IEnumerable SuccessfulTestCases = GetTestCases(false); @@ -79,11 +76,8 @@ public async Task DomainResult_With_Value_Task_Converted_ToIResult_Test( // WHEN convert a value to IResult var res = await domainValueTask.ToResult(); - // THEN the response type is correct - res.AssertOkObjectResultType(); - - // and value remains there - Assert.Equal((await domainValueTask).Value!, res.GetPropValue()); + // THEN the response type OK with correct value + res.AssertOkObjectResultTypeAndValue((await domainValueTask).Value!); } #endif public static readonly IEnumerable SuccessfulTaskTestCases = GetTestCases(true); diff --git a/tests/DomainResults.Tests/Mvc/To200OkResultTupleValueTests.cs b/tests/DomainResults.Tests/Mvc/To200OkResultTupleValueTests.cs index 49a6ad8..b93befd 100644 --- a/tests/DomainResults.Tests/Mvc/To200OkResultTupleValueTests.cs +++ b/tests/DomainResults.Tests/Mvc/To200OkResultTupleValueTests.cs @@ -37,14 +37,13 @@ public void ValueResult_Converted_ToActionResult_Test((TValue, IDomainRe [MemberData(nameof(SuccessfulTestCases))] public void ValueResult_Converted_ToIResult_Test((TValue, IDomainResult) tupleValue) { + var (value, _) = tupleValue; + // WHEN convert a value to IResult var res = tupleValue.ToResult(); - // THEN the response type is correct - res.AssertOkObjectResultType(); - - // and value remains there - Assert.Equal(tupleValue.Item1!, res.GetPropValue()); + // THEN the response type OK with correct value + res.AssertOkObjectResultTypeAndValue(value); } #endif @@ -82,14 +81,13 @@ public async Task ValueResult_Task_Converted_ToActionResult_Test(Task<(T [MemberData(nameof(SuccessfulTaskTestCases))] public async Task ValueResult_Task_Converted_ToIResult_Test(Task<(TValue, IDomainResult)> tupleValueTask) { + var (value, _) = await tupleValueTask; + // WHEN convert a value to IResult var res = await tupleValueTask.ToResult(); - // THEN the response type is correct - res.AssertOkObjectResultType(); - - // and value remains there - Assert.Equal((await tupleValueTask).Item1!, res.GetPropValue()); + // THEN the response type OK with correct value + res.AssertOkObjectResultTypeAndValue(value); } #endif diff --git a/tests/DomainResults.Tests/Mvc/To4xxActionResultTests.cs b/tests/DomainResults.Tests/Mvc/To4xxActionResultTests.cs index a9a73e5..5bde260 100644 --- a/tests/DomainResults.Tests/Mvc/To4xxActionResultTests.cs +++ b/tests/DomainResults.Tests/Mvc/To4xxActionResultTests.cs @@ -144,14 +144,8 @@ private void Then_Response_Is_ActionResult_Type_And_ProblemDetails_StatusAndText /// The expected description messages in the private void Then_Response_Is_IResult_Type_And_ProblemDetails_StatusAndText_Correct(IResult res, int expectedCode, string expectedTitle, string expectedErrorMsg) { - // THEN the response type is correct - res.AssertObjectResultType(); - - // and the ProblemDetails properties are as expected - var problemDetails = res.GetProblemDetails(); - Assert.Equal(expectedCode, problemDetails!.Status); - Assert.Equal(expectedTitle, problemDetails.Title); - Assert.Equal(expectedErrorMsg, problemDetails.Detail); + // THEN the response type is correct and the ProblemDetails properties are as expected + res.AssertObjectResultTypeWithProblemDetails(expectedCode, expectedTitle, expectedErrorMsg); } #endif diff --git a/tests/DomainResults.Tests/Mvc/ToCustomActionResultSuccessTests.cs b/tests/DomainResults.Tests/Mvc/ToCustomActionResultSuccessTests.cs index 2c873cf..bf414f4 100644 --- a/tests/DomainResults.Tests/Mvc/ToCustomActionResultSuccessTests.cs +++ b/tests/DomainResults.Tests/Mvc/ToCustomActionResultSuccessTests.cs @@ -143,15 +143,7 @@ private void Then_Response_Is_ActionResult_Type_And_Value_And_Url_Are_Correct private void Then_Response_Is_IResult_Type_And_Value_And_Url_Are_Correct(IResult res, TValue expectedValue) { - // THEN the response type is correct - res.AssertCreatedResultType(); - - // and the HTTP code is 201 - Assert.Equal(201, res.GetPropValue("StatusCode")); - // and the value remains there - Assert.Equal(expectedValue, res.GetPropValue()); - // and the location URL is correct - Assert.Equal(ExpectedUrl, res.GetPropValue("Location")); + res.AssertCreatedResultTypeAndValueAndLocation(expectedValue, ExpectedUrl); } #endif From 37424c38b7a410956306f16d11bc7f4ab4915414 Mon Sep 17 00:00:00 2001 From: Alex Klaus Date: Wed, 28 Dec 2022 18:34:46 +1000 Subject: [PATCH 5/6] Added .NET7 to the CI pipeline --- .github/workflows/ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6514185..bb6ecc6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,10 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: 6.x + - name: Setup .NET 7 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 7.x - name: Install dependencies run: dotnet restore @@ -38,6 +42,8 @@ jobs: # Test each targeted framework independently to spot errors faster # Generate coverage report for the latest framework only, as coverage on multiple is not supported + - name: Test on .NET 7 + run: dotnet test --no-restore --verbosity normal --framework net7.0 /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov - name: Test on .NET 6 run: dotnet test --no-restore --verbosity normal --framework net6.0 /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov - name: Test on .NET 5 @@ -49,4 +55,4 @@ jobs: uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: ./tests/DomainResults.Tests/TestResults/coverage.net6.0.info + path-to-lcov: ./tests/DomainResults.Tests/TestResults/coverage.net7.0.info From c87e50e7b69af95e0523b2c7a3a4c3190d29784b Mon Sep 17 00:00:00 2001 From: Alex Klaus Date: Wed, 28 Dec 2022 18:35:55 +1000 Subject: [PATCH 6/6] Added .NET7 to the Release pipeline --- .github/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c61bfa6..97cfae0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,6 +27,10 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: 6.x + - name: Setup .NET 7 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 7.x - name: Install dependencies run: dotnet restore