From 5eeb7ac89bc52ea5596b40eff32162709e5c5427 Mon Sep 17 00:00:00 2001 From: "Eric Sibly [chullybun]" Date: Sat, 6 Jan 2024 11:18:59 -0800 Subject: [PATCH] IActionResult as-is and PagingArgs.Token. (#85) --- CHANGELOG.md | 4 ++ Common.targets | 2 +- samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj | 2 +- samples/My.Hr/My.Hr.Api/Startup.cs | 4 +- .../My.Hr.Database/My.Hr.Database.csproj | 2 +- .../My.Hr.Functions/My.Hr.Functions.csproj | 2 +- .../My.Hr.UnitTest/My.Hr.UnitTest.csproj | 2 +- .../Http/HttpResultExtensions.cs | 23 +++++++---- .../WebApis/PagingOperationFilter.cs | 9 +++-- .../WebApis/PagingOperationFilterFields.cs | 17 +++++++- .../WebApis/ValueContentResult.cs | 34 +++++++++------- src/CoreEx.AspNetCore/WebApis/WebApi.cs | 22 ++++------- .../WebApis/WebApiRequestOptions.cs | 11 ++++-- src/CoreEx.Cosmos/CoreEx.Cosmos.csproj | 2 +- .../CoreEx.FluentValidation.csproj | 2 +- src/CoreEx.OData/ODataQuery.cs | 5 ++- src/CoreEx.Solace/CoreEx.Solace.csproj | 2 +- .../CoreEx.UnitTesting.NUnit.csproj | 2 +- .../CoreEx.UnitTesting.csproj | 3 +- .../Json/ToCoreExJsonSerializerMapper.cs | 11 ++---- .../Json/ToUnitTestExJsonSerializerMapper.cs | 11 ++---- .../Abstractions/IQueryableExtensions.cs | 11 +++++- src/CoreEx/Entities/PagingArgs.cs | 39 ++++++++++++++----- src/CoreEx/Entities/PagingOption.cs | 25 ++++++++++++ src/CoreEx/Entities/PagingResult.cs | 22 ++++++++--- src/CoreEx/Http/HttpConsts.cs | 17 +++++++- src/CoreEx/Http/HttpExtensions.cs | 5 ++- src/CoreEx/Http/HttpRequestOptions.cs | 34 +++++++++++----- src/CoreEx/Json/Data/JsonDataReader.cs | 10 ++--- src/CoreEx/Json/Data/JsonDataReaderArgs.cs | 4 +- src/CoreEx/Text/Json/JsonSerializer.cs | 15 +++---- .../CoreEx.Cosmos.Test.csproj | 4 +- tests/CoreEx.Cosmos.Test/TestSetUp.cs | 2 +- .../CoreEx.Solace.Test.csproj | 2 +- tests/CoreEx.Test/CoreEx.Test.csproj | 4 +- .../Reflection/TypeReflectorTest.cs | 12 +++--- .../Configuration/SettingsBaseTest.cs | 4 +- .../Entities/Extended/EntityBaseTest.cs | 4 +- .../Framework/Entities/PagingArgsTest.cs | 36 +++++++++++++++++ .../EventSubscriberAttributeTest.cs | 2 +- .../Framework/Http/TypedHttpClientCoreTest.cs | 27 +++++++++++++ .../Framework/Json/JsonSerializerTest.cs | 8 ++-- .../Json/Merge/JsonMergePatchTest.cs | 22 +++++------ .../Converters/TypeToStringConverterTest.cs | 2 +- .../Framework/UnitTesting/AgentTest.cs | 14 +++++++ .../Framework/UnitTesting/ValidationTest.cs | 20 ++++++++++ .../Validation/Rules/CollectionRuleTest.cs | 14 +++---- .../Validation/Rules/MandatoryRuleTest.cs | 2 +- .../Validation/Rules/NoneRuleTest.cs | 2 +- .../Framework/Validation/ValidatorTest.cs | 8 ++-- .../WebApis/WebApiRequestOptionsTest.cs | 28 ++++++++++++- .../Framework/Wildcards/WildcardTest.cs | 16 ++++---- .../Controllers/ProductController.cs | 5 +++ .../Services/ProductService.cs | 8 ++++ 54 files changed, 429 insertions(+), 171 deletions(-) create mode 100644 src/CoreEx/Entities/PagingOption.cs create mode 100644 tests/CoreEx.Test/Framework/Entities/PagingArgsTest.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e155839..97a5399d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ Represents the **NuGet** versions. +## v3.8.0 +- *Enhancement*: The `ValueContentResult.CreateResult` has been updated to return the resulting value as-is where is an instance of `IActionResult`; otherwise, converts `value` to a `ValueContentResult` (previous behavior). +- *Enhancement*: The `PagingArgs` has been extended to support `Token`; being a continuation token to enable paging to be performed where the underlying data source does not support skip/take-style paging. + ## v3.7.2 - *Fixed*: The `ReferenceDataMultiCollection` and `ReferenceDataMultiItem` have been replaced with the `ReferenceDataMultiDictionary` as existing resulted in an unintended format with which to return the data. This fix also removed the need for the `ReferenceDataMultiCollectionConverterFactory` as custom serialization for this is no longer required. diff --git a/Common.targets b/Common.targets index 7f86637e..a8d7e3a6 100644 --- a/Common.targets +++ b/Common.targets @@ -1,6 +1,6 @@  - 3.7.2 + 3.8.0 preview Avanade Avanade diff --git a/samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj b/samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj index 3a3d617f..39ce851b 100644 --- a/samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj +++ b/samples/My.Hr/My.Hr.Api/My.Hr.Api.csproj @@ -13,7 +13,7 @@ - + diff --git a/samples/My.Hr/My.Hr.Api/Startup.cs b/samples/My.Hr/My.Hr.Api/Startup.cs index 72d72fd6..c396e21e 100644 --- a/samples/My.Hr/My.Hr.Api/Startup.cs +++ b/samples/My.Hr/My.Hr.Api/Startup.cs @@ -62,7 +62,7 @@ public void ConfigureServices(IServiceCollection services) if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_CONNECTION_STRING"))) { services.AddOpenTelemetry().UseAzureMonitor(); - services.Configure(options => options.RecordException = true); + //services.Configure(options => options.RecordException = true); services.ConfigureOpenTelemetryTracerProvider((sp, builder) => builder.AddSource("CoreEx.*")); } @@ -73,7 +73,7 @@ public void ConfigureServices(IServiceCollection services) var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); options.OperationFilter(); // Needed to support AcceptsBodyAttribue where body parameter not explicitly defined. - options.OperationFilter(PagingOperationFilterFields.PageSizeCount); // Needed to support PagingAttribue where PagingArgs parameter not explicitly defined. + options.OperationFilter(PagingOperationFilterFields.TokenTake); // Needed to support PagingAttribue where PagingArgs parameter not explicitly defined. }); } diff --git a/samples/My.Hr/My.Hr.Database/My.Hr.Database.csproj b/samples/My.Hr/My.Hr.Database/My.Hr.Database.csproj index ba306b63..00fccb9a 100644 --- a/samples/My.Hr/My.Hr.Database/My.Hr.Database.csproj +++ b/samples/My.Hr/My.Hr.Database/My.Hr.Database.csproj @@ -21,7 +21,7 @@ - + diff --git a/samples/My.Hr/My.Hr.Functions/My.Hr.Functions.csproj b/samples/My.Hr/My.Hr.Functions/My.Hr.Functions.csproj index ebe57aa7..71bbdff5 100644 --- a/samples/My.Hr/My.Hr.Functions/My.Hr.Functions.csproj +++ b/samples/My.Hr/My.Hr.Functions/My.Hr.Functions.csproj @@ -7,7 +7,7 @@ - + diff --git a/samples/My.Hr/My.Hr.UnitTest/My.Hr.UnitTest.csproj b/samples/My.Hr/My.Hr.UnitTest/My.Hr.UnitTest.csproj index 96189a13..f512e326 100644 --- a/samples/My.Hr/My.Hr.UnitTest/My.Hr.UnitTest.csproj +++ b/samples/My.Hr/My.Hr.UnitTest/My.Hr.UnitTest.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/CoreEx.AspNetCore/Http/HttpResultExtensions.cs b/src/CoreEx.AspNetCore/Http/HttpResultExtensions.cs index 9cf7ec46..5c92342a 100644 --- a/src/CoreEx.AspNetCore/Http/HttpResultExtensions.cs +++ b/src/CoreEx.AspNetCore/Http/HttpResultExtensions.cs @@ -150,15 +150,22 @@ public static void AddPagingResult(this IHeaderDictionary headers, PagingResult? if (paging == null) return; - if (paging.IsSkipTake) + switch (paging.Option) { - headers[HttpConsts.PagingSkipHeaderName] = paging.Skip.ToString(CultureInfo.InvariantCulture); - headers[HttpConsts.PagingTakeHeaderName] = paging.Take.ToString(CultureInfo.InvariantCulture); - } - else - { - headers[HttpConsts.PagingPageNumberHeaderName] = paging.Page!.Value.ToString(CultureInfo.InvariantCulture); - headers[HttpConsts.PagingPageSizeHeaderName] = paging.Take.ToString(CultureInfo.InvariantCulture); + case PagingOption.SkipAndTake: + headers[HttpConsts.PagingSkipHeaderName] = paging.Skip!.Value.ToString(CultureInfo.InvariantCulture); + headers[HttpConsts.PagingTakeHeaderName] = paging.Take.ToString(CultureInfo.InvariantCulture); + break; + + case PagingOption.PageAndSize: + headers[HttpConsts.PagingPageNumberHeaderName] = paging.Page!.Value.ToString(CultureInfo.InvariantCulture); + headers[HttpConsts.PagingPageSizeHeaderName] = paging.Take.ToString(CultureInfo.InvariantCulture); + break; + + default: + headers[HttpConsts.PagingTokenHeaderName] = paging.Token; + headers[HttpConsts.PagingTakeHeaderName] = paging.Take.ToString(CultureInfo.InvariantCulture); + break; } if (paging.TotalCount.HasValue) diff --git a/src/CoreEx.AspNetCore/WebApis/PagingOperationFilter.cs b/src/CoreEx.AspNetCore/WebApis/PagingOperationFilter.cs index 2f6509ff..aa235976 100644 --- a/src/CoreEx.AspNetCore/WebApis/PagingOperationFilter.cs +++ b/src/CoreEx.AspNetCore/WebApis/PagingOperationFilter.cs @@ -48,12 +48,15 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) if (Fields.HasFlag(PagingOperationFilterFields.Skip)) operation.Parameters.Add(CreateParameter(HttpConsts.PagingArgsSkipQueryStringName, "The specified number of elements in a sequence to bypass.", "number")); - if (Fields.HasFlag(PagingOperationFilterFields.Take)) - operation.Parameters.Add(CreateParameter(HttpConsts.PagingArgsTakeQueryStringName, "The specified number of contiguous elements from the start of a sequence.", "number")); - if (Fields.HasFlag(PagingOperationFilterFields.Page)) operation.Parameters.Add(CreateParameter(HttpConsts.PagingArgsPageQueryStringName, "The page number for the elements in a sequence to select.", "number")); + if (Fields.HasFlag(PagingOperationFilterFields.Token)) + operation.Parameters.Add(CreateParameter(HttpConsts.PagingArgsTokenQueryStringName, "The token to get the next page of elements.", "string")); + + if (Fields.HasFlag(PagingOperationFilterFields.Take)) + operation.Parameters.Add(CreateParameter(HttpConsts.PagingArgsTakeQueryStringName, "The specified number of contiguous elements from the start of a sequence.", "number")); + if (Fields.HasFlag(PagingOperationFilterFields.Size)) operation.Parameters.Add(CreateParameter(HttpConsts.PagingArgsSizeQueryStringName, "The page size being the specified number of contiguous elements from the start of a sequence.", "number")); diff --git a/src/CoreEx.AspNetCore/WebApis/PagingOperationFilterFields.cs b/src/CoreEx.AspNetCore/WebApis/PagingOperationFilterFields.cs index 4bcd039c..ae802a7e 100644 --- a/src/CoreEx.AspNetCore/WebApis/PagingOperationFilterFields.cs +++ b/src/CoreEx.AspNetCore/WebApis/PagingOperationFilterFields.cs @@ -37,6 +37,11 @@ public enum PagingOperationFilterFields /// Count = 16, + /// + /// Indicates to include field (named ). + /// + Token = 32, + /// /// Indicates to include and fields. /// @@ -57,9 +62,19 @@ public enum PagingOperationFilterFields /// PageSizeCount = Page | Size | Count, + /// + /// Indicates to include and fields. + /// + TokenTake = Token | Take, + + /// + /// Indicates to include , and fields. + /// + TokenTakeCount = Token | Size | Count, + /// /// Indicates to include all fields. /// - All = Skip | Take | Page | Size | Count + All = Skip | Take | Page | Size | Count | Token } } \ No newline at end of file diff --git a/src/CoreEx.AspNetCore/WebApis/ValueContentResult.cs b/src/CoreEx.AspNetCore/WebApis/ValueContentResult.cs index f5eb29e8..92993861 100644 --- a/src/CoreEx.AspNetCore/WebApis/ValueContentResult.cs +++ b/src/CoreEx.AspNetCore/WebApis/ValueContentResult.cs @@ -3,7 +3,6 @@ using CoreEx.Abstractions; using CoreEx.AspNetCore.Http; using CoreEx.Entities; -using CoreEx.Http; using CoreEx.Json; using CoreEx.Results; using Microsoft.AspNetCore.Http; @@ -21,9 +20,10 @@ namespace CoreEx.AspNetCore.WebApis { /// - /// Represents a with a JSON serialized value. + /// Represents an with a JSON serialized value. /// - /// This contains extended functionality to manage the setting of response headers related to , and . + /// This contains extended functionality to manage the setting of response headers related to , and . + /// The and will return the value as-is where it is an instance of ; i.e. will bypass all related functionality. public sealed class ValueContentResult : ExtendedContentResult { /// @@ -75,7 +75,7 @@ public override Task ExecuteResultAsync(ActionContext context) } /// - /// Creates the as either or as per . + /// Creates the as either or as per ; unless is an instance of which will return as-is. /// /// The value. /// The primary status code where there is a value. @@ -86,10 +86,10 @@ public override Task ExecuteResultAsync(ActionContext context) /// The . /// The . public static IActionResult CreateResult(T value, HttpStatusCode statusCode, HttpStatusCode? alternateStatusCode, IJsonSerializer jsonSerializer, WebApiRequestOptions requestOptions, bool checkForNotModified, Uri? location) - => TryCreateValueContentResult(value, statusCode, alternateStatusCode, jsonSerializer, requestOptions, checkForNotModified, location, out var vcr, out var ar) ? vcr! : ar!; + => TryCreateValueContentResult(value, statusCode, alternateStatusCode, jsonSerializer, requestOptions, checkForNotModified, location, out var pr, out var ar) ? pr! : ar!; /// - /// Try and create a ; otherwise, a . + /// Try and create an as either or as per ; unless is an instance of which will return as-is. /// /// The value. /// The primary status code where there is a value. @@ -98,14 +98,22 @@ public static IActionResult CreateResult(T value, HttpStatusCode statusCode, /// The . /// Indicates whether to check for by comparing request and response values. /// The . - /// The where created. - /// The alternate result where not created. - /// true indicates that the was created; otherwise, false for creation. - public static bool TryCreateValueContentResult(T value, HttpStatusCode statusCode, HttpStatusCode? alternateStatusCode, IJsonSerializer jsonSerializer, WebApiRequestOptions requestOptions, bool checkForNotModified, Uri? location, out ValueContentResult? valueContentResult, out StatusCodeResult? alternateResult) + /// The where created. + /// The alternate result where no . + /// true indicates that the was created; otherwise, false for creation. + public static bool TryCreateValueContentResult(T value, HttpStatusCode statusCode, HttpStatusCode? alternateStatusCode, IJsonSerializer jsonSerializer, WebApiRequestOptions requestOptions, bool checkForNotModified, Uri? location, out IActionResult? primaryResult, out IActionResult? alternateResult) { if (value is Results.IResult) throw new ArgumentException($"The {nameof(value)} must not implement {nameof(Results.IResult)}; the underlying {nameof(Results.IResult.Value)} must be unwrapped before invoking.", nameof(value)); + // Where already an IActionResult then return as-is. + if (value is IActionResult iar) + { + primaryResult = iar; + alternateResult = null; + return true; + } + object? val; PagingResult? paging; @@ -126,7 +134,7 @@ public static bool TryCreateValueContentResult(T value, HttpStatusCode status { if (alternateStatusCode.HasValue) { - valueContentResult = null; + primaryResult = null; alternateResult = new StatusCodeResult((int)alternateStatusCode); return false; } @@ -163,13 +171,13 @@ public static bool TryCreateValueContentResult(T value, HttpStatusCode status // Check for not-modified and return status accordingly. if (checkForNotModified && etag == requestOptions.ETag) { - valueContentResult = null; + primaryResult = null; alternateResult = new StatusCodeResult((int)HttpStatusCode.NotModified); return false; } // Create and return the ValueContentResult. - valueContentResult = new ValueContentResult(json, statusCode, etag, paging, location); + primaryResult = new ValueContentResult(json, statusCode, etag, paging, location); alternateResult = null; return true; } diff --git a/src/CoreEx.AspNetCore/WebApis/WebApi.cs b/src/CoreEx.AspNetCore/WebApis/WebApi.cs index 93d313f6..71c0a9db 100644 --- a/src/CoreEx.AspNetCore/WebApis/WebApi.cs +++ b/src/CoreEx.AspNetCore/WebApis/WebApi.cs @@ -21,24 +21,18 @@ namespace CoreEx.AspNetCore.WebApis /// /// Provides the core (, , and ) Web API execution encapsulation. /// - public partial class WebApi : WebApiBase + /// The . + /// The . + /// The . + /// The . + /// The ; defaults where not specified. + /// The to support the operations. + public partial class WebApi(ExecutionContext executionContext, SettingsBase settings, IJsonSerializer jsonSerializer, ILogger logger, WebApiInvoker? invoker = null, IJsonMergePatch? jsonMergePatch = null) : WebApiBase(executionContext, settings, jsonSerializer, logger, invoker) { - /// - /// Initializes a new instance of the class. - /// - /// The . - /// The . - /// The . - /// The . - /// The ; defaults where not specified. - /// The to support the operations. - public WebApi(ExecutionContext executionContext, SettingsBase settings, IJsonSerializer jsonSerializer, ILogger logger, WebApiInvoker? invoker = null, IJsonMergePatch? jsonMergePatch = null) - : base(executionContext, settings, jsonSerializer, logger, invoker) => JsonMergePatch = jsonMergePatch; - /// /// Gets the . /// - public IJsonMergePatch? JsonMergePatch { get; } + public IJsonMergePatch? JsonMergePatch { get; } = jsonMergePatch; /// /// Indicates whether to convert a to the default on delete (see . diff --git a/src/CoreEx.AspNetCore/WebApis/WebApiRequestOptions.cs b/src/CoreEx.AspNetCore/WebApis/WebApiRequestOptions.cs index 31aa9a71..e98820dd 100644 --- a/src/CoreEx.AspNetCore/WebApis/WebApiRequestOptions.cs +++ b/src/CoreEx.AspNetCore/WebApis/WebApiRequestOptions.cs @@ -109,16 +109,19 @@ private void GetQueryStringOptions(IQueryCollection query) long? skip = HttpExtensions.ParseLongValue(GetNamedQueryString(query, HttpConsts.PagingArgsSkipQueryStringNames)); long? take = HttpExtensions.ParseLongValue(GetNamedQueryString(query, HttpConsts.PagingArgsTakeQueryStringNames)); long? page = skip.HasValue ? null : HttpExtensions.ParseLongValue(GetNamedQueryString(query, HttpConsts.PagingArgsPageQueryStringNames)); + string? token = GetNamedQueryString(query, HttpConsts.PagingArgsTokenQueryStringNames); bool isGetCount = HttpExtensions.ParseBoolValue(GetNamedQueryString(query, HttpConsts.PagingArgsCountQueryStringNames)); - if (skip == null && take == null && page == null && !isGetCount) + if (skip == null && take == null && page == null && string.IsNullOrEmpty(token) && !isGetCount) return null; PagingArgs paging; - if (skip == null && page == null) - paging = (take.HasValue) ? PagingArgs.CreateSkipAndTake(0, take) : new PagingArgs(); + if (!string.IsNullOrEmpty(token)) + paging = PagingArgs.CreateTokenAndTake(token, take); + else if (skip == null && page == null) + paging = take.HasValue ? PagingArgs.CreateSkipAndTake(0, take) : new PagingArgs(); else - paging = (skip.HasValue) ? PagingArgs.CreateSkipAndTake(skip.Value, take) : PagingArgs.CreatePageAndSize(page == null ? 0 : page.Value, take); + paging = skip.HasValue ? PagingArgs.CreateSkipAndTake(skip.Value, take) : PagingArgs.CreatePageAndSize(page == null ? 0 : page.Value, take); paging.IsGetCount = isGetCount; return paging; diff --git a/src/CoreEx.Cosmos/CoreEx.Cosmos.csproj b/src/CoreEx.Cosmos/CoreEx.Cosmos.csproj index 7641a97c..08be50e5 100644 --- a/src/CoreEx.Cosmos/CoreEx.Cosmos.csproj +++ b/src/CoreEx.Cosmos/CoreEx.Cosmos.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/CoreEx.FluentValidation/CoreEx.FluentValidation.csproj b/src/CoreEx.FluentValidation/CoreEx.FluentValidation.csproj index 593170bd..8ec07bba 100644 --- a/src/CoreEx.FluentValidation/CoreEx.FluentValidation.csproj +++ b/src/CoreEx.FluentValidation/CoreEx.FluentValidation.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/CoreEx.OData/ODataQuery.cs b/src/CoreEx.OData/ODataQuery.cs index a8d43066..4c4882ba 100644 --- a/src/CoreEx.OData/ODataQuery.cs +++ b/src/CoreEx.OData/ODataQuery.cs @@ -254,7 +254,10 @@ private async readonly Task> SelectQueryWithResultInternalAsync - + diff --git a/src/CoreEx.UnitTesting.NUnit/CoreEx.UnitTesting.NUnit.csproj b/src/CoreEx.UnitTesting.NUnit/CoreEx.UnitTesting.NUnit.csproj index 2a334d80..b9c88cde 100644 --- a/src/CoreEx.UnitTesting.NUnit/CoreEx.UnitTesting.NUnit.csproj +++ b/src/CoreEx.UnitTesting.NUnit/CoreEx.UnitTesting.NUnit.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/CoreEx.UnitTesting/CoreEx.UnitTesting.csproj b/src/CoreEx.UnitTesting/CoreEx.UnitTesting.csproj index d86db256..633dfe06 100644 --- a/src/CoreEx.UnitTesting/CoreEx.UnitTesting.csproj +++ b/src/CoreEx.UnitTesting/CoreEx.UnitTesting.csproj @@ -18,7 +18,8 @@ - + + diff --git a/src/CoreEx.UnitTesting/Json/ToCoreExJsonSerializerMapper.cs b/src/CoreEx.UnitTesting/Json/ToCoreExJsonSerializerMapper.cs index 62c6be58..a7149fe2 100644 --- a/src/CoreEx.UnitTesting/Json/ToCoreExJsonSerializerMapper.cs +++ b/src/CoreEx.UnitTesting/Json/ToCoreExJsonSerializerMapper.cs @@ -11,15 +11,10 @@ namespace UnitTestEx.Json /// Provides a wrapper for to . /// /// Only the compatible capabilities have been implemented. - public class ToCoreExJsonSerializerMapper : CoreEx.Json.IJsonSerializer + /// The . + public class ToCoreExJsonSerializerMapper(IJsonSerializer testJsonSerializer) : CoreEx.Json.IJsonSerializer { - private readonly IJsonSerializer _testJsonSerializer; - - /// - /// Initializes a new instance. - /// - /// The . - public ToCoreExJsonSerializerMapper(IJsonSerializer testJsonSerializer) => _testJsonSerializer = testJsonSerializer ?? throw new ArgumentNullException(nameof(testJsonSerializer)); + private readonly IJsonSerializer _testJsonSerializer = testJsonSerializer ?? throw new ArgumentNullException(nameof(testJsonSerializer)); /// public object Options => _testJsonSerializer.Options; diff --git a/src/CoreEx.UnitTesting/Json/ToUnitTestExJsonSerializerMapper.cs b/src/CoreEx.UnitTesting/Json/ToUnitTestExJsonSerializerMapper.cs index f22c5ccf..9d968861 100644 --- a/src/CoreEx.UnitTesting/Json/ToUnitTestExJsonSerializerMapper.cs +++ b/src/CoreEx.UnitTesting/Json/ToUnitTestExJsonSerializerMapper.cs @@ -11,15 +11,10 @@ namespace UnitTestEx.Json /// Provides a wrapper for to . /// /// Only the compatible capabilities have been implemented. - public class ToUnitTestExJsonSerializerMapper : IJsonSerializer, CoreEx.Json.IJsonSerializer + /// The . + public class ToUnitTestExJsonSerializerMapper(CoreEx.Json.IJsonSerializer coreJsonSerializer) : IJsonSerializer, CoreEx.Json.IJsonSerializer { - private readonly CoreEx.Json.IJsonSerializer _coreJsonSerializer; - - /// - /// Initializes a new instance. - /// - /// The . - public ToUnitTestExJsonSerializerMapper(CoreEx.Json.IJsonSerializer coreJsonSerializer) => _coreJsonSerializer = coreJsonSerializer ?? throw new ArgumentNullException(nameof(coreJsonSerializer)); + private readonly CoreEx.Json.IJsonSerializer _coreJsonSerializer = coreJsonSerializer ?? throw new ArgumentNullException(nameof(coreJsonSerializer)); /// public object Options => _coreJsonSerializer.Options; diff --git a/src/CoreEx/Abstractions/IQueryableExtensions.cs b/src/CoreEx/Abstractions/IQueryableExtensions.cs index 0ff2cef7..51fdb395 100644 --- a/src/CoreEx/Abstractions/IQueryableExtensions.cs +++ b/src/CoreEx/Abstractions/IQueryableExtensions.cs @@ -19,7 +19,16 @@ public static class IQueryableExtensions /// The query. /// The . /// The query. - public static IQueryable WithPaging(this IQueryable query, PagingArgs? paging) => paging == null ? query.WithPaging(0, null) : query.WithPaging(paging.Skip, paging.Take); + public static IQueryable WithPaging(this IQueryable query, PagingArgs? paging) + { + if (paging is null) + return query.WithPaging(0, null); + + if (paging.Option == PagingOption.TokenAndTake) + throw new ArgumentException("PagingArgs.Option must not be PagingOption.TokenAndTake.", nameof(paging)); + + return query.WithPaging(paging.Skip!.Value, paging.Take); + } /// /// Adds paging to the query using the specified and . diff --git a/src/CoreEx/Entities/PagingArgs.cs b/src/CoreEx/Entities/PagingArgs.cs index 64eb8d8b..b8d67fd7 100644 --- a/src/CoreEx/Entities/PagingArgs.cs +++ b/src/CoreEx/Entities/PagingArgs.cs @@ -7,7 +7,7 @@ namespace CoreEx.Entities { /// - /// Represents either position-based paging being ( and ), or and . The and (and ) + /// Represents position-based paging being a) and , b) and , or c) and . The and (and ) /// are static settings to encourage page-size consistency, as well as limit the maximum value possible. /// [System.Diagnostics.DebuggerStepThrough] @@ -85,6 +85,20 @@ public static PagingArgs CreatePageAndSize(long page, long? size = null, bool? i IsGetCount = isGetCount == null ? DefaultIsGetCount : isGetCount.Value }; + /// + /// Creates a for a specified token and take. + /// + /// The to use to get the next page of elements. + /// The value (defaults to ). + /// Indicates whether to get the total count (see ) when performing the underlying query (defaults to where null). + /// The . + public static PagingArgs CreateTokenAndTake(string token, long? take = null, bool? isGetCount = null) => new () + { + Token = token.ThrowIfNullOrEmpty(), + Take = !take.HasValue || take.Value< 1 ? DefaultTake : (take.Value > MaxTake? MaxTake : take.Value), + IsGetCount = isGetCount == null ? DefaultIsGetCount : isGetCount.Value + }; + /// /// Initializes a new instance of the class with default and . /// @@ -116,21 +130,25 @@ public PagingArgs(PagingArgs pagingArgs) public long? Page { get; internal protected set; } /// - /// Indicates whether the paging was created with a and (see ); versus and (see ). + /// Gets the specified number of elements in a sequence to bypass. + /// + public long? Skip { get; internal protected set; } + + /// + /// Gets the token to use to get the next page of elements (see ). /// - [JsonIgnore] - public bool IsSkipTake => Page == null; + public string? Token { get; internal protected set; } /// - /// Gets the page size (see ). + /// Indicates the . /// - [JsonIgnore] - public long Size => Take; + public PagingOption Option => Page is not null ? PagingOption.PageAndSize : (Token is not null ? PagingOption.TokenAndTake : PagingOption.SkipAndTake); /// - /// Gets the specified number of elements in a sequence to bypass. + /// Gets the page size (synonym for ). /// - public long Skip { get; internal protected set; } + [JsonIgnore] + public long Size => Take; /// /// Gets the specified number of contiguous elements from the start of a sequence. @@ -144,6 +162,9 @@ public PagingArgs(PagingArgs pagingArgs) /// The instance to support fluent-style method chaining. public PagingArgs OverrideSkip(long skip) { + if (Option == PagingOption.TokenAndTake) + throw new InvalidOperationException($"Cannot override {nameof(Skip)} where {nameof(Option)} is {nameof(PagingOption.TokenAndTake)}."); + if (skip == Skip) return this; diff --git a/src/CoreEx/Entities/PagingOption.cs b/src/CoreEx/Entities/PagingOption.cs new file mode 100644 index 00000000..56985ede --- /dev/null +++ b/src/CoreEx/Entities/PagingOption.cs @@ -0,0 +1,25 @@ +// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/CoreEx + +namespace CoreEx.Entities +{ + /// + /// Represents the option. + /// + public enum PagingOption + { + /// + /// Indicates that the was used to instantiate. + /// + PageAndSize, + + /// + /// Indicates that the was used to instantiate. + /// + SkipAndTake, + + /// + /// Indicates that the was used to instantiate. + /// + TokenAndTake + } +} \ No newline at end of file diff --git a/src/CoreEx/Entities/PagingResult.cs b/src/CoreEx/Entities/PagingResult.cs index e50f5233..b5d448c4 100644 --- a/src/CoreEx/Entities/PagingResult.cs +++ b/src/CoreEx/Entities/PagingResult.cs @@ -12,23 +12,32 @@ namespace CoreEx.Entities public class PagingResult : PagingArgs { /// - /// Creates a for a specified page number and size. + /// Creates a for a specified page number and size. /// /// The number. /// The page (defaults to ). /// Indicates whether to get the total count (see ) when performing the underlying query (defaults to where null). - /// The . + /// The . public static new PagingResult CreatePageAndSize(long page, long? size = null, bool? isGetCount = null) => new(PagingArgs.CreatePageAndSize(page, size, isGetCount)); /// - /// Creates a for a specified skip and take. + /// Creates a for a specified skip and take. /// /// The value. /// The value (defaults to ). /// Indicates whether to get the total count (see ) when performing the underlying query (defaults to where null). - /// The . + /// The . public static new PagingResult CreateSkipAndTake(long skip, long? take = null, bool? isGetCount = null) => new(PagingArgs.CreateSkipAndTake(skip, take, isGetCount)); + /// + /// Creates a for a specified token and take. + /// + /// The to use to get the next page of elements. + /// The value (defaults to ). + /// Indicates whether to get the total count (see ) when performing the underlying query (defaults to where null). + /// The . + public static new PagingResult CreateTokenAndTake(string token, long? take = null, bool? isGetCount = null) => new(PagingArgs.CreateTokenAndTake(token, take, isGetCount)); + /// /// Initializes a new instance of the class from a and optional . /// @@ -42,6 +51,7 @@ public PagingResult(PagingArgs? pagingArgs = null, long? totalCount = null) Skip = pagingArgs.Skip; Take = pagingArgs.Take; Page = pagingArgs.Page; + Token = pagingArgs.Token; IsGetCount = pagingArgs.IsGetCount; TotalCount = (totalCount.HasValue && totalCount.Value < 0) ? null : totalCount; } @@ -58,9 +68,9 @@ public PagingResult(PagingResult pagingResult) : this(pagingResult, pagingResult public long? TotalCount { get; set; } /// - /// Gets the calculated total pages for all elements in the sequence (needs and values as well as not being an ). + /// Gets the calculated total pages for all elements in the sequence where the is equal to . /// [JsonIgnore()] - public long? TotalPages => !IsSkipTake && TotalCount.HasValue ? (long)System.Math.Ceiling(TotalCount.Value / (double)Take) : null; + public long? TotalPages => Option == PagingOption.PageAndSize && TotalCount.HasValue ? (long)System.Math.Ceiling(TotalCount.Value / (double)Take) : null; } } \ No newline at end of file diff --git a/src/CoreEx/Http/HttpConsts.cs b/src/CoreEx/Http/HttpConsts.cs index aef39d3a..07f1c098 100644 --- a/src/CoreEx/Http/HttpConsts.cs +++ b/src/CoreEx/Http/HttpConsts.cs @@ -42,6 +42,11 @@ public static class HttpConsts /// public static string PagingTakeHeaderName { get; set; } = "x-paging-take"; + /// + /// Gets or sets the header name for the . + /// + public static string PagingTokenHeaderName { get; set; } = "x-paging-token"; + /// /// Gets or sets the header name for the . /// @@ -96,6 +101,11 @@ public static class HttpConsts /// public static string PagingArgsTakeQueryStringName { get; set; } = "$take"; + /// + /// Gets or sets the query string name. + /// + public static string PagingArgsTokenQueryStringName { get; set; } = "$token"; + /// /// Gets or sets the query string name. /// @@ -129,7 +139,12 @@ public static class HttpConsts /// /// Gets or sets the list of possible query string names. /// - public static List PagingArgsTakeQueryStringNames { get; set; } = new List(new string[] { "$take", "$top", "$size", "$pageSize", "$limit", "paging-take", "paging-size" }); + public static List PagingArgsTakeQueryStringNames { get; set; } = new List(new string[] { "$take", "$top", "$size", "$pageSize", "$limit", "paging-take", "paging-size", "paging-limit" }); + + /// + /// Gets or sets the list of possible query string names. + /// + public static List PagingArgsTokenQueryStringNames { get; set; } = new List(new string[] { "$token", "$after", "$cursor", "paging-token", "paging-after", "paging-cursor" }); /// /// Gets or sets the list of possible query string names. diff --git a/src/CoreEx/Http/HttpExtensions.cs b/src/CoreEx/Http/HttpExtensions.cs index 12048c1c..85adfb81 100644 --- a/src/CoreEx/Http/HttpExtensions.cs +++ b/src/CoreEx/Http/HttpExtensions.cs @@ -115,8 +115,11 @@ public static bool TryGetPagingResult(this HttpResponseMessage response, [NotNul { var skip = ParseLongValue(TryGetHeaderValue(response, HttpConsts.PagingSkipHeaderName, out var vs) ? vs : null); var page = skip.HasValue ? null : ParseLongValue(TryGetHeaderValue(response, HttpConsts.PagingPageNumberHeaderName, out var vpn) ? vpn : null); + var token = TryGetHeaderValue(response, HttpConsts.PagingTokenHeaderName, out var vtk) ? vtk : null; - if (skip.HasValue) + if (!string.IsNullOrEmpty(token)) + result = new PagingResult(PagingArgs.CreateTokenAndTake(token, ParseLongValue(TryGetHeaderValue(response, HttpConsts.PagingTakeHeaderName, out var vt) ? vt : null))); + else if (skip.HasValue) result = new PagingResult(PagingArgs.CreateSkipAndTake(skip.Value, ParseLongValue(TryGetHeaderValue(response, HttpConsts.PagingTakeHeaderName, out var vt) ? vt : null))); else if (page.HasValue) result = new PagingResult(PagingArgs.CreatePageAndSize(page.Value, ParseLongValue(TryGetHeaderValue(response, HttpConsts.PagingPageSizeHeaderName, out var vps) ? vps : null))); diff --git a/src/CoreEx/Http/HttpRequestOptions.cs b/src/CoreEx/Http/HttpRequestOptions.cs index 060d7745..1b661286 100644 --- a/src/CoreEx/Http/HttpRequestOptions.cs +++ b/src/CoreEx/Http/HttpRequestOptions.cs @@ -54,6 +54,12 @@ public class HttpRequestOptions /// Defaults to . public string QueryStringNamePagingArgsTake { get; set; } = HttpConsts.PagingArgsTakeQueryStringName; + /// + /// Gets or sets the query string name. + /// + /// Defaults to . + public string QueryStringNamePagingArgsToken { get; set; } = HttpConsts.PagingArgsTokenQueryStringName; + /// /// Gets or sets the query string name. /// @@ -152,15 +158,23 @@ public HttpRequestOptions Exclude(params string[] fields) if (Paging != null) { - if (Paging.IsSkipTake) + switch (Paging.Option) { - AddNameValuePair(sb, QueryStringNamePagingArgsSkip, Paging.Skip.ToString(), false); - AddNameValuePair(sb, QueryStringNamePagingArgsTake, Paging.Take.ToString(), false); - } - else - { - AddNameValuePair(sb, QueryStringNamePagingArgsPage, Paging.Page?.ToString() ?? 1.ToString(), false); - AddNameValuePair(sb, QueryStringNamePagingArgsSize, Paging.Size.ToString(), false); + case PagingOption.SkipAndTake: + AddNameValuePair(sb, QueryStringNamePagingArgsSkip, Paging.Skip?.ToString(), false); + AddNameValuePair(sb, QueryStringNamePagingArgsTake, Paging.Take.ToString(), false); + break; + + case PagingOption.PageAndSize: + AddNameValuePair(sb, QueryStringNamePagingArgsPage, Paging.Page?.ToString() ?? 1.ToString(), false); + AddNameValuePair(sb, QueryStringNamePagingArgsSize, Paging.Size.ToString(), false); + break; + + default: + AddNameValuePair(sb, QueryStringNamePagingArgsToken, Paging.Token, false); + AddNameValuePair(sb, QueryStringNamePagingArgsTake, Paging.Take.ToString(), false); + break; + } if (Paging.IsGetCount) @@ -183,9 +197,9 @@ public HttpRequestOptions Exclude(params string[] fields) if (!string.IsNullOrEmpty(UrlQueryString)) { if (qs is null) - return UrlQueryString.StartsWith("?") ? UrlQueryString : $"?{(UrlQueryString.StartsWith("&") ? UrlQueryString[1..] : UrlQueryString)}"; + return UrlQueryString.StartsWith('?') ? UrlQueryString : $"?{(UrlQueryString.StartsWith('&') ? UrlQueryString[1..] : UrlQueryString)}"; else - return $"{qs}{(UrlQueryString.StartsWith("&") ? UrlQueryString : $"&{UrlQueryString}")}"; + return $"{qs}{(UrlQueryString.StartsWith('&') ? UrlQueryString : $"&{UrlQueryString}")}"; } else return qs; diff --git a/src/CoreEx/Json/Data/JsonDataReader.cs b/src/CoreEx/Json/Data/JsonDataReader.cs index a1e88704..8822ccf3 100644 --- a/src/CoreEx/Json/Data/JsonDataReader.cs +++ b/src/CoreEx/Json/Data/JsonDataReader.cs @@ -30,7 +30,7 @@ public sealed class JsonDataReader : IDisposable private class YamlNodeTypeResolver : INodeTypeResolver { - private static readonly string[] boolValues = { "true", "false" }; + private static readonly string[] boolValues = ["true", "false"]; /// bool INodeTypeResolver.Resolve(NodeEvent? nodeEvent, ref Type currentType) @@ -184,7 +184,7 @@ public bool TryDeserialize(string? name, [NotNullWhen(true)] out List? ite var item = Deserialize(jd); if (item != null) { - (items ??= new List()).Add(item); + (items ??= []).Add(item); _args.IdentifierGenerator?.AssignIdentifierAsync(item); ChangeLog.PrepareCreated(item, _executionContext); PrepareReferenceData(typeof(T), item, jd, items.Count - 1); @@ -217,7 +217,7 @@ public bool TryDeserialize(Type type, string? name, [NotNullWhen(true)] out List var item = Deserialize(type, jd); if (item != null) { - (items ??= new List()).Add(item); + (items ??= []).Add(item); _args.IdentifierGenerator?.AssignIdentifierAsync(item); ChangeLog.PrepareCreated(item, _executionContext); PrepareReferenceData(type, item, jd, items.Count - 1); @@ -303,7 +303,7 @@ private void ReplaceDynamicParameter(JsonElement je, Utf8JsonWriter jw) var str = je.GetString(); if (!string.IsNullOrEmpty(str) && str.Length > 1 && str[0] == '^') { - if (str.StartsWith("^(") && str.EndsWith(")")) + if (str.StartsWith("^(") && str.EndsWith(')')) { var val = GetRuntimeValue(_args.Parameters, str[2..^1]); if (val == null) @@ -408,7 +408,7 @@ private static (object? value, string? message) GetSystemPropertyValue(string pa var part = parts[0]; if (part.EndsWith("()")) { - var mi = type.GetMethod(part[0..^2], Array.Empty()); + var mi = type.GetMethod(part[0..^2], []); if (mi == null || mi.GetParameters().Length != 0) return (null, $"Runtime value parameter '{param}' is invalid; specified method '{part}' is invalid."); diff --git a/src/CoreEx/Json/Data/JsonDataReaderArgs.cs b/src/CoreEx/Json/Data/JsonDataReaderArgs.cs index 1cf345a1..c7ec5276 100644 --- a/src/CoreEx/Json/Data/JsonDataReaderArgs.cs +++ b/src/CoreEx/Json/Data/JsonDataReaderArgs.cs @@ -74,11 +74,11 @@ public JsonDataReaderArgs(IJsonSerializer? jsonSerializer = null, string? userna /// with function 'i => i + 1' (increment by 1 from 1). /// /// - public Dictionary> RefDataColumnDefaults { get; } = new Dictionary>(); + public Dictionary> RefDataColumnDefaults { get; } = []; /// /// Gets the runtime parameters. /// - public Dictionary Parameters { get; } = new(); + public Dictionary Parameters { get; } = []; } } \ No newline at end of file diff --git a/src/CoreEx/Text/Json/JsonSerializer.cs b/src/CoreEx/Text/Json/JsonSerializer.cs index e396b791..08cc711a 100644 --- a/src/CoreEx/Text/Json/JsonSerializer.cs +++ b/src/CoreEx/Text/Json/JsonSerializer.cs @@ -14,8 +14,11 @@ namespace CoreEx.Text.Json /// /// Provides the encapsulated implementation. /// - public class JsonSerializer : IJsonSerializer + /// The . Defaults to . + public class JsonSerializer(Stj.JsonSerializerOptions? options = null) : IJsonSerializer { + private Stj.JsonSerializerOptions? _indentedOptions; + /// /// Gets or sets the default . /// @@ -37,12 +40,6 @@ public class JsonSerializer : IJsonSerializer Converters = { new JsonStringEnumConverter(), new ExceptionConverterFactory(), new ReferenceDataConverterFactory(), new CollectionResultConverterFactory(), new ResultConverterFactory() } }; - /// - /// Initializes a new instance of the class. - /// - /// The . Defaults to . - public JsonSerializer(Stj.JsonSerializerOptions? options = null) => Options = options ?? DefaultOptions; - /// /// Gets the underlying serializer configuration settings/options. /// @@ -51,14 +48,14 @@ public class JsonSerializer : IJsonSerializer /// /// Gets the . /// - public Stj.JsonSerializerOptions Options { get; } + public Stj.JsonSerializerOptions Options { get; } = options ?? DefaultOptions; /// public string Serialize(T value, JsonWriteFormat? format = null) => SerializeToBinaryData(value, format).ToString(); /// public BinaryData SerializeToBinaryData(T value, JsonWriteFormat? format = null) - => new(Stj.JsonSerializer.SerializeToUtf8Bytes(value, format == null ? Options : new Stj.JsonSerializerOptions(Options) { WriteIndented = format.Value == JsonWriteFormat.Indented })); + => new(Stj.JsonSerializer.SerializeToUtf8Bytes(value, format == null ? Options : (_indentedOptions ??= new Stj.JsonSerializerOptions(Options) { WriteIndented = format.Value == JsonWriteFormat.Indented }))); /// public object? Deserialize(string json) => Stj.JsonSerializer.Deserialize(json, Options); diff --git a/tests/CoreEx.Cosmos.Test/CoreEx.Cosmos.Test.csproj b/tests/CoreEx.Cosmos.Test/CoreEx.Cosmos.Test.csproj index 1f35dd8d..c8da703b 100644 --- a/tests/CoreEx.Cosmos.Test/CoreEx.Cosmos.Test.csproj +++ b/tests/CoreEx.Cosmos.Test/CoreEx.Cosmos.Test.csproj @@ -19,7 +19,7 @@ - + @@ -34,7 +34,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/tests/CoreEx.Cosmos.Test/TestSetUp.cs b/tests/CoreEx.Cosmos.Test/TestSetUp.cs index 2bc77fda..b04b571a 100644 --- a/tests/CoreEx.Cosmos.Test/TestSetUp.cs +++ b/tests/CoreEx.Cosmos.Test/TestSetUp.cs @@ -85,7 +85,7 @@ public static async Task SetUpAsync(string partitionKeyPath = "/_partitionKey", await db.Persons1.ImportBatchAsync(jdr); await db.Persons2.ImportBatchAsync(jdr); await db.Persons3.ImportValueBatchAsync(jdr); - await db.ImportValueBatchAsync("Persons3", new Person1[] { new Person1 { Id = 100.ToGuid().ToString() } }); // Add other random "type" to Person3. + await db.ImportValueBatchAsync("Persons3", new Person1[] { new() { Id = 100.ToGuid().ToString() } }); // Add other random "type" to Person3. jdr = JsonDataReader.ParseYaml("RefData.yaml", new JsonDataReaderArgs(new Text.Json.ReferenceDataContentJsonSerializer())); await db.ImportValueBatchAsync("RefData", jdr, new Type[] { typeof(Gender) }); diff --git a/tests/CoreEx.Solace.Test/CoreEx.Solace.Test.csproj b/tests/CoreEx.Solace.Test/CoreEx.Solace.Test.csproj index 5e7fd56e..6d4d5948 100644 --- a/tests/CoreEx.Solace.Test/CoreEx.Solace.Test.csproj +++ b/tests/CoreEx.Solace.Test/CoreEx.Solace.Test.csproj @@ -13,7 +13,7 @@ - + diff --git a/tests/CoreEx.Test/CoreEx.Test.csproj b/tests/CoreEx.Test/CoreEx.Test.csproj index a6b370aa..cf5a5779 100644 --- a/tests/CoreEx.Test/CoreEx.Test.csproj +++ b/tests/CoreEx.Test/CoreEx.Test.csproj @@ -8,7 +8,7 @@ - + all @@ -18,7 +18,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/tests/CoreEx.Test/Framework/Abstractions/Reflection/TypeReflectorTest.cs b/tests/CoreEx.Test/Framework/Abstractions/Reflection/TypeReflectorTest.cs index 865c8d53..0d5aaed2 100644 --- a/tests/CoreEx.Test/Framework/Abstractions/Reflection/TypeReflectorTest.cs +++ b/tests/CoreEx.Test/Framework/Abstractions/Reflection/TypeReflectorTest.cs @@ -85,13 +85,13 @@ public void GetReflector_PropertyReflector_Addresses() var pr = tr.GetProperty("Addresses"); Assert.IsNotNull(pr.GetTypeReflector()); - var a = new List
{ new Address { Street = "s", City = "c" } }; + var a = new List
{ new() { Street = "s", City = "c" } }; var p = new Person { Addresses = a }; var a2 = (List
)pr.PropertyExpression.GetValue(p)!; Assert.AreEqual("s", a2[0].Street); - pr.PropertyExpression.SetValue(p, new List
{ new Address { Street = "s2", City = "c2" } }); + pr.PropertyExpression.SetValue(p, new List
{ new() { Street = "s2", City = "c2" } }); Assert.AreEqual("s2", p.Addresses[0].Street); pr.PropertyExpression.SetValue(p, null!); @@ -196,10 +196,10 @@ public void GetReflector_PropertyReflector_Compare_Collection() Assert.IsTrue(pr.Compare(new List
(), new List
())); // No equality check for Address, so will all fail. - Assert.IsFalse(pr.Compare(new List
{ new Address() }, new List
{ new Address() })); - Assert.IsFalse(pr.Compare(null, new List
{ new Address() })); - Assert.IsFalse(pr.Compare(new List
{ new Address() }, null)); - Assert.IsFalse(pr.Compare(new List
{ new Address() }, new List
{ new Address(), new Address() })); + Assert.IsFalse(pr.Compare(new List
{ new() }, new List
{ new() })); + Assert.IsFalse(pr.Compare(null, new List
{ new() })); + Assert.IsFalse(pr.Compare(new List
{ new() }, null)); + Assert.IsFalse(pr.Compare(new List
{ new() }, new List
{ new(), new() })); } [Test] diff --git a/tests/CoreEx.Test/Framework/Configuration/SettingsBaseTest.cs b/tests/CoreEx.Test/Framework/Configuration/SettingsBaseTest.cs index fe7e253d..d3235735 100644 --- a/tests/CoreEx.Test/Framework/Configuration/SettingsBaseTest.cs +++ b/tests/CoreEx.Test/Framework/Configuration/SettingsBaseTest.cs @@ -30,8 +30,8 @@ public SettingsForTesting(IConfiguration configuration, params string[] prefixes private IConfiguration CreateTestConfiguration() { Environment.SetEnvironmentVariable("this_is_a_unittest_underscore__key", "underscoreValue"); - ConfigurationBuilder builder = new ConfigurationBuilder(); - Dictionary testSettings = new Dictionary() + ConfigurationBuilder builder = new(); + Dictionary testSettings = new() { {"SomethingGlobal", "foo"}, {"prefix1/key1", "value1"}, diff --git a/tests/CoreEx.Test/Framework/Entities/Extended/EntityBaseTest.cs b/tests/CoreEx.Test/Framework/Entities/Extended/EntityBaseTest.cs index e9a87dd8..a66f3f79 100644 --- a/tests/CoreEx.Test/Framework/Entities/Extended/EntityBaseTest.cs +++ b/tests/CoreEx.Test/Framework/Entities/Extended/EntityBaseTest.cs @@ -797,7 +797,7 @@ public void Dictionary_Person_Equals() Assert.AreNotEqual(pd.GetHashCode(), pxd.GetHashCode()); } - private DateTime CreateDateTime() => new DateTime(2000, 01, 01, 12, 45, 59); + private DateTime CreateDateTime() => new(2000, 01, 01, 12, 45, 59); public class Person : EntityBase, CoreEx.Entities.IPrimaryKey { @@ -809,7 +809,7 @@ public class Person : EntityBase, CoreEx.Entities.IPrimaryKey public int Age { get => _age; set => SetValue(ref _age, value); } public ChangeLogEx? ChangeLog { get => _changeLog; set => SetValue(ref _changeLog, value); } - public CoreEx.Entities.CompositeKey PrimaryKey => new CoreEx.Entities.CompositeKey(Name); + public CoreEx.Entities.CompositeKey PrimaryKey => new(Name); protected override IEnumerable GetPropertyValues() { diff --git a/tests/CoreEx.Test/Framework/Entities/PagingArgsTest.cs b/tests/CoreEx.Test/Framework/Entities/PagingArgsTest.cs new file mode 100644 index 00000000..af087b70 --- /dev/null +++ b/tests/CoreEx.Test/Framework/Entities/PagingArgsTest.cs @@ -0,0 +1,36 @@ +using CoreEx.Entities; +using NUnit.Framework; + +namespace CoreEx.Test.Framework.Entities +{ + [TestFixture] + public class PagingArgsTest + { + [Test] + public void CreateSkipAndTake() + { + var pa = PagingArgs.CreateSkipAndTake(10, 20); + Assert.That(pa.Skip, Is.EqualTo(10)); + Assert.That(pa.Take, Is.EqualTo(20)); + Assert.That(pa.Option, Is.EqualTo(PagingOption.SkipAndTake)); + } + + [Test] + public void CreatePageAndSize() + { + var pa = PagingArgs.CreatePageAndSize(10, 20); + Assert.That(pa.Page, Is.EqualTo(10)); + Assert.That(pa.Size, Is.EqualTo(20)); + Assert.That(pa.Option, Is.EqualTo(PagingOption.PageAndSize)); + } + + [Test] + public void CreateTokenAndTake() + { + var pa = PagingArgs.CreateTokenAndTake("blah-blah", 20); + Assert.That(pa.Token, Is.EqualTo("blah-blah")); + Assert.That(pa.Take, Is.EqualTo(20)); + Assert.That(pa.Option, Is.EqualTo(PagingOption.TokenAndTake)); + } + } +} \ No newline at end of file diff --git a/tests/CoreEx.Test/Framework/Events/Subscribing/EventSubscriberAttributeTest.cs b/tests/CoreEx.Test/Framework/Events/Subscribing/EventSubscriberAttributeTest.cs index f862ef75..1f157603 100644 --- a/tests/CoreEx.Test/Framework/Events/Subscribing/EventSubscriberAttributeTest.cs +++ b/tests/CoreEx.Test/Framework/Events/Subscribing/EventSubscriberAttributeTest.cs @@ -114,6 +114,6 @@ public void Match_Source() Assert.IsFalse(esa.IsMatch(edf, new EventData { Source = new Uri("http://host:5050/test/xyz", UriKind.Absolute) })); } - private EventData Create(string? subject, string? type, string? action) => new EventData { Subject = subject, Type = type, Action = action }; + private EventData Create(string? subject, string? type, string? action) => new() { Subject = subject, Type = type, Action = action }; } } \ No newline at end of file diff --git a/tests/CoreEx.Test/Framework/Http/TypedHttpClientCoreTest.cs b/tests/CoreEx.Test/Framework/Http/TypedHttpClientCoreTest.cs index f93dfcf6..10ccc4fc 100644 --- a/tests/CoreEx.Test/Framework/Http/TypedHttpClientCoreTest.cs +++ b/tests/CoreEx.Test/Framework/Http/TypedHttpClientCoreTest.cs @@ -207,6 +207,33 @@ public void Get_SuccessWithCollectionResultAndPaging() mcf.VerifyAll(); } + [Test] + public void Get_SuccessWithCollectionResultAndTokenPaging() + { + var pc = new ProductCollection { new Product { Id = "abc", Name = "banana", Price = 0.99m }, new Product { Id = "def", Name = "apple", Price = 0.49m } }; + + var mcf = MockHttpClientFactory.Create(); + var mc = mcf.CreateClient("Backend", "https://backend/"); + mc.Request(HttpMethod.Get, "product").Respond.WithJson(pc, HttpStatusCode.OK, r => + { + r.Headers.Add(HttpConsts.PagingTokenHeaderName, "token"); + r.Headers.Add(HttpConsts.PagingTakeHeaderName, "25"); + r.Headers.Add(HttpConsts.PagingTotalCountHeaderName, "1000"); + }); + + using var test = FunctionTester.Create(); + var r = test.ReplaceHttpClientFactory(mcf) + .Type() + .Run(f => f.GetAsync("product")) + .AssertSuccess(); + + Assert.IsTrue(r.Result.IsSuccess); + Assert.AreEqual(HttpStatusCode.OK, r.Result.StatusCode); + ObjectComparer.Assert(new ProductCollectionResult { Items = pc, Paging = new PagingResult(PagingArgs.CreateTokenAndTake("token", 25), 1000) }, r.Result.Value); + + mcf.VerifyAll(); + } + [Test] public void Post_Success() { diff --git a/tests/CoreEx.Test/Framework/Json/JsonSerializerTest.cs b/tests/CoreEx.Test/Framework/Json/JsonSerializerTest.cs index f58e12ba..8475b52d 100644 --- a/tests/CoreEx.Test/Framework/Json/JsonSerializerTest.cs +++ b/tests/CoreEx.Test/Framework/Json/JsonSerializerTest.cs @@ -94,7 +94,7 @@ public void SystemTextJson_Serialize_Deserialize_Dynamic_BinaryData() [Test] public void SystemTextJson_TryApplyFilter_JsonString() { - var p = new Person { FirstName = "John", LastName = "Smith", Addresses = new List
{ new Address { Street = "One", City = "First" }, new Address { Street = "Two", City = "Second" } } }; + var p = new Person { FirstName = "John", LastName = "Smith", Addresses = new List
{ new() { Street = "One", City = "First" }, new() { Street = "Two", City = "Second" } } }; var js = new CoreEx.Text.Json.JsonSerializer() as IJsonSerializer; Assert.IsTrue(js.TryApplyFilter(p, new string[] { "LastName", "Addresses.City", "LastName" }, out string json, JsonPropertyFilter.Exclude)); @@ -122,7 +122,7 @@ public void SystemTextJson_TryApplyFilter_JsonString() [Test] public void SystemTextJson_TryApplyFilter_JsonObject() { - var p = new Person { FirstName = "John", LastName = "Smith", Addresses = new List
{ new Address { Street = "One", City = "First" }, new Address { Street = "Two", City = "Second" } } }; + var p = new Person { FirstName = "John", LastName = "Smith", Addresses = new List
{ new() { Street = "One", City = "First" }, new() { Street = "Two", City = "Second" } } }; var js = new CoreEx.Text.Json.JsonSerializer() as IJsonSerializer; Assert.IsTrue(js.TryApplyFilter(p, new string[] { "LastName", "Addresses.City", "LastName" }, out object json, JsonPropertyFilter.Exclude)); @@ -337,7 +337,7 @@ public void NewtonsoftJson_Serialize_Deserialize_Dynamic_BinaryData() [Test] public void NewtonsoftJson_TryApplyFilter_JsonString() { - var p = new Person { FirstName = "John", LastName = "Smith", Addresses = new List
{ new Address { Street = "One", City = "First" }, new Address { Street = "Two", City = "Second" } } }; + var p = new Person { FirstName = "John", LastName = "Smith", Addresses = new List
{ new() { Street = "One", City = "First" }, new() { Street = "Two", City = "Second" } } }; var js = new CoreEx.Newtonsoft.Json.JsonSerializer() as IJsonSerializer; Assert.IsTrue(js.TryApplyFilter(p, new string[] { "LastName", "Addresses.City", "LastName" }, out string json, JsonPropertyFilter.Exclude)); @@ -365,7 +365,7 @@ public void NewtonsoftJson_TryApplyFilter_JsonString() [Test] public void NewtonsoftJson_TryApplyFilter_JsonObject() { - var p = new Person { FirstName = "John", LastName = "Smith", Addresses = new List
{ new Address { Street = "One", City = "First" }, new Address { Street = "Two", City = "Second" } } }; + var p = new Person { FirstName = "John", LastName = "Smith", Addresses = new List
{ new() { Street = "One", City = "First" }, new() { Street = "Two", City = "Second" } } }; var js = new CoreEx.Newtonsoft.Json.JsonSerializer() as IJsonSerializer; Assert.IsTrue(js.TryApplyFilter(p, new string[] { "LastName", "Addresses.City", "LastName" }, out object json, JsonPropertyFilter.Exclude)); diff --git a/tests/CoreEx.Test/Framework/Json/Merge/JsonMergePatchTest.cs b/tests/CoreEx.Test/Framework/Json/Merge/JsonMergePatchTest.cs index 27c5e516..bfb53d52 100644 --- a/tests/CoreEx.Test/Framework/Json/Merge/JsonMergePatchTest.cs +++ b/tests/CoreEx.Test/Framework/Json/Merge/JsonMergePatchTest.cs @@ -248,7 +248,7 @@ public void Merge_Property_ArrayValues_Changes() [Test] public void Merge_Property_NoKeys_ListNull() { - var td = new TestData { NoKeys = new List { new SubData() } }; + var td = new TestData { NoKeys = new List { new() } }; Assert.IsTrue(new JsonMergePatch().Merge(BinaryData.FromString("{ \"nokeys\": null }"), ref td)); Assert.IsNull(td!.Values); } @@ -256,7 +256,7 @@ public void Merge_Property_NoKeys_ListNull() [Test] public void Merge_Property_NoKeys_ListEmpty() { - var td = new TestData { NoKeys = new List { new SubData() } }; + var td = new TestData { NoKeys = new List { new() } }; Assert.IsTrue(new JsonMergePatch().Merge(BinaryData.FromString("{ \"nokeys\": [ ] }"), ref td)); Assert.IsNotNull(td!.NoKeys); Assert.AreEqual(0, td.NoKeys!.Count); @@ -265,7 +265,7 @@ public void Merge_Property_NoKeys_ListEmpty() [Test] public void Merge_Property_NoKeys_List() { - var td = new TestData { NoKeys = new List { new SubData() } }; + var td = new TestData { NoKeys = new List { new() } }; Assert.IsTrue(new JsonMergePatch().Merge(BinaryData.FromString("{ \"nokeys\": [ { \"code\": \"abc\", \"text\": \"xyz\" }, { }, null ] }"), ref td)); Assert.IsNotNull(td!.NoKeys); Assert.AreEqual(3, td.NoKeys!.Count); @@ -284,7 +284,7 @@ public void Merge_Property_NoKeys_List() [Test] public void Merge_Property_Keys_ListNull() { - var td = new TestData { Keys = new List { new KeyData { Code = "abc", Text = "def" } } }; + var td = new TestData { Keys = new List { new() { Code = "abc", Text = "def" } } }; Assert.IsTrue(new JsonMergePatch().Merge(BinaryData.FromString("{ \"keys\": null }"), ref td)); Assert.IsNull(td!.Keys); @@ -293,7 +293,7 @@ public void Merge_Property_Keys_ListNull() [Test] public void Merge_Property_Keys_ListEmpty() { - var td = new TestData { Keys = new List { new KeyData { Code = "abc", Text = "def" } } }; + var td = new TestData { Keys = new List { new() { Code = "abc", Text = "def" } } }; Assert.IsTrue(new JsonMergePatch().Merge(BinaryData.FromString("{ \"keys\": [ ] }"), ref td)); Assert.IsNotNull(td!.Keys); @@ -303,7 +303,7 @@ public void Merge_Property_Keys_ListEmpty() [Test] public void Merge_Property_Keys_Null() { - var td = new TestData { Keys = new List { new KeyData { Code = "abc", Text = "def" } } }; + var td = new TestData { Keys = new List { new() { Code = "abc", Text = "def" } } }; Assert.IsTrue(new JsonMergePatch().Merge(BinaryData.FromString("{ \"keys\": [ null ] }"), ref td)); Assert.IsNotNull(td!.Keys); @@ -314,7 +314,7 @@ public void Merge_Property_Keys_Null() [Test] public void Merge_Property_Keys_Replace() { - var td = new TestData { Keys = new List { new KeyData { Code = "abc", Text = "def" } } }; + var td = new TestData { Keys = new List { new() { Code = "abc", Text = "def" } } }; Assert.IsTrue(new JsonMergePatch().Merge(BinaryData.FromString("{ \"keys\": [ { \"code\": \"abc\" }, { \"code\": \"uvw\", \"text\": \"xyz\" } ] }"), ref td)); Assert.IsNotNull(td!.Keys); @@ -329,7 +329,7 @@ public void Merge_Property_Keys_Replace() public void Merge_Property_Keys_NoChanges() { // Note, although technically no changes, there is no means to verify without specific equality checking, so is seen as a change. - var td = new TestData { Keys = new List { new KeyData { Code = "abc", Text = "def" } } }; + var td = new TestData { Keys = new List { new() { Code = "abc", Text = "def" } } }; Assert.IsTrue(new JsonMergePatch().Merge(BinaryData.FromString("{ \"keys\": [ { \"code\": \"abc\", \"text\": \"def\" } ] }"), ref td)); Assert.IsNotNull(td!.Keys); @@ -790,7 +790,7 @@ public void Merge_XLoadTest_NoCache_1000() for (int i = 0; i < 1000; i++) { - var td = new TestData { Values = new int[] { 1, 2, 3 }, Keys = new List { new KeyData { Code = "abc", Text = "def" } }, Dict = new Dictionary() { { "a", "b" } }, Dict2 = new Dictionary { { "x", new KeyData { Code = "xx" } } } }; + var td = new TestData { Values = new int[] { 1, 2, 3 }, Keys = new List { new() { Code = "abc", Text = "def" } }, Dict = new Dictionary() { { "a", "b" } }, Dict2 = new Dictionary { { "x", new KeyData { Code = "xx" } } } }; new JsonMergePatch(new JsonMergePatchOptions { EntityKeyCollectionMergeApproach = EntityKeyCollectionMergeApproach.Merge, DictionaryMergeApproach = DictionaryMergeApproach.Merge }).Merge(BinaryData.FromString(text), ref td); } } @@ -805,7 +805,7 @@ public void Merge_XLoadTest_WithCache_1000() for (int i = 0; i < 1000; i++) { - var td = new TestData { Values = new int[] { 1, 2, 3 }, Keys = new List { new KeyData { Code = "abc", Text = "def" } }, Dict = new Dictionary() { { "a", "b" } }, Dict2 = new Dictionary { { "x", new KeyData { Code = "xx" } } } }; + var td = new TestData { Values = new int[] { 1, 2, 3 }, Keys = new List { new() { Code = "abc", Text = "def" } }, Dict = new Dictionary() { { "a", "b" } }, Dict2 = new Dictionary { { "x", new KeyData { Code = "xx" } } } }; jmp.Merge(BinaryData.FromString(text), ref td); } } @@ -919,7 +919,7 @@ public void Merge_RootArray_Simple() [Test] public void Merge_RootArray_Complex() { - var arr = new SubData[] { new SubData { Code = "a", Text = "aa" }, new SubData { Code = "b", Text = "bb" } }; + var arr = new SubData[] { new() { Code = "a", Text = "aa" }, new() { Code = "b", Text = "bb" } }; var jmp = new JsonMergePatch(); // No equality checker so will appear as changed - is a replacement. diff --git a/tests/CoreEx.Test/Framework/Mapping/Converters/TypeToStringConverterTest.cs b/tests/CoreEx.Test/Framework/Mapping/Converters/TypeToStringConverterTest.cs index 7486a814..d3c94dde 100644 --- a/tests/CoreEx.Test/Framework/Mapping/Converters/TypeToStringConverterTest.cs +++ b/tests/CoreEx.Test/Framework/Mapping/Converters/TypeToStringConverterTest.cs @@ -10,7 +10,7 @@ namespace CoreEx.Test.Framework.Mapping.Converters public class TypeToStringConverterTest { private const string GuidString = "382c74c3-721d-4f34-80e5-57657b6cbc27"; - private readonly Guid GuidValue = new Guid(GuidString); + private readonly Guid GuidValue = new(GuidString); [Test] public void Convert() diff --git a/tests/CoreEx.Test/Framework/UnitTesting/AgentTest.cs b/tests/CoreEx.Test/Framework/UnitTesting/AgentTest.cs index ddf13f72..3c1d0902 100644 --- a/tests/CoreEx.Test/Framework/UnitTesting/AgentTest.cs +++ b/tests/CoreEx.Test/Framework/UnitTesting/AgentTest.cs @@ -50,6 +50,17 @@ public void Delete() .AssertNoContent(); } + [Test] + public void Catalogue() + { + var test = ApiTester.Create(); + var x = test.Agent().With() + .Run(a => a.CatalogueAsync("abc")) + .AssertOK() + .AssertContentTypePlainText() + .AssertContent("Catalog for 'abc'."); + } + public class ProductAgent : CoreEx.Http.TypedHttpClientBase { public ProductAgent(HttpClient client, IJsonSerializer jsonSerializer, CoreEx.ExecutionContext executionContext, SettingsBase settings, ILogger logger) @@ -63,6 +74,9 @@ public Task DeleteAsync(string id, CoreEx.Http.HttpRequestOptions? r public Task> UpdateAsync(Product value, string id, CoreEx.Http.HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) => PutAsync("products/{id}", value, requestOptions: requestOptions, args: new IHttpArg[] { new HttpArg("id", id) }, cancellationToken: cancellationToken); + + public Task CatalogueAsync(string id, CoreEx.Http.HttpRequestOptions? requestOptions = null, CancellationToken cancellationToken = default) + => GetAsync("products/{id}/catalogue", requestOptions: requestOptions, args: new IHttpArg[] { new HttpArg("id", id) }, cancellationToken: cancellationToken); } } } \ No newline at end of file diff --git a/tests/CoreEx.Test/Framework/UnitTesting/ValidationTest.cs b/tests/CoreEx.Test/Framework/UnitTesting/ValidationTest.cs index 036c0a55..893e83be 100644 --- a/tests/CoreEx.Test/Framework/UnitTesting/ValidationTest.cs +++ b/tests/CoreEx.Test/Framework/UnitTesting/ValidationTest.cs @@ -2,9 +2,12 @@ using UnitTestEx.NUnit; using UnitTestEx; using UnitTestEx.Expectations; +using CoreEx; using CoreEx.TestApi; using CoreEx.TestApi.Validation; using CoreEx.TestFunction.Models; +using CoreEx.Localization; +using CoreEx.Validation; namespace CoreEx.Test.Framework.UnitTesting { @@ -27,5 +30,22 @@ public void Validate_OK() .ExpectSuccess() .Validation().With(new Product { Id = "abc", Name = "xyz", Price = 50.95m }); } + + [Test] + public void Validate_OK_Provide() + { + GenericTester.Create() + .ExpectSuccess() + .Validation().With(new ProductValidator(), new Product { Id = "abc", Name = "xyz", Price = 50.95m }); + } + + [Test] + public void Validate_Error_Provide() + { + GenericTester.Create() + .ReplaceSingleton() + .ExpectErrors("Name is required.", "Price must be between 0 and 100.") + .Validation().With(new ProductValidator(), new Product { Price = 450.95m }); + } } } \ No newline at end of file diff --git a/tests/CoreEx.Test/Framework/Validation/Rules/CollectionRuleTest.cs b/tests/CoreEx.Test/Framework/Validation/Rules/CollectionRuleTest.cs index e4ece366..dce92fe4 100644 --- a/tests/CoreEx.Test/Framework/Validation/Rules/CollectionRuleTest.cs +++ b/tests/CoreEx.Test/Framework/Validation/Rules/CollectionRuleTest.cs @@ -71,7 +71,7 @@ public async Task Validate_Item() var v1 = await new TestItem[0].Validate("value").Collection(item: CollectionRuleItem.Create(iv)).ValidateAsync(); Assert.IsFalse(v1.HasErrors); - v1 = await new TestItem[] { new TestItem() }.Validate("value").Collection(item: CollectionRuleItem.Create(iv)).ValidateAsync(); + v1 = await new TestItem[] { new() }.Validate("value").Collection(item: CollectionRuleItem.Create(iv)).ValidateAsync(); Assert.IsTrue(v1.HasErrors); Assert.AreEqual(1, v1.Messages!.Count); Assert.AreEqual("Identifier is required.", v1.Messages[0].Text); @@ -98,7 +98,7 @@ public async Task Validate_ItemInt() [Test] public async Task Validate_Item_Null() { - var v1 = await new List { new TestItem() }.Validate("value").Collection().ValidateAsync(); + var v1 = await new List { new() }.Validate("value").Collection().ValidateAsync(); Assert.IsFalse(v1.HasErrors); v1 = await new List() { null }.Validate("value").Collection().ValidateAsync(); @@ -120,7 +120,7 @@ public async Task Validate_Item_Duplicates() var v1 = await new TestItem[0].Validate("value").Collection(item: CollectionRuleItem.Create(iv).DuplicateCheck(x => x.Id)).ValidateAsync(); Assert.IsFalse(v1.HasErrors); - var tis = new TestItem[] { new TestItem { Id = "ABC", Text = "Abc" }, new TestItem { Id = "DEF", Text = "Def" }, new TestItem { Id = "GHI", Text = "Ghi" } }; + var tis = new TestItem[] { new() { Id = "ABC", Text = "Abc" }, new() { Id = "DEF", Text = "Def" }, new() { Id = "GHI", Text = "Ghi" } }; v1 = await tis.Validate("value").Collection(item: CollectionRuleItem.Create(iv).DuplicateCheck(x => x.Id)).ValidateAsync(); Assert.IsFalse(v1.HasErrors); @@ -142,7 +142,7 @@ public async Task Validate_Item_Duplicates_PrimaryKey() var v1 = await new TestItem2[0].Validate("value").Collection(item: CollectionRuleItem.Create(iv).DuplicateCheck()).ValidateAsync(); Assert.IsFalse(v1.HasErrors); - var tis = new TestItem2[] { new TestItem2 { Part1 = "ABC", Part2 = 1, Text = "Abc" }, new TestItem2 { Part1 = "DEF", Part2 = 1, Text = "Def" }, new TestItem2 { Part1 = "GHI", Part2 = 1, Text = "Ghi" } }; + var tis = new TestItem2[] { new() { Part1 = "ABC", Part2 = 1, Text = "Abc" }, new() { Part1 = "DEF", Part2 = 1, Text = "Def" }, new() { Part1 = "GHI", Part2 = 1, Text = "Ghi" } }; v1 = await tis.Validate("value").Collection(item: CollectionRuleItem.Create(iv).DuplicateCheck()).ValidateAsync(); Assert.IsFalse(v1.HasErrors); @@ -164,7 +164,7 @@ public async Task Validate_Item_Duplicates_Identifier() var v1 = await new TestItem[0].Validate("value").Collection(item: CollectionRuleItem.Create(iv).DuplicateCheck(true)).ValidateAsync(); Assert.IsFalse(v1.HasErrors); - var tis = new TestItem[] { new TestItem { Id = "ABC", Text = "Abc" }, new TestItem { Id = "DEF", Text = "Def" }, new TestItem { Id = "GHI", Text = "Ghi" } }; + var tis = new TestItem[] { new() { Id = "ABC", Text = "Abc" }, new() { Id = "DEF", Text = "Def" }, new() { Id = "GHI", Text = "Ghi" } }; v1 = await tis.Validate("value").Collection(item: CollectionRuleItem.Create(iv).DuplicateCheck(true)).ValidateAsync(); Assert.IsFalse(v1.HasErrors); @@ -184,7 +184,7 @@ public async Task Validate_Item_Duplicates_Identifier2() var v1 = await new TestItem3[0].Validate("value").Collection(item: CollectionRuleItem.Create().DuplicateCheck(true)).ValidateAsync(); Assert.IsFalse(v1.HasErrors); - var tis = new TestItem3[] { new TestItem3 { Id = 1.ToGuid() }, new TestItem3 { Id = 2.ToGuid() }, new TestItem3 { Id = 3.ToGuid() } }; + var tis = new TestItem3[] { new() { Id = 1.ToGuid() }, new() { Id = 2.ToGuid() }, new() { Id = 3.ToGuid() } }; v1 = await tis.Validate("value").Collection(item: CollectionRuleItem.Create().DuplicateCheck(true)).ValidateAsync(); Assert.IsFalse(v1.HasErrors); @@ -208,7 +208,7 @@ public async Task Validate_Item_Duplicates_IgnoreInitial() var v1 = await new TestItem3[0].Validate("value").Collection(item: CollectionRuleItem.Create().DuplicateCheck(true)).ValidateAsync(); Assert.IsFalse(v1.HasErrors); - var tis = new TestItem3[] { new TestItem3 { Id = Guid.Empty }, new TestItem3 { Id = 2.ToGuid() }, new TestItem3 { Id = Guid.Empty } }; + var tis = new TestItem3[] { new() { Id = Guid.Empty }, new() { Id = 2.ToGuid() }, new() { Id = Guid.Empty } }; v1 = await tis.Validate("value").Collection(item: CollectionRuleItem.Create().DuplicateCheck(true)).ValidateAsync(); Assert.IsFalse(v1.HasErrors); diff --git a/tests/CoreEx.Test/Framework/Validation/Rules/MandatoryRuleTest.cs b/tests/CoreEx.Test/Framework/Validation/Rules/MandatoryRuleTest.cs index 572fea35..d2f02bfe 100644 --- a/tests/CoreEx.Test/Framework/Validation/Rules/MandatoryRuleTest.cs +++ b/tests/CoreEx.Test/Framework/Validation/Rules/MandatoryRuleTest.cs @@ -67,7 +67,7 @@ public class Foo [Test] public async Task Validate_Entity() { - Foo? foo = new Foo(); + Foo? foo = new(); var v1 = await foo.Validate("value").Mandatory().ValidateAsync(); Assert.IsFalse(v1.HasErrors); diff --git a/tests/CoreEx.Test/Framework/Validation/Rules/NoneRuleTest.cs b/tests/CoreEx.Test/Framework/Validation/Rules/NoneRuleTest.cs index f7ea36e7..11b68245 100644 --- a/tests/CoreEx.Test/Framework/Validation/Rules/NoneRuleTest.cs +++ b/tests/CoreEx.Test/Framework/Validation/Rules/NoneRuleTest.cs @@ -59,7 +59,7 @@ public class Foo [Test] public async Task Validate_Entity() { - Foo? foo = new Foo(); + Foo? foo = new(); var v1 = await foo.Validate("value").None().ValidateAsync(); Assert.IsTrue(v1.HasErrors); Assert.AreEqual(1, v1.Messages!.Count); diff --git a/tests/CoreEx.Test/Framework/Validation/ValidatorTest.cs b/tests/CoreEx.Test/Framework/Validation/ValidatorTest.cs index d0fb8c17..8ad047b3 100644 --- a/tests/CoreEx.Test/Framework/Validation/ValidatorTest.cs +++ b/tests/CoreEx.Test/Framework/Validation/ValidatorTest.cs @@ -445,7 +445,7 @@ public void Entity_ValueCachePerfAsync() public async Task Coll_Validator_MaxCount() { var vxc = Validator.CreateCollection, TestItem>(minCount: 1, maxCount: 2, item: CollectionRuleItem.Create(new TestItemValidator())); - var tc = new List { new TestItem { Id = "A", Text = "aaa" }, new TestItem { Id = "B", Text = "bbb" }, new TestItem { Id = "C", Text = "ccc" } }; + var tc = new List { new() { Id = "A", Text = "aaa" }, new() { Id = "B", Text = "bbb" }, new() { Id = "C", Text = "ccc" } }; var r = await vxc.ValidateAsync(tc); @@ -466,7 +466,7 @@ public async Task Coll_Validator_MaxCount() public async Task Coll_Validator_MinCount() { var vxc = Validator.CreateCollection, TestItem>(minCount: 3, item: CollectionRuleItem.Create(new TestItemValidator())); - var tc = new List { new TestItem { Id = "A", Text = "A" }, new TestItem { Id = "B", Text = "B" } }; + var tc = new List { new() { Id = "A", Text = "A" }, new() { Id = "B", Text = "B" } }; var r = await vxc.ValidateAsync(tc); @@ -481,7 +481,7 @@ public async Task Coll_Validator_MinCount() public async Task Coll_Validator_Duplicate() { var vxc = Validator.CreateCollection, TestItem>(item: CollectionRuleItem.Create(new TestItemValidator()).DuplicateCheck(x => x.Id)); - var tc = new List { new TestItem { Id = "A", Text = "A" }, new TestItem { Id = "A", Text = "A" } }; + var tc = new List { new() { Id = "A", Text = "A" }, new() { Id = "A", Text = "A" } }; var r = await vxc.ValidateAsync(tc); @@ -496,7 +496,7 @@ public async Task Coll_Validator_Duplicate() public async Task Coll_Validator_OK() { var vxc = Validator.CreateCollection, TestItem>(minCount: 1, maxCount: 2, item: CollectionRuleItem.Create(new TestItemValidator()).DuplicateCheck(x => x.Id)); - var tc = new List { new TestItem { Id = "A", Text = "A" }, new TestItem { Id = "B", Text = "B" } }; + var tc = new List { new() { Id = "A", Text = "A" }, new() { Id = "B", Text = "B" } }; var r = await vxc.ValidateAsync(tc); diff --git a/tests/CoreEx.Test/Framework/WebApis/WebApiRequestOptionsTest.cs b/tests/CoreEx.Test/Framework/WebApis/WebApiRequestOptionsTest.cs index 5ddb7975..5604c36a 100644 --- a/tests/CoreEx.Test/Framework/WebApis/WebApiRequestOptionsTest.cs +++ b/tests/CoreEx.Test/Framework/WebApis/WebApiRequestOptionsTest.cs @@ -38,7 +38,7 @@ public void GetRequestOptions_Configured() var ro = new HttpRequestOptions { ETag = "etag-value", IncludeText = true, IncludeInactive = true, Paging = PagingArgs.CreateSkipAndTake(20, 25, true), UrlQueryString = "fruit=apples" }.Include("fielda", "fieldb").Exclude("fieldc"); hr.ApplyRequestOptions(ro); - Assert.NotNull("?$skip=20&$take=25&$count=true&$fields=fielda,fieldb&$exclude=fieldc&$text=true&$inactive=true&fruit=apples", hr.QueryString.Value); + Assert.AreEqual("?$skip=20&$take=25&$count=true&$fields=fielda,fieldb&$exclude=fieldc&$text=true&$inactive=true&fruit=apples", hr.QueryString.Value); var wro = hr.GetRequestOptions(); @@ -54,5 +54,31 @@ public void GetRequestOptions_Configured() Assert.AreEqual(25, wro.Paging.Take); Assert.IsTrue(wro.Paging.IsGetCount); } + + + [Test] + public void GetRequestOptions_Configured_TokenPaging() + { + using var test = FunctionTester.Create(); + var hr = test.CreateHttpRequest(HttpMethod.Get, "https://unittest"); + var ro = new HttpRequestOptions { ETag = "etag-value", IncludeText = true, IncludeInactive = true, Paging = PagingArgs.CreateTokenAndTake("token", 25, true), UrlQueryString = "fruit=apples" }.Include("fielda", "fieldb").Exclude("fieldc"); + + hr.ApplyRequestOptions(ro); + Assert.AreEqual("?$token=token&$take=25&$count=true&$fields=fielda,fieldb&$exclude=fieldc&$text=true&$inactive=true&fruit=apples", hr.QueryString.Value); + + var wro = hr.GetRequestOptions(); + + Assert.NotNull(wro); + Assert.AreSame(hr, wro.Request); + Assert.AreEqual("etag-value", wro.ETag); + Assert.IsTrue(wro.IncludeText); + Assert.IsTrue(wro.IncludeInactive); + Assert.AreEqual(new string[] { "fielda", "fieldb" }, wro.IncludeFields); + Assert.AreEqual(new string[] { "fieldc" }, wro.ExcludeFields); + Assert.NotNull(wro.Paging); + Assert.AreEqual("token", wro.Paging!.Token); + Assert.AreEqual(25, wro.Paging.Take); + Assert.IsTrue(wro.Paging.IsGetCount); + } } } \ No newline at end of file diff --git a/tests/CoreEx.Test/Framework/Wildcards/WildcardTest.cs b/tests/CoreEx.Test/Framework/Wildcards/WildcardTest.cs index 5f46eda0..d6c0e5fb 100644 --- a/tests/CoreEx.Test/Framework/Wildcards/WildcardTest.cs +++ b/tests/CoreEx.Test/Framework/Wildcards/WildcardTest.cs @@ -266,14 +266,14 @@ private List GetPeople() { return new List { - new Person { First = "Amy", Last = "Johnson" }, - new Person { First = "Jenny", Last = "Smith" }, - new Person { First = "Gerry", Last = "McQuire" }, - new Person { First = "Gary", Last = "Lawson" }, - new Person { First = "Simon", Last = "Reynolds" }, - new Person { First = "Amanada", Last = "Gray" }, - new Person { First = "B", Last = "P" }, - new Person { First = null, Last = null } + new() { First = "Amy", Last = "Johnson" }, + new() { First = "Jenny", Last = "Smith" }, + new() { First = "Gerry", Last = "McQuire" }, + new() { First = "Gary", Last = "Lawson" }, + new() { First = "Simon", Last = "Reynolds" }, + new() { First = "Amanada", Last = "Gray" }, + new() { First = "B", Last = "P" }, + new() { First = null, Last = null } }; } diff --git a/tests/CoreEx.TestApi/Controllers/ProductController.cs b/tests/CoreEx.TestApi/Controllers/ProductController.cs index 352beed2..24fb2501 100644 --- a/tests/CoreEx.TestApi/Controllers/ProductController.cs +++ b/tests/CoreEx.TestApi/Controllers/ProductController.cs @@ -5,6 +5,7 @@ using CoreEx.TestFunction.Validators; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; +using System; namespace CoreEx.TestApi.Controllers { @@ -35,5 +36,9 @@ public ProductController(WebApi webApi, ProductService service) [HttpDelete] [Route("{id}")] public Task DeleteAsync(string id) => _webApi.DeleteAsync(Request, _ => _service.DeleteProductAsync(id)); + + [HttpGet] + [Route("{id}/catalogue")] + public Task GetCatalogueAsync(string id) => _webApi.GetAsync(Request, _ => _service.GetCatalogueAsync(id)); } } \ No newline at end of file diff --git a/tests/CoreEx.TestFunction/Services/ProductService.cs b/tests/CoreEx.TestFunction/Services/ProductService.cs index 52a9952d..02c351e0 100644 --- a/tests/CoreEx.TestFunction/Services/ProductService.cs +++ b/tests/CoreEx.TestFunction/Services/ProductService.cs @@ -1,7 +1,9 @@ using AutoMapper; using CoreEx.TestFunction.Models; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System.Collections.Generic; +using System.Text; using System.Threading.Tasks; namespace CoreEx.TestFunction.Services @@ -53,5 +55,11 @@ public Task DeleteProductAsync(string id) _logger.LogInformation($"Deleting product {id}."); return Task.CompletedTask; } + + public Task GetCatalogueAsync(string id) + { + var fcr = new FileContentResult(Encoding.ASCII.GetBytes($"Catalog for '{id}'."), "text/plain"); + return Task.FromResult(fcr); + } } } \ No newline at end of file