diff --git a/Directory.Build.props b/Directory.Build.props index 68e77e8..f321b18 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,11 +18,11 @@ - 10.0.3 + 10.0.11 - 3.0.3 + 3.1.0 DrSkillIssue MIT https://github.com/DrSkillIssue/EFPagination diff --git a/Directory.Packages.props b/Directory.Packages.props index e59f08e..5b2299d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,12 +11,12 @@ - + - + @@ -26,13 +26,13 @@ - - - - + + + + - + diff --git a/docs/api-reference.md b/docs/api-reference.md index 3565556..711b736 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -146,10 +146,8 @@ public sealed class PaginationSortRegistry Behavior: -- `Resolve` uses the default definition when `sortBy` is empty or unknown. -- `TryResolve` returns `false` when `sortBy` does not match any registered field (empty `sortBy` returns the default and `true`). -- Selects the descending variant only when `sortDir` equals `"desc"` (case-insensitive). -- Uses ascending for all other direction values. +- `Resolve` uses the default definition when `sortBy` is empty or unknown, selects the descending variant when `sortDir` equals `"desc"` (case-insensitive), and uses ascending for all other direction values. +- `TryResolve` returns `false` when `sortBy` does not match any registered field or `sortDir` is outside empty, `"asc"`, or `"desc"` (case-insensitive). Empty `sortBy` returns the default and `true`. ## SortField\ / SortField @@ -536,7 +534,7 @@ KeysetQueryBuilder FromRequest( ### Keyset (Registry + Request) -Creates a builder from a sort registry and pagination request, resolving the definition from the request's sort parameters and applying cursors: +Creates a builder from a sort registry and pagination request, resolving the definition from the request's sort parameters and applying cursors. Throws `BadHttpRequestException` for an unknown sort field or an unsupported direction: ```cs KeysetQueryBuilder Keyset( @@ -597,7 +595,7 @@ Task> PaginateAsync( CancellationToken ct = default) where T : class; ``` -Both have a `PaginationSortRegistry` overload that resolves the definition from `request.SortBy`/`request.SortDir`: +Both have a `PaginationSortRegistry` overload that resolves the definition from `request.SortBy`/`request.SortDir`, throwing `BadHttpRequestException` for an unknown sort field or an unsupported direction: ```cs Task> PaginateAsync( @@ -621,6 +619,8 @@ Task> PaginateAsync( The `Expression>` overloads run the projection inside the SQL statement that applies the keyset `ORDER BY`. Subqueries inside the selector stay server-side and the SELECT list materializes only the projected columns plus the keyset key columns. The pagination definition must have 1–8 key columns; outside that range, the overload throws `NotSupportedException`. +Every `PaginateAsync` overload throws `BadHttpRequestException` (status 400) for an invalid or expired cursor and for a `request.PageSize` of zero or less. ASP.NET Core's exception handler middleware maps its status code to the response. + See [Server-Side Projection](patterns.md#server-side-projection) for end-to-end examples. ## Exceptions diff --git a/docs/patterns.md b/docs/patterns.md index f47c342..3eec31e 100644 --- a/docs/patterns.md +++ b/docs/patterns.md @@ -242,7 +242,7 @@ app.MapGet("/api/users", async ( }); ``` -The `Keyset(registry, request)` overload resolves the definition from `request.SortBy`/`request.SortDir`, applies the cursor from `request.After` or `request.Before`, and embeds the sort key in cursor tokens so sort context is preserved across pages. +The `Keyset(registry, request)` overload resolves the definition from `request.SortBy`/`request.SortDir`, applies the cursor from `request.After` or `request.Before`, and embeds the sort key in cursor tokens so sort context is preserved across pages. It throws `BadHttpRequestException` (status 400) for an unknown sort field or an unsupported direction. ## Manual Cursor Encode/Decode diff --git a/docs/prebuilt-definitions.md b/docs/prebuilt-definitions.md index 39c1be2..0a720a5 100644 --- a/docs/prebuilt-definitions.md +++ b/docs/prebuilt-definitions.md @@ -80,7 +80,7 @@ var page = await dbContext.Users `Resolve` is case-insensitive for both field names and `desc`, and falls back to the default definition when the request asks for an unknown sort. -`TryResolve` returns `false` instead of falling back, allowing you to reject invalid sort field names: +`TryResolve` returns `false` for an unknown sort field or an unsupported direction, allowing you to reject invalid sort requests: ```cs if (!Sorts.TryResolve(sortBy, sortDir, out var definition)) diff --git a/samples/packages.lock.json b/samples/packages.lock.json index 97365fd..65075f9 100644 --- a/samples/packages.lock.json +++ b/samples/packages.lock.json @@ -4,105 +4,105 @@ "net10.0": { "Microsoft.EntityFrameworkCore": { "type": "Direct", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "VOSGU8en6HZJs8t7UMFN+9vGcRgVOOn6fA44Ngcg2NyvJ3P1KE94iAb0XzaVaGhXGtt+qaM/VtEn0/hzluQJeg==", "dependencies": { - "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", - "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3" + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.11", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.11" } }, "Microsoft.EntityFrameworkCore.Sqlite": { "type": "Direct", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "pXP9l00ZzsZuS6ajUJFLHi5vt3vVxHaxQXCwRs73tyqNbjPX/4ac114wyfAepMSmUKq0roFFoTZ7h+1K1+iQjQ==", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "jc7iVrhQyInR3loraMESfEFaFOtQOB1mRKHjX6QYC9o7YDbfMNbAPnIwlpffnFwhXd6/27FKaaV+sWSoLd4F1g==", "dependencies": { - "Microsoft.EntityFrameworkCore.Sqlite.Core": "10.0.3", - "Microsoft.Extensions.DependencyModel": "10.0.3", - "SQLitePCLRaw.bundle_e_sqlite3": "2.1.11", - "SQLitePCLRaw.core": "2.1.11" + "Microsoft.EntityFrameworkCore.Sqlite.Core": "10.0.11", + "Microsoft.Extensions.DependencyModel": "10.0.11", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.12", + "SQLitePCLRaw.core": "2.1.12" } }, "Microsoft.Data.Sqlite.Core": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "onD94qHlvteS2S9eg1T7Huehm3x92no4nU1AyDWjSmT6jDBhY8QF0UapwNhFA+1dArzxM10fV7s6uI1EIK2+dw==", + "resolved": "10.0.11", + "contentHash": "hubA20AGenQ4Sx0ElWaPpB8DISjXpdx463+1zOGRslsT0e/t/06ITv+pHsop8CcJ0d8PZLfgnT7juCDVD79Dkw==", "dependencies": { - "SQLitePCLRaw.core": "2.1.11" + "SQLitePCLRaw.core": "2.1.12" } }, "Microsoft.EntityFrameworkCore.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==" + "resolved": "10.0.11", + "contentHash": "6auJR+9+9VunznKfH7WGrHMrnrmA0F7JZ22EXzwXvVhjfnbu9Xq7NSIWaOf3KJsOanM2qf5ajJ2JR5TlcPZTLA==" }, "Microsoft.EntityFrameworkCore.Analyzers": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==" + "resolved": "10.0.11", + "contentHash": "Bv7X4wSSnzCQED9WYXKJ8fwgyvKwf0xZM1GO8xkf6CF9zl+UBnvjxmcPnokJRy0JKjc1SlHSzzhx1HcL4jitTQ==" }, "Microsoft.EntityFrameworkCore.Relational": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "Pmh60OK9neVr/M0FJwm9hlzm2bD4Kd65SID8E6SP5c90tExNgXwORrlEWl0oGU/ig9ifpNN4PSpIrnHNozlT5w==", + "resolved": "10.0.11", + "contentHash": "grznnTJgEYxaWpdKAsTzg6j+89jHgCXWYp+QGtlX5O92+w/VuhWM6JLPYb+uw8M9VhGUvOTsO76dYOy9vNPd5Q==", "dependencies": { - "Microsoft.EntityFrameworkCore": "10.0.3" + "Microsoft.EntityFrameworkCore": "10.0.11" } }, "Microsoft.EntityFrameworkCore.Sqlite.Core": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "bUkOkSwPqvhNlsMMg5dA/PR9S4cVehiHEIZklakRH6JZCFsEBNvUz8kBRGK8Hi6mcaPKUAmVZQkN3moZsBYZLA==", + "resolved": "10.0.11", + "contentHash": "ywTQKt32xnVhCzjEQAqFufpEyXkOUfvW/EC/s4xnS8Xaor2xXE+TMUyzhgACqXtZEU5IR95y94RDzHto55Fx7w==", "dependencies": { - "Microsoft.Data.Sqlite.Core": "10.0.3", - "Microsoft.EntityFrameworkCore.Relational": "10.0.3", - "Microsoft.Extensions.DependencyModel": "10.0.3", - "SQLitePCLRaw.core": "2.1.11" + "Microsoft.Data.Sqlite.Core": "10.0.11", + "Microsoft.EntityFrameworkCore.Relational": "10.0.11", + "Microsoft.Extensions.DependencyModel": "10.0.11", + "SQLitePCLRaw.core": "2.1.12" } }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "31kRjr1fgdJO1UZ/AsjL2noqwht+juHMQ8c/oh8CEsDhlM2+0zwVZVsZjxSfOFiPtn5+6kRGuvSbLAufAPT0kA==" + "resolved": "10.0.11", + "contentHash": "PJPtFYsZ+r+uz9qqXWUTEyKeJ1EiBGIJtqavkg9ZXijjGSFAk4Fgi5sqIxj+uAyLZwEKgexDUQXhWhvU6l3+og==" }, "SQLitePCLRaw.bundle_e_sqlite3": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "DC4nA7yWnf4UZdgJDF+9Mus4/cb0Y3Sfgi3gDnAoKNAIBwzkskNAbNbyu+u4atT0ruVlZNJfwZmwiEwE5oz9LQ==", + "resolved": "2.1.12", + "contentHash": "mAgscpQMLw5/nfA1Q5oJVAT29yROUo1ifZGbbTpx/lwZpSxMUGoYbKfmvdm8oXER+RzxqBmmQzeBEVKfeHv2nw==", "dependencies": { - "SQLitePCLRaw.lib.e_sqlite3": "2.1.11", - "SQLitePCLRaw.provider.e_sqlite3": "2.1.11" + "SQLitePCLRaw.lib.e_sqlite3": "2.1.12", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.12" } }, "SQLitePCLRaw.core": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "PK0GLFkfhZzLQeR3PJf71FmhtHox+U3vcY6ZtswoMjrefkB9k6ErNJEnwXqc5KgXDSjige2XXrezqS39gkpQKA==" + "resolved": "2.1.12", + "contentHash": "ETpNw9DY3ckWLgRRAeCHj+GKOuPi61aeczkXhgHexUvqoZBAYg8RYESE2J7O1M7+o6QbdSEZwrw9bfqztUVWXg==" }, "SQLitePCLRaw.lib.e_sqlite3": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "Ev2ytaXiOlWZ4b3R67GZBsemTINslLD1DCJr2xiacpn4tbapu0Q4dHEzSvZSMnVWeE5nlObU3VZN2p81q3XOYQ==" + "resolved": "2.1.12", + "contentHash": "fWi8Dbknuhgg72fWinIdjXVaqO1hHL4YBBwVLnr7e1c9TAZwJ0QE38j9syW1hwx6HaqEVTwI+O07WPdZn8Rp0w==" }, "SQLitePCLRaw.provider.e_sqlite3": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "Y/0ZkR+r0Cg3DQFuCl1RBnv/tmxpIZRU3HUvelPw6MVaKHwYYR8YNvgs0vuNuXCMvlyJ+Fh88U1D4tah1tt6qw==", + "resolved": "2.1.12", + "contentHash": "W3oH4XIfCzFrgUSDKHhN6N+dgzA5YHOR2VxX8GB6Qy7CyrJJgxPEG8NirgYWlPQC5P2jz2knSsexWu4tDUL33g==", "dependencies": { - "SQLitePCLRaw.core": "2.1.11" + "SQLitePCLRaw.core": "2.1.12" } }, "efpagination": { "type": "Project", "dependencies": { - "Microsoft.EntityFrameworkCore": "[10.0.3, )" + "Microsoft.EntityFrameworkCore": "[10.0.11, )" } }, "efpagination.aspnetcore": { "type": "Project", "dependencies": { - "EFPagination": "[1.1.3, )" + "EFPagination": "[3.0.3, )" } } } diff --git a/src/EFPagination.Analyzers/packages.lock.json b/src/EFPagination.Analyzers/packages.lock.json index 740c7be..105e768 100644 --- a/src/EFPagination.Analyzers/packages.lock.json +++ b/src/EFPagination.Analyzers/packages.lock.json @@ -4,31 +4,31 @@ ".NETStandard,Version=v2.0": { "Microsoft.CodeAnalysis.CSharp": { "type": "Direct", - "requested": "[5.3.0, )", - "resolved": "5.3.0", - "contentHash": "SQFNGQF4f7UfDXKxMzzGNMr3fjrPDIjLfmRvvVgDCw+dyvEHDaRfHuKA5q0Pr0/JW0Gcw89TxrxrS/MjwBvluQ==", + "requested": "[5.6.0, )", + "resolved": "5.6.0", + "contentHash": "r1DrKQ/L0xTw03wJrLr36AMQNslyaeEKBFyFmQcOKa8HX3YvmhY//JEUafb6IR/m0gmaUVCfBTWitKJRNb7YAA==", "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", - "Microsoft.CodeAnalysis.Common": "[5.3.0]", - "System.Buffers": "4.6.0", - "System.Collections.Immutable": "9.0.0", - "System.Memory": "4.6.0", - "System.Numerics.Vectors": "4.6.0", - "System.Reflection.Metadata": "9.0.0", - "System.Runtime.CompilerServices.Unsafe": "6.1.0", + "Microsoft.CodeAnalysis.Analyzers": "5.3.0", + "Microsoft.CodeAnalysis.Common": "[5.6.0]", + "System.Buffers": "4.6.1", + "System.Collections.Immutable": "10.0.1", + "System.Memory": "4.6.3", + "System.Numerics.Vectors": "4.6.1", + "System.Reflection.Metadata": "10.0.1", + "System.Runtime.CompilerServices.Unsafe": "6.1.2", "System.Text.Encoding.CodePages": "8.0.0", - "System.Threading.Tasks.Extensions": "4.6.0" + "System.Threading.Tasks.Extensions": "4.6.3" } }, "Microsoft.SourceLink.GitHub": { "type": "Direct", - "requested": "[10.0.201, )", - "resolved": "10.0.201", - "contentHash": "qxYAmO4ktzd9L+HMdnqWucxpu7bI9undPyACXOMqPyhaiMtbpbYL/n0ACyWIJlbyEJrXFwxiOaBOSasLtDvsCg==", + "requested": "[10.0.400, )", + "resolved": "10.0.400", + "contentHash": "N1Ut0+EmMb2Tma9spHxAgpCspkln4gst1RWYc1PYPn8LA4KJWDSiggKpE183lfR98TNG2C+XnJ71zSYtuCSsqA==", "dependencies": { - "Microsoft.Build.Tasks.Git": "10.0.201", - "Microsoft.SourceLink.Common": "10.0.201", - "System.IO.Hashing": "10.0.5" + "Microsoft.Build.Tasks.Git": "10.0.400", + "Microsoft.SourceLink.Common": "10.0.400", + "System.IO.Hashing": "10.0.11" } }, "NETStandard.Library": { @@ -42,31 +42,31 @@ }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", - "resolved": "10.0.201", - "contentHash": "DMYBnrFZvLnBKn14VavEuuIr31CY6YY2i2L9P8DorS/Qp6ifRR8ZPLdJCFLFfjikNq8DykbYyLd/RP6lSqHcWw==", + "resolved": "10.0.400", + "contentHash": "uXJ/eBDqAo9HlyPjvvvynjHb59KXQWG9txpBzyI1drO6MV3Lhndt2yhxuMKvIga4kND2UKc3xCNXlXBhWU8Hlg==", "dependencies": { - "System.IO.Hashing": "10.0.5" + "System.IO.Hashing": "10.0.11" } }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", - "resolved": "5.3.0-2.25625.1", - "contentHash": "4Yhh2fnu3G+J0J1lDc8WZVgMjgbynSeTfkl5IFJMFrmiIO0sc7Tjx+f3sFVV8Sd35PrIUWfof0RWc3lAMl7Azg==" + "resolved": "5.3.0", + "contentHash": "KuLhbZwB0L8JikL86AE5VWEp3RLNjIcp+j8yz9EJ/UBgRz4+qDEjHg/tluRFbpYpD/e37BqaaNFbQ0vqawBwWQ==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", - "resolved": "5.3.0", - "contentHash": "uC0qk3jzTQY7i90ehfnCqaOZpBUGJyPMiHJ3c0jOb8yaPBjWzIhVdNxPbeVzI74DB0C+YgBKPLqUkgFZzua5Mg==", + "resolved": "5.6.0", + "contentHash": "eWYNB5e92PSdkQ0xcmy2aLtrvBXNydnVi0Hj/VjaAely6XBqA3By+ClGAJaj4d16pzQmrXPLLK9RDVuS1Ec9xQ==", "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", - "System.Buffers": "4.6.0", - "System.Collections.Immutable": "9.0.0", - "System.Memory": "4.6.0", - "System.Numerics.Vectors": "4.6.0", - "System.Reflection.Metadata": "9.0.0", - "System.Runtime.CompilerServices.Unsafe": "6.1.0", + "Microsoft.CodeAnalysis.Analyzers": "5.3.0", + "System.Buffers": "4.6.1", + "System.Collections.Immutable": "10.0.1", + "System.Memory": "4.6.3", + "System.Numerics.Vectors": "4.6.1", + "System.Reflection.Metadata": "10.0.1", + "System.Runtime.CompilerServices.Unsafe": "6.1.2", "System.Text.Encoding.CodePages": "8.0.0", - "System.Threading.Tasks.Extensions": "4.6.0" + "System.Threading.Tasks.Extensions": "4.6.3" } }, "Microsoft.NETCore.Platforms": { @@ -76,8 +76,8 @@ }, "Microsoft.SourceLink.Common": { "type": "Transitive", - "resolved": "10.0.201", - "contentHash": "QbBYhkjgL6rCnBfDbzsAJLlsad13TlBHqYCFDIw56OO2g6ix+9RsmY8uxiQGdWwFKbZXaXyAA6jDCzFYVGCZDw==" + "resolved": "10.0.400", + "contentHash": "Knli3Y2k/rdBqUlQ8TP+Sbd0J0jV/0kvgLsxieowoVayioZ0JGfqb9go+/dQJqtU53i6IpZzRxAPxbBT5UJBNg==" }, "System.Buffers": { "type": "Transitive", @@ -86,17 +86,17 @@ }, "System.Collections.Immutable": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==", + "resolved": "10.0.1", + "contentHash": "kdTe61B8P7i2M1pODC3MLbZ/CfFGjpC6c6jzxjQoB5DHZNewayCRqgFUmx3JKB6vLQtozpMQEiw+R5fO32Jv4g==", "dependencies": { - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" + "System.Memory": "4.6.3", + "System.Runtime.CompilerServices.Unsafe": "6.1.2" } }, "System.IO.Hashing": { "type": "Transitive", - "resolved": "10.0.5", - "contentHash": "8IBJWcCT9+e4Bmevm4T7+fQEiAh133KGiz4oiVTgJckd3Q76OFdR1falgn9lpz7+C4HJvogCDJeAa2QmvbeVtg==", + "resolved": "10.0.11", + "contentHash": "OzKDcIRkeNJeC8qAsbn8yJXnfTLP1dtkWILe+T56Gf/z+IkAASi7sMqLqJQat08j5z/mRN5xVtoAwbkMNMoBUQ==", "dependencies": { "System.Buffers": "4.6.1", "System.Memory": "4.6.3" @@ -119,11 +119,10 @@ }, "System.Reflection.Metadata": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "ANiqLu3DxW9kol/hMmTWbt3414t9ftdIuiIU7j80okq2YzAueo120M442xk1kDJWtmZTqWQn7wHDvMRipVOEOQ==", + "resolved": "10.0.1", + "contentHash": "zpcfT/wacPPhE17zcudozlxQtWN/84qyiMyZNGLnK4cj2IMBtLsZYwYjVnALUhPliwyUVj/P7kaZvBWYBCnf2Q==", "dependencies": { - "System.Collections.Immutable": "9.0.0", - "System.Memory": "4.5.5" + "System.Collections.Immutable": "10.0.1" } }, "System.Runtime.CompilerServices.Unsafe": { @@ -142,10 +141,10 @@ }, "System.Threading.Tasks.Extensions": { "type": "Transitive", - "resolved": "4.6.0", - "contentHash": "I5G6Y8jb0xRtGUC9Lahy7FUvlYlnGMMkbuKAQBy8Jb7Y6Yn8OlBEiUOY0PqZ0hy6Ua8poVA1ui1tAIiXNxGdsg==", + "resolved": "4.6.3", + "contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==", "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "6.1.0" + "System.Runtime.CompilerServices.Unsafe": "6.1.2" } } } diff --git a/src/EFPagination.AspNetCore/KeysetBuilderAspNetExtensions.cs b/src/EFPagination.AspNetCore/KeysetBuilderAspNetExtensions.cs index b511adf..495cc1d 100644 --- a/src/EFPagination.AspNetCore/KeysetBuilderAspNetExtensions.cs +++ b/src/EFPagination.AspNetCore/KeysetBuilderAspNetExtensions.cs @@ -42,9 +42,8 @@ public static KeysetQueryBuilder Keyset( PaginationRequest request) where T : class { ArgumentNullException.ThrowIfNull(source); - ArgumentNullException.ThrowIfNull(registry); - var definition = registry.Resolve(request.SortBy.AsSpan(), request.SortDir.AsSpan()); + var definition = registry.Resolve(request); return new KeysetQueryBuilder(source, definition) .WithSortBy(request.SortBy) .FromRequest(request); diff --git a/src/EFPagination.AspNetCore/PaginationEndpointExtensions.cs b/src/EFPagination.AspNetCore/PaginationEndpointExtensions.cs index 281f6f4..ec53042 100644 --- a/src/EFPagination.AspNetCore/PaginationEndpointExtensions.cs +++ b/src/EFPagination.AspNetCore/PaginationEndpointExtensions.cs @@ -1,4 +1,5 @@ using System.Linq.Expressions; +using Microsoft.AspNetCore.Http; namespace EFPagination.AspNetCore; @@ -31,20 +32,29 @@ public static async Task> PaginateAsync( bool includeCount = false, CancellationToken ct = default) where T : class { - var (cursor, direction) = ResolveCursorAndDirection(request); + var (cursor, direction) = request.Before is not null + ? (request.Before, PaginationDirection.Backward) + : (request.After, PaginationDirection.Forward); - var page = await PaginationExecutor.ExecuteFromCursorAsync( - query, - definition, - new ExecutionOptions( - PageSize: request.PageSize, - Direction: direction, - IncludeCount: includeCount, - MaxPageSize: maxPageSize), - cursor, - ct).ConfigureAwait(false); + try + { + var page = await PaginationExecutor.ExecuteFromCursorAsync( + query, + definition, + new ExecutionOptions( + PageSize: request.PageSize, + Direction: direction, + IncludeCount: includeCount, + MaxPageSize: maxPageSize), + cursor, + ct).ConfigureAwait(false); - return page.ToPaginatedResponse(selector); + return page.ToPaginatedResponse(selector); + } + catch (ArgumentException e) when (e is not ArgumentNullException) + { + throw new BadHttpRequestException(e.Message, e); + } } /// @@ -73,8 +83,7 @@ public static Task> PaginateAsync( bool includeCount = false, CancellationToken ct = default) where T : class { - ArgumentNullException.ThrowIfNull(registry); - var definition = registry.Resolve(request.SortBy.AsSpan(), request.SortDir.AsSpan()); + var definition = registry.Resolve(request); return PaginateAsync(query, definition, request, selector, maxPageSize, includeCount, ct); } @@ -111,12 +120,20 @@ public static async Task> PaginateAsync( ArgumentNullException.ThrowIfNull(selector); var effectivePageSize = request.PageSize > maxPageSize ? maxPageSize : request.PageSize; - var page = await query.Keyset(definition).FromRequest(request) - .MaxPageSize(maxPageSize) - .ConfigureIncludeCount(includeCount) - .TakeAsync(effectivePageSize, selector, ct).ConfigureAwait(false); + var builder = query.Keyset(definition).FromRequest(request).MaxPageSize(maxPageSize); + if (includeCount) + builder = builder.IncludeCount(); - return page.ToPaginatedResponse(); + try + { + var page = await builder.TakeAsync(effectivePageSize, selector, ct).ConfigureAwait(false); + + return page.ToPaginatedResponse(); + } + catch (ArgumentException e) when (e is not ArgumentNullException) + { + throw new BadHttpRequestException(e.Message, e); + } } /// @@ -145,19 +162,8 @@ public static Task> PaginateAsync( bool includeCount = false, CancellationToken ct = default) where T : class { - ArgumentNullException.ThrowIfNull(registry); - var definition = registry.Resolve(request.SortBy.AsSpan(), request.SortDir.AsSpan()); + var definition = registry.Resolve(request); return PaginateAsync(query, definition, request, selector, maxPageSize, includeCount, ct); } - private static KeysetQueryBuilder ConfigureIncludeCount(this KeysetQueryBuilder builder, bool includeCount) where T : class - => includeCount ? builder.IncludeCount() : builder; - - private static (string? cursor, PaginationDirection direction) ResolveCursorAndDirection(PaginationRequest request) - { - if (request.Before is not null) - return (request.Before, PaginationDirection.Backward); - - return (request.After, PaginationDirection.Forward); - } } diff --git a/src/EFPagination.AspNetCore/PaginationSortRegistryAspNetExtensions.cs b/src/EFPagination.AspNetCore/PaginationSortRegistryAspNetExtensions.cs new file mode 100644 index 0000000..23d0302 --- /dev/null +++ b/src/EFPagination.AspNetCore/PaginationSortRegistryAspNetExtensions.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Http; + +namespace EFPagination.AspNetCore; + +/// +/// Extensions for resolving sort parameters against a . +/// +public static class PaginationSortRegistryAspNetExtensions +{ + /// + /// Resolves the definition for the request's sort parameters. + /// + /// The entity type handled by the registry. + /// The registry of sortable fields. + /// The bound pagination request parameters. + /// The matched pagination definition, or the default definition when no sort was requested. + /// names an unknown sort field. + /// -or- + /// names an unsupported sort direction. + public static PaginationQueryDefinition Resolve(this PaginationSortRegistry registry, PaginationRequest request) + { + ArgumentNullException.ThrowIfNull(registry); + + if (registry.TryResolve(request.SortBy.AsSpan(), request.SortDir.AsSpan(), out var definition)) + return definition; + + var dir = request.SortDir.AsSpan(); + if (!dir.IsEmpty && + !dir.Equals("asc", StringComparison.OrdinalIgnoreCase) && + !dir.Equals("desc", StringComparison.OrdinalIgnoreCase)) + { + throw new BadHttpRequestException($"sortDir '{request.SortDir}' is not supported. sortDir must be 'asc' or 'desc'."); + } + + throw new BadHttpRequestException($"'{request.SortBy}' is not a sortable field."); + } +} diff --git a/src/EFPagination.AspNetCore/packages.lock.json b/src/EFPagination.AspNetCore/packages.lock.json index 48f50ff..2b67a10 100644 --- a/src/EFPagination.AspNetCore/packages.lock.json +++ b/src/EFPagination.AspNetCore/packages.lock.json @@ -4,57 +4,57 @@ "net10.0": { "Microsoft.SourceLink.GitHub": { "type": "Direct", - "requested": "[10.0.201, )", - "resolved": "10.0.201", - "contentHash": "qxYAmO4ktzd9L+HMdnqWucxpu7bI9undPyACXOMqPyhaiMtbpbYL/n0ACyWIJlbyEJrXFwxiOaBOSasLtDvsCg==", + "requested": "[10.0.400, )", + "resolved": "10.0.400", + "contentHash": "N1Ut0+EmMb2Tma9spHxAgpCspkln4gst1RWYc1PYPn8LA4KJWDSiggKpE183lfR98TNG2C+XnJ71zSYtuCSsqA==", "dependencies": { - "Microsoft.Build.Tasks.Git": "10.0.201", - "Microsoft.SourceLink.Common": "10.0.201", - "System.IO.Hashing": "10.0.5" + "Microsoft.Build.Tasks.Git": "10.0.400", + "Microsoft.SourceLink.Common": "10.0.400", + "System.IO.Hashing": "10.0.11" } }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", - "resolved": "10.0.201", - "contentHash": "DMYBnrFZvLnBKn14VavEuuIr31CY6YY2i2L9P8DorS/Qp6ifRR8ZPLdJCFLFfjikNq8DykbYyLd/RP6lSqHcWw==", + "resolved": "10.0.400", + "contentHash": "uXJ/eBDqAo9HlyPjvvvynjHb59KXQWG9txpBzyI1drO6MV3Lhndt2yhxuMKvIga4kND2UKc3xCNXlXBhWU8Hlg==", "dependencies": { - "System.IO.Hashing": "10.0.5" + "System.IO.Hashing": "10.0.11" } }, "Microsoft.EntityFrameworkCore.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==" + "resolved": "10.0.11", + "contentHash": "6auJR+9+9VunznKfH7WGrHMrnrmA0F7JZ22EXzwXvVhjfnbu9Xq7NSIWaOf3KJsOanM2qf5ajJ2JR5TlcPZTLA==" }, "Microsoft.EntityFrameworkCore.Analyzers": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==" + "resolved": "10.0.11", + "contentHash": "Bv7X4wSSnzCQED9WYXKJ8fwgyvKwf0xZM1GO8xkf6CF9zl+UBnvjxmcPnokJRy0JKjc1SlHSzzhx1HcL4jitTQ==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", - "resolved": "10.0.201", - "contentHash": "QbBYhkjgL6rCnBfDbzsAJLlsad13TlBHqYCFDIw56OO2g6ix+9RsmY8uxiQGdWwFKbZXaXyAA6jDCzFYVGCZDw==" + "resolved": "10.0.400", + "contentHash": "Knli3Y2k/rdBqUlQ8TP+Sbd0J0jV/0kvgLsxieowoVayioZ0JGfqb9go+/dQJqtU53i6IpZzRxAPxbBT5UJBNg==" }, "System.IO.Hashing": { "type": "Transitive", - "resolved": "10.0.5", - "contentHash": "8IBJWcCT9+e4Bmevm4T7+fQEiAh133KGiz4oiVTgJckd3Q76OFdR1falgn9lpz7+C4HJvogCDJeAa2QmvbeVtg==" + "resolved": "10.0.11", + "contentHash": "OzKDcIRkeNJeC8qAsbn8yJXnfTLP1dtkWILe+T56Gf/z+IkAASi7sMqLqJQat08j5z/mRN5xVtoAwbkMNMoBUQ==" }, "efpagination": { "type": "Project", "dependencies": { - "Microsoft.EntityFrameworkCore": "[10.0.3, )" + "Microsoft.EntityFrameworkCore": "[10.0.11, )" } }, "Microsoft.EntityFrameworkCore": { "type": "CentralTransitive", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "VOSGU8en6HZJs8t7UMFN+9vGcRgVOOn6fA44Ngcg2NyvJ3P1KE94iAb0XzaVaGhXGtt+qaM/VtEn0/hzluQJeg==", "dependencies": { - "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", - "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3" + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.11", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.11" } } } diff --git a/src/EFPagination/Cursor/CursorDecoder.cs b/src/EFPagination/Cursor/CursorDecoder.cs index b8feb08..0dba568 100644 --- a/src/EFPagination/Cursor/CursorDecoder.cs +++ b/src/EFPagination/Cursor/CursorDecoder.cs @@ -47,126 +47,92 @@ public static bool TryDecodeWithDefinition( return false; var payload = scratch[..bytesWritten]; - if (!ParseHeader(payload, signingKey, out var header)) + if (payload.Length < 2 + 4 || payload[0] != CursorFormat.Version) return false; - if (header.Fingerprint != definition.SchemaFingerprint) - return false; - - var reader = new CursorReader(payload.Slice(header.BodyStart, header.BodyEnd - header.BodyStart)); - if (!DecodeBody(ref reader, definition, bindings, out var valueCount)) - return false; - if (reader.Failed || reader.Remaining != 0) - return false; + var flags = payload[1]; + var signed = (flags & CursorFormat.FlagSigned) != 0; - metadata = new CursorMetadata(header.SortBy, header.TotalCount, header.Fingerprint, valueCount); - return true; - } - catch (FormatException) { return false; } - catch (InvalidOperationException) { return false; } - catch (InvalidCastException) { return false; } - finally - { - if (rented is not null) ArrayPool.Shared.Return(rented); - } - } + int bodyEnd; + if (signed) + { + if (signingKey is null) return false; + if (payload.Length < 2 + 4 + CursorFormat.HmacTruncatedLength) return false; + bodyEnd = payload.Length - CursorFormat.HmacTruncatedLength; + + Span expected = stackalloc byte[HMACSHA256.HashSizeInBytes]; + HMACSHA256.HashData(signingKey, payload[..bodyEnd], expected); + if (!CryptographicOperations.FixedTimeEquals(expected[..CursorFormat.HmacTruncatedLength], payload.Slice(bodyEnd, CursorFormat.HmacTruncatedLength))) + return false; + } + else + { + if (signingKey is not null) return false; + bodyEnd = payload.Length; + } - private readonly ref struct ParsedHeader - { - public uint Fingerprint { get; init; } - public string? SortBy { get; init; } - public int? TotalCount { get; init; } - public int BodyStart { get; init; } - public int BodyEnd { get; init; } - } + var headerReader = new CursorReader(payload[2..bodyEnd]); - [SkipLocalsInit] - private static bool ParseHeader(ReadOnlySpan payload, byte[]? signingKey, out ParsedHeader header) - { - header = default; - if (payload.Length < 2 + 4 || payload[0] != CursorFormat.Version) - return false; + var fingerprint = headerReader.ReadUInt32(); + if (headerReader.Failed) return false; - var flags = payload[1]; - var signed = (flags & CursorFormat.FlagSigned) != 0; + string? sortBy = null; + if ((flags & CursorFormat.FlagSortBy) != 0) + { + sortBy = headerReader.ReadString(); + if (headerReader.Failed) return false; + } - int bodyEnd; - if (signed) - { - if (signingKey is null) return false; - if (payload.Length < 2 + 4 + CursorFormat.HmacTruncatedLength) return false; - bodyEnd = payload.Length - CursorFormat.HmacTruncatedLength; + int? totalCount = null; + if ((flags & CursorFormat.FlagTotalCount) != 0) + { + totalCount = headerReader.ReadVarInt32(); + if (headerReader.Failed) return false; + } - Span expected = stackalloc byte[HMACSHA256.HashSizeInBytes]; - HMACSHA256.HashData(signingKey, payload[..bodyEnd], expected); - if (!CryptographicOperations.FixedTimeEquals(expected[..CursorFormat.HmacTruncatedLength], payload.Slice(bodyEnd, CursorFormat.HmacTruncatedLength))) + if (fingerprint != definition.SchemaFingerprint) return false; - } - else - { - if (signingKey is not null) return false; - bodyEnd = payload.Length; - } - var reader = new CursorReader(payload[2..bodyEnd]); + var bodyStart = 2 + headerReader.Position; + var reader = new CursorReader(payload.Slice(bodyStart, bodyEnd - bodyStart)); - var fingerprint = reader.ReadUInt32(); - if (reader.Failed) return false; - - string? sortBy = null; - if ((flags & CursorFormat.FlagSortBy) != 0) - { - sortBy = reader.ReadString(); + var count = (int)reader.ReadVarUInt32(); if (reader.Failed) return false; - } - int? totalCount = null; - if ((flags & CursorFormat.FlagTotalCount) != 0) - { - totalCount = reader.ReadVarInt32(); - if (reader.Failed) return false; - } + var columns = definition.Columns; + if (count != columns.Length || bindings.Length < count) return false; - header = new ParsedHeader - { - Fingerprint = fingerprint, - SortBy = sortBy, - TotalCount = totalCount, - BodyStart = 2 + reader.Position, - BodyEnd = bodyEnd, - }; - return true; - } - - private static bool DecodeBody(ref CursorReader reader, PaginationQueryDefinition definition, ColumnBinding[] bindings, out int valueCount) - { - valueCount = 0; - var count = (int)reader.ReadVarUInt32(); - if (reader.Failed) return false; + var nullMaskLength = (count + 7) >>> 3; + var nullMask = reader.ReadRawBytes(nullMaskLength); + if (reader.Failed) return false; - var columns = definition.Columns; - if (count != columns.Length || bindings.Length < count) return false; + for (var i = 0; i < count; i++) + { + var col = columns[i]; + if (((nullMask[i >>> 3] >>> (i & 7)) & 1) != 0) + { + if (!col.IsNullable) return false; + col.WriteBindingFromBoxed(null, bindings[i]); + } + else + { + col.DecodeCursorValueInto(ref reader, bindings[i]); + if (reader.Failed) return false; + } + } - var nullMaskLength = (count + 7) >>> 3; - var nullMask = reader.ReadRawBytes(nullMaskLength); - if (reader.Failed) return false; + if (reader.Failed || reader.Remaining != 0) + return false; - for (var i = 0; i < count; i++) + metadata = new CursorMetadata(sortBy, totalCount, fingerprint, count); + return true; + } + catch (FormatException) { return false; } + catch (InvalidOperationException) { return false; } + catch (InvalidCastException) { return false; } + finally { - var col = columns[i]; - if (((nullMask[i >>> 3] >>> (i & 7)) & 1) != 0) - { - if (!col.IsNullable) return false; - col.WriteBindingFromBoxed(null, bindings[i]); - } - else - { - col.DecodeCursorValueInto(ref reader, bindings[i]); - if (reader.Failed) return false; - } + if (rented is not null) ArrayPool.Shared.Return(rented); } - - valueCount = count; - return true; } } diff --git a/src/EFPagination/Cursor/CursorEncoder.cs b/src/EFPagination/Cursor/CursorEncoder.cs index 39015ba..66af74b 100644 --- a/src/EFPagination/Cursor/CursorEncoder.cs +++ b/src/EFPagination/Cursor/CursorEncoder.cs @@ -50,8 +50,21 @@ private static string EncodeCore(PaginationCursorOptions options, scoped { var writer = new CursorWriter(buffer); body.Write(ref writer); - AppendHmac(buffer, options.SigningKey); - return Base64UrlEncode(buffer); + + if (options.SigningKey is byte[] signingKey) + { + Span hmacFull = stackalloc byte[HMACSHA256.HashSizeInBytes]; + HMACSHA256.HashData(signingKey, buffer.WrittenSpan, hmacFull); + var dest = buffer.GetSpan(CursorFormat.HmacTruncatedLength); + hmacFull[..CursorFormat.HmacTruncatedLength].CopyTo(dest); + buffer.Advance(CursorFormat.HmacTruncatedLength); + } + + var len = Base64Url.GetEncodedLength(buffer.Written); + return string.Create(len, buffer, static (span, state) => + { + Base64Url.TryEncodeToChars(state.WrittenSpan, span, out _); + }); } finally { @@ -128,22 +141,4 @@ private static void WriteHeader(ref CursorWriter writer, PaginationCursorOptions writer.WriteVarUInt32((uint)valueCount); } - private static void AppendHmac(CursorBuffer buffer, byte[]? signingKey) - { - if (signingKey is null) return; - Span hmacFull = stackalloc byte[HMACSHA256.HashSizeInBytes]; - HMACSHA256.HashData(signingKey, buffer.WrittenSpan, hmacFull); - var dest = buffer.GetSpan(CursorFormat.HmacTruncatedLength); - hmacFull[..CursorFormat.HmacTruncatedLength].CopyTo(dest); - buffer.Advance(CursorFormat.HmacTruncatedLength); - } - - private static string Base64UrlEncode(CursorBuffer buffer) - { - var len = Base64Url.GetEncodedLength(buffer.Written); - return string.Create(len, buffer, static (span, state) => - { - Base64Url.TryEncodeToChars(state.WrittenSpan, span, out _); - }); - } } diff --git a/src/EFPagination/Definition/PaginationQueryDefinition.cs b/src/EFPagination/Definition/PaginationQueryDefinition.cs index abad80f..7e4d6c6 100644 --- a/src/EFPagination/Definition/PaginationQueryDefinition.cs +++ b/src/EFPagination/Definition/PaginationQueryDefinition.cs @@ -15,19 +15,7 @@ internal PaginationQueryDefinition( { Columns = columns; PredicateTemplate = FilterPredicateStrategy.CreateTemplate(columns); - SchemaFingerprint = ComputeFingerprint(columns); - } - - internal PaginationColumn[] Columns { get; } - - internal CachedPredicateTemplate PredicateTemplate { get; } - - internal int ColumnCount => Columns.Length; - - internal uint SchemaFingerprint { get; } - private static uint ComputeFingerprint(PaginationColumn[] columns) - { uint hash = 2166136261; for (var i = 0; i < columns.Length; i++) { @@ -38,6 +26,14 @@ private static uint ComputeFingerprint(PaginationColumn[] columns) hash ^= columns[i].IsDescending ? 1u : 0u; hash *= 16777619; } - return hash; + SchemaFingerprint = hash; } + + internal PaginationColumn[] Columns { get; } + + internal CachedPredicateTemplate PredicateTemplate { get; } + + internal int ColumnCount => Columns.Length; + + internal uint SchemaFingerprint { get; } } diff --git a/src/EFPagination/Internal/KeysetProjectionExecutor.cs b/src/EFPagination/Internal/KeysetProjectionExecutor.cs index 14399f2..b9586a3 100644 --- a/src/EFPagination/Internal/KeysetProjectionExecutor.cs +++ b/src/EFPagination/Internal/KeysetProjectionExecutor.cs @@ -47,9 +47,21 @@ public static async Task> ExecuteAsync( .ResolveAsync(builder.ShouldIncludeCount, resolved.TotalCount, builder.Source, ct) .ConfigureAwait(false); - var (next, previous) = EncodeCursorPair( - builder.Definition, page, page.HasMore, resolved.HasInitialReference, - builder.Direction, sortBy, totalCount); + string? next = null; + string? previous = null; + if (page.Count > 0) + { + var options = new PaginationCursorOptions(sortBy, PaginationCount.AsNullable(totalCount)); + + var hasPrevious = builder.Direction == PaginationDirection.Forward ? resolved.HasInitialReference : page.HasMore; + var hasNext = builder.Direction == PaginationDirection.Forward ? page.HasMore : resolved.HasInitialReference; + + if (hasNext) + next = EncodeFromIndex(builder.Definition, page, page.Count - 1, options); + + if (hasPrevious) + previous = EncodeFromIndex(builder.Definition, page, 0, options); + } return new CursorPage(page.ToItemList(), next, previous, totalCount); } @@ -105,34 +117,6 @@ public static async IAsyncEnumerable> StreamAsync( } } - private static (string? Next, string? Previous) EncodeCursorPair( - PaginationQueryDefinition definition, - ProjectionMaterializedPage page, - bool hasMore, - bool hasInitialReference, - PaginationDirection direction, - string? sortBy, - int totalCount) where T : class - { - if (page.Count == 0) return (null, null); - - var options = new PaginationCursorOptions(sortBy, PaginationCount.AsNullable(totalCount)); - - var hasPrevious = direction == PaginationDirection.Forward ? hasInitialReference : hasMore; - var hasNext = direction == PaginationDirection.Forward ? hasMore : hasInitialReference; - - string? next = null; - string? previous = null; - - if (hasNext) - next = EncodeFromIndex(definition, page, page.Count - 1, options); - - if (hasPrevious) - previous = EncodeFromIndex(definition, page, 0, options); - - return (next, previous); - } - private static string EncodeFromIndex( PaginationQueryDefinition definition, ProjectionMaterializedPage page, diff --git a/src/EFPagination/PaginationExecutor.cs b/src/EFPagination/PaginationExecutor.cs index a0ebd26..cbf43ea 100644 --- a/src/EFPagination/PaginationExecutor.cs +++ b/src/EFPagination/PaginationExecutor.cs @@ -185,7 +185,6 @@ private static async Task> ExecuteCoreAsync( PaginationContext context, CancellationToken ct) where T : class { - ArgumentNullException.ThrowIfNull(query); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(options.PageSize); var (items, hasMore, totalCount) = await MaterializeWithCountAsync( diff --git a/src/EFPagination/PaginationExtensions.cs b/src/EFPagination/PaginationExtensions.cs index 639a4cd..999722d 100644 --- a/src/EFPagination/PaginationExtensions.cs +++ b/src/EFPagination/PaginationExtensions.cs @@ -144,7 +144,10 @@ private static PaginationContext PaginateCore( using var activity = PaginationDiagnostics.StartPaginate(columns, direction, predicateTemplate is not null); - var orderedQuery = ApplyOrdering(source, columns, direction); + var orderedQuery = columns[0].ApplyOrderBy(source, direction); + for (var i = 1; i < columns.Length; i++) + orderedQuery = columns[i].ApplyThenOrderBy(orderedQuery, direction); + if (bindings is null || bindings.Length == 0) return new PaginationContext(orderedQuery, orderedQuery, columns, direction, predicateTemplate); @@ -307,8 +310,29 @@ internal static ColumnBinding[] BuildBindingsFromColumnValues( { var bindings = new ColumnBinding[columns.Length]; - if (referenceValues.Length == columns.Length && TryPopulatePositional(columns, referenceValues, bindings)) - return bindings; + if (referenceValues.Length == columns.Length) + { + var positional = true; + for (var i = 0; i < columns.Length; i++) + { + if (!string.Equals(referenceValues[i].Name, columns[i].GetRequiredPropertyNameForColumnValues(), StringComparison.OrdinalIgnoreCase)) + { + positional = false; + break; + } + } + + if (positional) + { + for (var i = 0; i < columns.Length; i++) + { + var binding = columns[i].CreateBinding(); + columns[i].WriteBindingFromBoxed(referenceValues[i].Value, binding); + bindings[i] = binding; + } + return bindings; + } + } for (var i = 0; i < columns.Length; i++) { @@ -336,27 +360,6 @@ internal static ColumnBinding[] BuildBindingsFromColumnValues( return bindings; } - private static bool TryPopulatePositional( - PaginationColumn[] columns, - ReadOnlySpan values, - ColumnBinding[] bindings) - { - for (var i = 0; i < columns.Length; i++) - { - if (!string.Equals(values[i].Name, columns[i].GetRequiredPropertyNameForColumnValues(), StringComparison.OrdinalIgnoreCase)) - return false; - } - - for (var i = 0; i < columns.Length; i++) - { - var binding = columns[i].CreateBinding(); - columns[i].WriteBindingFromBoxed(values[i].Value, binding); - bindings[i] = binding; - } - - return true; - } - private static Expression> BuildFilterPredicateFromBindings( PaginationColumn[] columns, PaginationDirection direction, @@ -372,15 +375,4 @@ private static Expression> BuildFilterPredicateFromBindings( return FastLambda.Create(body, param); } - private static IOrderedQueryable ApplyOrdering( - IQueryable source, - PaginationColumn[] columns, - PaginationDirection direction) - { - var orderedQuery = columns[0].ApplyOrderBy(source, direction); - for (var i = 1; i < columns.Length; i++) - orderedQuery = columns[i].ApplyThenOrderBy(orderedQuery, direction); - - return orderedQuery; - } } diff --git a/src/EFPagination/PaginationStreaming.cs b/src/EFPagination/PaginationStreaming.cs index 19e6f77..ecb7f5f 100644 --- a/src/EFPagination/PaginationStreaming.cs +++ b/src/EFPagination/PaginationStreaming.cs @@ -30,8 +30,6 @@ public static IAsyncEnumerable> PaginateAllAsync( int pageSize, CancellationToken ct = default) where T : class { - ArgumentNullException.ThrowIfNull(query); - ArgumentNullException.ThrowIfNull(definition); ArgumentOutOfRangeException.ThrowIfNegativeOrZero(pageSize); var context = query.Paginate(definition, PaginationDirection.Forward); diff --git a/src/EFPagination/Sorting/PaginationSortRegistry.cs b/src/EFPagination/Sorting/PaginationSortRegistry.cs index b6b512c..0221486 100644 --- a/src/EFPagination/Sorting/PaginationSortRegistry.cs +++ b/src/EFPagination/Sorting/PaginationSortRegistry.cs @@ -63,23 +63,28 @@ public PaginationQueryDefinition Resolve(ReadOnlySpan sortBy, ReadOnlyS /// Attempts to resolve a pagination definition for the requested sort field and direction. /// /// The requested logical sort field name. - /// The requested direction; desc selects descending. + /// The requested direction: asc, desc, or empty for ascending. /// When this method returns , the matched definition. - /// if a matching field was found or was empty (default used); if the field was not found. + /// if a definition was resolved; otherwise, . public bool TryResolve( ReadOnlySpan sortBy, ReadOnlySpan sortDir, out PaginationQueryDefinition definition) { + var isDesc = sortDir.Equals("desc", StringComparison.OrdinalIgnoreCase); + if (!isDesc && !sortDir.IsEmpty && !sortDir.Equals("asc", StringComparison.OrdinalIgnoreCase)) + { + definition = default!; + return false; + } + if (sortBy.IsEmpty) { definition = _default; return true; } - var isDesc = sortDir.Equals("desc", StringComparison.OrdinalIgnoreCase); var lookup = isDesc ? _descLookup : _ascLookup; - return lookup.TryGetValue(sortBy, out definition!); } } diff --git a/src/EFPagination/packages.lock.json b/src/EFPagination/packages.lock.json index 3426653..175ae71 100644 --- a/src/EFPagination/packages.lock.json +++ b/src/EFPagination/packages.lock.json @@ -4,119 +4,119 @@ "net10.0": { "Microsoft.EntityFrameworkCore": { "type": "Direct", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "VOSGU8en6HZJs8t7UMFN+9vGcRgVOOn6fA44Ngcg2NyvJ3P1KE94iAb0XzaVaGhXGtt+qaM/VtEn0/hzluQJeg==", "dependencies": { - "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", - "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3", - "Microsoft.Extensions.Caching.Memory": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3" + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.11", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.11", + "Microsoft.Extensions.Caching.Memory": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11" } }, "Microsoft.SourceLink.GitHub": { "type": "Direct", - "requested": "[10.0.201, )", - "resolved": "10.0.201", - "contentHash": "qxYAmO4ktzd9L+HMdnqWucxpu7bI9undPyACXOMqPyhaiMtbpbYL/n0ACyWIJlbyEJrXFwxiOaBOSasLtDvsCg==", + "requested": "[10.0.400, )", + "resolved": "10.0.400", + "contentHash": "N1Ut0+EmMb2Tma9spHxAgpCspkln4gst1RWYc1PYPn8LA4KJWDSiggKpE183lfR98TNG2C+XnJ71zSYtuCSsqA==", "dependencies": { - "Microsoft.Build.Tasks.Git": "10.0.201", - "Microsoft.SourceLink.Common": "10.0.201", - "System.IO.Hashing": "10.0.5" + "Microsoft.Build.Tasks.Git": "10.0.400", + "Microsoft.SourceLink.Common": "10.0.400", + "System.IO.Hashing": "10.0.11" } }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", - "resolved": "10.0.201", - "contentHash": "DMYBnrFZvLnBKn14VavEuuIr31CY6YY2i2L9P8DorS/Qp6ifRR8ZPLdJCFLFfjikNq8DykbYyLd/RP6lSqHcWw==", + "resolved": "10.0.400", + "contentHash": "uXJ/eBDqAo9HlyPjvvvynjHb59KXQWG9txpBzyI1drO6MV3Lhndt2yhxuMKvIga4kND2UKc3xCNXlXBhWU8Hlg==", "dependencies": { - "System.IO.Hashing": "10.0.5" + "System.IO.Hashing": "10.0.11" } }, "Microsoft.EntityFrameworkCore.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==" + "resolved": "10.0.11", + "contentHash": "6auJR+9+9VunznKfH7WGrHMrnrmA0F7JZ22EXzwXvVhjfnbu9Xq7NSIWaOf3KJsOanM2qf5ajJ2JR5TlcPZTLA==" }, "Microsoft.EntityFrameworkCore.Analyzers": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==" + "resolved": "10.0.11", + "contentHash": "Bv7X4wSSnzCQED9WYXKJ8fwgyvKwf0xZM1GO8xkf6CF9zl+UBnvjxmcPnokJRy0JKjc1SlHSzzhx1HcL4jitTQ==" }, "Microsoft.Extensions.Caching.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "5dtXBvI8t3z8pF4tB38JYgi/enCL/DwSXxpqShgFz3SHJ7IzqFIMs6Gu5ik8sNZzcO9qQs3xIDpB3vDamkYG+Q==", + "resolved": "10.0.11", + "contentHash": "vUl798SmruTqqlt/xH2gDk3tJlhk6k3HdOXAHirlRfbNKDym4g/kRpUL9S4sl6F6FsOTOMW+ZsDapqlZMOOiEw==", "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.3" + "Microsoft.Extensions.Primitives": "10.0.11" } }, "Microsoft.Extensions.Caching.Memory": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "No4fVh0z30SWqiWFRoA4PNdrEco6OjXvCqRFvlmRgDQqqks2bRDdeavUgWEiAX153ZAwW9loUgbxcvuP4NKQLg==", + "resolved": "10.0.11", + "contentHash": "el1g0mBEbDBGY2bT9mcSfrTWO8QlPdq2nOCnvQugioOFwHV+bVBMeiakoI0dNOdj8d6Hi9K6HY2xzRUWJiDR3w==", "dependencies": { - "Microsoft.Extensions.Caching.Abstractions": "10.0.3", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "Microsoft.Extensions.Options": "10.0.3", - "Microsoft.Extensions.Primitives": "10.0.3" + "Microsoft.Extensions.Caching.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" } }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", + "resolved": "10.0.11", + "contentHash": "PSmotV19c7E3lKed++uYo1kSiXFI+uTl37CBSrhq+CfLC3FCHjG7R91+xPnNehQfHS1b0Tzo/CCLPWH3qaEheg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" + "resolved": "10.0.11", + "contentHash": "/a1aJz4m7ylhEDf25ugQChLQoN5XwoGjWw/BoR/ZWWKsO1v4DdJElS1uyngahz4B/eOzjFk1KNTkarRLE5wsIg==" }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "8D9Er1cGXNjNDIB+VLBNHn386L5ls2FoiG9a6o12gyn+GG3w6jdfUhzT8dtBnKcevE7/fsVA8MS3FBgFfClFtQ==", + "resolved": "10.0.11", + "contentHash": "nUOJwgFkSiLHiVGFpU22pIJtuWYewuSYQ3JVuP/gdK8ASMT807Px+TYQiRWs6uSsOmoyFTaVCwKXTasczV6BpA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "Microsoft.Extensions.Options": "10.0.3" + "Microsoft.Extensions.DependencyInjection": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", + "resolved": "10.0.11", + "contentHash": "Ljd0Uxoq5XpScD2Bg0nM/r3mwx7Ao5Uq24eo2ARxbGvqJ7Zht6rt2cJtwVRH4Cv+1ZVMdXz6TB43KbpmsxRrvQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "hU6WzGTPvPoLA2ng1ILvWQb3g0qORdlHNsxI8IcPLumJb3suimYUl+bbDzdo1V4KFsvVhnMWzysHpKbZaoDQPQ==", + "resolved": "10.0.11", + "contentHash": "eY1GAKcTfD2maP27J84X9IovT3yjHJ2dVDzPmDg6/XqYvt3jMzJhtfQCLjG9pVsZGAd+8DQ2QrjaDcs2+VQLGw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", - "Microsoft.Extensions.Primitives": "10.0.3" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "GEcpTwo7sUoLGGNTqV1FZEuL+tTD9m81NX/mh099dqGNna07/UGZShKQNZRw4hv6nlliSUwYQgSYc7OR99Jufg==" + "resolved": "10.0.11", + "contentHash": "SXcz+kF+4Oo9b1+55zntpJFYfwb1jw66ioxptyNOOTDc8g2FHnBFWjZpsWfCvZIhzr0x+4e2trVTs4OKwQfBtw==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", - "resolved": "10.0.201", - "contentHash": "QbBYhkjgL6rCnBfDbzsAJLlsad13TlBHqYCFDIw56OO2g6ix+9RsmY8uxiQGdWwFKbZXaXyAA6jDCzFYVGCZDw==" + "resolved": "10.0.400", + "contentHash": "Knli3Y2k/rdBqUlQ8TP+Sbd0J0jV/0kvgLsxieowoVayioZ0JGfqb9go+/dQJqtU53i6IpZzRxAPxbBT5UJBNg==" }, "System.IO.Hashing": { "type": "Transitive", - "resolved": "10.0.5", - "contentHash": "8IBJWcCT9+e4Bmevm4T7+fQEiAh133KGiz4oiVTgJckd3Q76OFdR1falgn9lpz7+C4HJvogCDJeAa2QmvbeVtg==" + "resolved": "10.0.11", + "contentHash": "OzKDcIRkeNJeC8qAsbn8yJXnfTLP1dtkWILe+T56Gf/z+IkAASi7sMqLqJQat08j5z/mRN5xVtoAwbkMNMoBUQ==" } } } diff --git a/test/EFPagination.Tests/packages.lock.json b/test/EFPagination.Tests/packages.lock.json index 4ce8473..8bf9a72 100644 --- a/test/EFPagination.Tests/packages.lock.json +++ b/test/EFPagination.Tests/packages.lock.json @@ -4,80 +4,80 @@ "net10.0": { "coverlet.collector": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "EMkj/2F6n6IVPrvGYkqzGJs6phuGGkq6N+E7KW9rNyzNxXbwQ1KfMqWyXNf9nCNEQOA6IjFwmOLvkriwKE7Orw==" + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "27jXSV/0DbVqF5jDrAxuQFZ9oaz6gmG03p8ttxAFk+X0M4woFYj7MoWDLCna5EGLb0CE6OE7X6ZH3Wt5smTtaA==" }, "FluentAssertions": { "type": "Direct", - "requested": "[8.8.0, )", - "resolved": "8.8.0", - "contentHash": "m0kwcqBwvVel03FuMa7Ozo/oTaxYbjeNlcOhQFkyQpwX/8wks6RNl/Jnn58DCZVs6c2oG1RsCZw7HfKSaxLm3w==" + "requested": "[8.10.0, )", + "resolved": "8.10.0", + "contentHash": "yGvv+xp0gHFgGwhQcGoDdKIrhLhPi3zYYWWLLHPMctr+G6PWD7QH8L4wqoa5WRZquPbfSV/cP1MraI7ppAHrWw==" }, "GitHubActionsTestLogger": { "type": "Direct", - "requested": "[3.0.1, )", - "resolved": "3.0.1", - "contentHash": "HCfw7V1yuKxnm89vVKrb8b6aug96PUgxN9Z0YthEFCR8JOfTJF4XcU1i6RX2eEB08p3zriuFIG7BGXns4PyE8Q==" + "requested": "[3.0.5, )", + "resolved": "3.0.5", + "contentHash": "LM5IKPSylMYp42YC9bnL+JNUnQUS2y9TgoFLjnqeXLpDvqeyiTVc9fSh6m1KzhgLCKLGtUkpZxk9xI9/PHFaiw==" }, "Microsoft.CodeAnalysis.CSharp": { "type": "Direct", - "requested": "[5.3.0, )", - "resolved": "5.3.0", - "contentHash": "SQFNGQF4f7UfDXKxMzzGNMr3fjrPDIjLfmRvvVgDCw+dyvEHDaRfHuKA5q0Pr0/JW0Gcw89TxrxrS/MjwBvluQ==", + "requested": "[5.6.0, )", + "resolved": "5.6.0", + "contentHash": "r1DrKQ/L0xTw03wJrLr36AMQNslyaeEKBFyFmQcOKa8HX3YvmhY//JEUafb6IR/m0gmaUVCfBTWitKJRNb7YAA==", "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1", - "Microsoft.CodeAnalysis.Common": "[5.3.0]" + "Microsoft.CodeAnalysis.Analyzers": "5.3.0", + "Microsoft.CodeAnalysis.Common": "[5.6.0]" } }, "Microsoft.EntityFrameworkCore": { "type": "Direct", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "CDEImwD4A7BseABJMCpLZnhfFjmPY/bHwhhS70elc6gLI/bYUEOhxWt7PmaNGYGhIEzOnStlCy5QcVb+8dod5Q==", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "VOSGU8en6HZJs8t7UMFN+9vGcRgVOOn6fA44Ngcg2NyvJ3P1KE94iAb0XzaVaGhXGtt+qaM/VtEn0/hzluQJeg==", "dependencies": { - "Microsoft.EntityFrameworkCore.Abstractions": "10.0.3", - "Microsoft.EntityFrameworkCore.Analyzers": "10.0.3", - "Microsoft.Extensions.Caching.Memory": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3" + "Microsoft.EntityFrameworkCore.Abstractions": "10.0.11", + "Microsoft.EntityFrameworkCore.Analyzers": "10.0.11", + "Microsoft.Extensions.Caching.Memory": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11" } }, "Microsoft.EntityFrameworkCore.Sqlite": { "type": "Direct", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "pXP9l00ZzsZuS6ajUJFLHi5vt3vVxHaxQXCwRs73tyqNbjPX/4ac114wyfAepMSmUKq0roFFoTZ7h+1K1+iQjQ==", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "jc7iVrhQyInR3loraMESfEFaFOtQOB1mRKHjX6QYC9o7YDbfMNbAPnIwlpffnFwhXd6/27FKaaV+sWSoLd4F1g==", "dependencies": { - "Microsoft.EntityFrameworkCore.Sqlite.Core": "10.0.3", - "Microsoft.Extensions.Caching.Memory": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.DependencyModel": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3", - "SQLitePCLRaw.bundle_e_sqlite3": "2.1.11", - "SQLitePCLRaw.core": "2.1.11" + "Microsoft.EntityFrameworkCore.Sqlite.Core": "10.0.11", + "Microsoft.Extensions.Caching.Memory": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyModel": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.12", + "SQLitePCLRaw.core": "2.1.12" } }, "Microsoft.EntityFrameworkCore.SqlServer": { "type": "Direct", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "q0fUFQ4PG4uhKwP+D5dsvlDQbwNpEfWsag3HaoGzkkKj+OQ90ya9W0HuFtwigwEEqc/H1SzDZYUAv7Ya1hh5ow==", + "requested": "[10.0.11, )", + "resolved": "10.0.11", + "contentHash": "LClSs5cNN6za2Uk7IeH7RAP4Qe5EMXxFsD9i53ncPx21TVg04IEjoFyxvvsSNkq2/Ux9HyJsJ2qEO8Tl7Gcrrg==", "dependencies": { - "Microsoft.Data.SqlClient": "6.1.1", - "Microsoft.EntityFrameworkCore.Relational": "10.0.3", - "Microsoft.Extensions.Caching.Memory": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3" + "Microsoft.Data.SqlClient": "6.1.6", + "Microsoft.EntityFrameworkCore.Relational": "10.0.11", + "Microsoft.Extensions.Caching.Memory": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11" } }, "Microsoft.NET.Test.Sdk": { "type": "Direct", - "requested": "[18.0.1, )", - "resolved": "18.0.1", - "contentHash": "WNpu6vI2rA0pXY4r7NKxCN16XRWl5uHu6qjuyVLoDo6oYEggIQefrMjkRuibQHm/NslIUNCcKftvoWAN80MSAg==", + "requested": "[18.9.0, )", + "resolved": "18.9.0", + "contentHash": "xIzVXa/VpKXqDWzyC/5Hw8JbFY5u9k3Jc53CpcjBiOXvkQGio484LD9FNwOiGUSMDcYL3Rcw9sNgGi5WrswlPQ==", "dependencies": { - "Microsoft.CodeCoverage": "18.0.1", - "Microsoft.TestPlatform.TestHost": "18.0.1" + "Microsoft.CodeCoverage": "18.9.0", + "Microsoft.TestPlatform.TestHost": "18.9.0" } }, "Moq": { @@ -102,28 +102,28 @@ }, "xunit.runner.visualstudio": { "type": "Direct", - "requested": "[3.1.5, )", - "resolved": "3.1.5", - "contentHash": "tKi7dSTwP4m5m9eXPM2Ime4Kn7xNf4x4zT9sdLO/G4hZVnQCRiMTWoSZqI/pYTVeI27oPPqHBKYI/DjJ9GsYgA==" + "requested": "[4.0.0, )", + "resolved": "4.0.0", + "contentHash": "kzLFyBYUnoidpGOXvpNOfIo8C92gVgVR6X8ncHwhcrDYugiOut5rrhDkr0L3cWHd7J2pKXf6LgaeXoBBDlx1ag==" }, "Azure.Core": { "type": "Transitive", - "resolved": "1.47.1", - "contentHash": "oPcncSsDHuxB8SC522z47xbp2+ttkcKv2YZ90KXhRKN0YQd2+7l1UURT9EBzUNEXtkLZUOAB5xbByMTrYRh3yA==", + "resolved": "1.50.0", + "contentHash": "GBNKZEhdIbTXxedvD3R7I/yDVFX9jJJEz02kCziFSJxspSQ5RMHc3GktulJ1s7+ffXaXD7kMgrtdQTaggyInLw==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "8.0.0", - "System.ClientModel": "1.5.1", + "System.ClientModel": "1.8.0", "System.Memory.Data": "8.0.1" } }, "Azure.Identity": { "type": "Transitive", - "resolved": "1.14.2", - "contentHash": "YhNMwOTwT+I2wIcJKSdP0ADyB2aK+JaYWZxO8LSRDm5w77LFr0ykR9xmt2ZV5T1gaI7xU6iNFIh/yW1dAlpddQ==", + "resolved": "1.17.1", + "contentHash": "MSZkBrctcpiGxs9Cvr2VKKoN6qFLZlP3I6xuCWJ9iTgitI5Rgxtk5gfOSpXPZE3+CJmZ/mnqpQyGyjawFn5Vvg==", "dependencies": { - "Azure.Core": "1.46.1", - "Microsoft.Identity.Client": "4.73.1", - "Microsoft.Identity.Client.Extensions.Msal": "4.73.1" + "Azure.Core": "1.50.0", + "Microsoft.Identity.Client": "4.78.0", + "Microsoft.Identity.Client.Extensions.Msal": "4.78.0" } }, "Castle.Core": { @@ -139,44 +139,41 @@ "resolved": "8.0.0", "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==" }, - "Microsoft.Bcl.Cryptography": { - "type": "Transitive", - "resolved": "9.0.4", - "contentHash": "YgZYAWzyNuPVtPq6WNm0bqOWNjYaWgl5mBWTGZyNoXitYBUYSp6iUB9AwK0V1mo793qRJUXz2t6UZrWITZSvuQ==" - }, "Microsoft.CodeAnalysis.Analyzers": { "type": "Transitive", - "resolved": "5.3.0-2.25625.1", - "contentHash": "4Yhh2fnu3G+J0J1lDc8WZVgMjgbynSeTfkl5IFJMFrmiIO0sc7Tjx+f3sFVV8Sd35PrIUWfof0RWc3lAMl7Azg==" + "resolved": "5.3.0", + "contentHash": "KuLhbZwB0L8JikL86AE5VWEp3RLNjIcp+j8yz9EJ/UBgRz4+qDEjHg/tluRFbpYpD/e37BqaaNFbQ0vqawBwWQ==" }, "Microsoft.CodeAnalysis.Common": { "type": "Transitive", - "resolved": "5.3.0", - "contentHash": "uC0qk3jzTQY7i90ehfnCqaOZpBUGJyPMiHJ3c0jOb8yaPBjWzIhVdNxPbeVzI74DB0C+YgBKPLqUkgFZzua5Mg==", + "resolved": "5.6.0", + "contentHash": "eWYNB5e92PSdkQ0xcmy2aLtrvBXNydnVi0Hj/VjaAely6XBqA3By+ClGAJaj4d16pzQmrXPLLK9RDVuS1Ec9xQ==", "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "5.3.0-2.25625.1" + "Microsoft.CodeAnalysis.Analyzers": "5.3.0" } }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "O+utSr97NAJowIQT/OVp3Lh9QgW/wALVTP4RG1m2AfFP4IyJmJz0ZBmFJUsRQiAPgq6IRC0t8AAzsiPIsaUDEA==" + "resolved": "18.9.0", + "contentHash": "MtegCIKGuG0r/LCdIjoR1HgzCGIUBhR3aNdbtQpts5vMXQuqBDZ2Jsd2uFKoHFM2XrQV6ZrDf3F5oLi3SLTp8w==" }, "Microsoft.Data.SqlClient": { "type": "Transitive", - "resolved": "6.1.1", - "contentHash": "syGQmIUPAYYHAHyTD8FCkTNThpQWvoA7crnIQRMfp8dyB5A2cWU3fQexlRTFkVmV7S0TjVmthi0LJEFVjHo8AQ==", + "resolved": "6.1.6", + "contentHash": "6abRPXrjjEWxcNkTomBIzBdGolYnwD0ykP+6YAynaEEfKlGe94ZSQd58pggucusp28xUA7wLo9XcvzU92ZuF6Q==", "dependencies": { - "Azure.Core": "1.47.1", - "Azure.Identity": "1.14.2", - "Microsoft.Bcl.Cryptography": "9.0.4", + "Azure.Core": "1.50.0", + "Azure.Identity": "1.17.1", "Microsoft.Data.SqlClient.SNI.runtime": "6.0.2", - "Microsoft.Extensions.Caching.Memory": "9.0.4", + "Microsoft.Extensions.Caching.Memory": "9.0.11", + "Microsoft.Identity.Client": "4.84.2", + "Microsoft.Identity.Client.Broker": "4.84.2", "Microsoft.IdentityModel.JsonWebTokens": "7.7.1", "Microsoft.IdentityModel.Protocols.OpenIdConnect": "7.7.1", "Microsoft.SqlServer.Server": "1.0.0", - "System.Configuration.ConfigurationManager": "9.0.4", - "System.Security.Cryptography.Pkcs": "9.0.4" + "System.Configuration.ConfigurationManager": "9.0.11", + "System.IdentityModel.Tokens.Jwt": "7.7.1", + "System.Security.Cryptography.Pkcs": "9.0.11" } }, "Microsoft.Data.SqlClient.SNI.runtime": { @@ -186,146 +183,160 @@ }, "Microsoft.Data.Sqlite.Core": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "onD94qHlvteS2S9eg1T7Huehm3x92no4nU1AyDWjSmT6jDBhY8QF0UapwNhFA+1dArzxM10fV7s6uI1EIK2+dw==", + "resolved": "10.0.11", + "contentHash": "hubA20AGenQ4Sx0ElWaPpB8DISjXpdx463+1zOGRslsT0e/t/06ITv+pHsop8CcJ0d8PZLfgnT7juCDVD79Dkw==", "dependencies": { - "SQLitePCLRaw.core": "2.1.11" + "SQLitePCLRaw.core": "2.1.12" } }, "Microsoft.EntityFrameworkCore.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "c7Uoz381xnMHNBRB8eHRhGgzUtXbgddlbODhwZRrTSzZWDharp3RkJsFwhxyESbeXhCqmML7VdvjMQ7uu+HreA==" + "resolved": "10.0.11", + "contentHash": "6auJR+9+9VunznKfH7WGrHMrnrmA0F7JZ22EXzwXvVhjfnbu9Xq7NSIWaOf3KJsOanM2qf5ajJ2JR5TlcPZTLA==" }, "Microsoft.EntityFrameworkCore.Analyzers": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "lVABgJTyTUNE7Bi0bSu4dWHiCHIXEGzTh/kLh0N07IgU/tIKwTeBPp8tgV4x2iKj4h7iPLo8oXzyHmLDGtAE1g==" + "resolved": "10.0.11", + "contentHash": "Bv7X4wSSnzCQED9WYXKJ8fwgyvKwf0xZM1GO8xkf6CF9zl+UBnvjxmcPnokJRy0JKjc1SlHSzzhx1HcL4jitTQ==" }, "Microsoft.EntityFrameworkCore.Relational": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "Pmh60OK9neVr/M0FJwm9hlzm2bD4Kd65SID8E6SP5c90tExNgXwORrlEWl0oGU/ig9ifpNN4PSpIrnHNozlT5w==", + "resolved": "10.0.11", + "contentHash": "grznnTJgEYxaWpdKAsTzg6j+89jHgCXWYp+QGtlX5O92+w/VuhWM6JLPYb+uw8M9VhGUvOTsO76dYOy9vNPd5Q==", "dependencies": { - "Microsoft.EntityFrameworkCore": "10.0.3", - "Microsoft.Extensions.Caching.Memory": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3" + "Microsoft.EntityFrameworkCore": "10.0.11", + "Microsoft.Extensions.Caching.Memory": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11" } }, "Microsoft.EntityFrameworkCore.Sqlite.Core": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "bUkOkSwPqvhNlsMMg5dA/PR9S4cVehiHEIZklakRH6JZCFsEBNvUz8kBRGK8Hi6mcaPKUAmVZQkN3moZsBYZLA==", + "resolved": "10.0.11", + "contentHash": "ywTQKt32xnVhCzjEQAqFufpEyXkOUfvW/EC/s4xnS8Xaor2xXE+TMUyzhgACqXtZEU5IR95y94RDzHto55Fx7w==", "dependencies": { - "Microsoft.Data.Sqlite.Core": "10.0.3", - "Microsoft.EntityFrameworkCore.Relational": "10.0.3", - "Microsoft.Extensions.Caching.Memory": "10.0.3", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.3", - "Microsoft.Extensions.DependencyModel": "10.0.3", - "Microsoft.Extensions.Logging": "10.0.3", - "SQLitePCLRaw.core": "2.1.11" + "Microsoft.Data.Sqlite.Core": "10.0.11", + "Microsoft.EntityFrameworkCore.Relational": "10.0.11", + "Microsoft.Extensions.Caching.Memory": "10.0.11", + "Microsoft.Extensions.Configuration.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyModel": "10.0.11", + "Microsoft.Extensions.Logging": "10.0.11", + "SQLitePCLRaw.core": "2.1.12" } }, "Microsoft.Extensions.Caching.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "5dtXBvI8t3z8pF4tB38JYgi/enCL/DwSXxpqShgFz3SHJ7IzqFIMs6Gu5ik8sNZzcO9qQs3xIDpB3vDamkYG+Q==", + "resolved": "10.0.11", + "contentHash": "vUl798SmruTqqlt/xH2gDk3tJlhk6k3HdOXAHirlRfbNKDym4g/kRpUL9S4sl6F6FsOTOMW+ZsDapqlZMOOiEw==", "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.3" + "Microsoft.Extensions.Primitives": "10.0.11" } }, "Microsoft.Extensions.Caching.Memory": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "No4fVh0z30SWqiWFRoA4PNdrEco6OjXvCqRFvlmRgDQqqks2bRDdeavUgWEiAX153ZAwW9loUgbxcvuP4NKQLg==", + "resolved": "10.0.11", + "contentHash": "el1g0mBEbDBGY2bT9mcSfrTWO8QlPdq2nOCnvQugioOFwHV+bVBMeiakoI0dNOdj8d6Hi9K6HY2xzRUWJiDR3w==", "dependencies": { - "Microsoft.Extensions.Caching.Abstractions": "10.0.3", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "Microsoft.Extensions.Options": "10.0.3", - "Microsoft.Extensions.Primitives": "10.0.3" + "Microsoft.Extensions.Caching.Abstractions": "10.0.11", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" } }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "xVDHL0+SIgemfh95fTO9cGLe17TWv/ZP0n7m01z8X6pzt2DmQpucioWR/mYZA1sRlkWnkXzfl0JweLNWmE9WMg==", + "resolved": "10.0.11", + "contentHash": "fVi053xdpda9Em7vSkmgVxO/PtgC2m78ekReKWsgcyskqY0U82Bz/MONwxpGzI0hElYKJfw+fupqMVeKW3fSaA==", "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.3" + "Microsoft.Extensions.Primitives": "10.0.11" } }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "2DLOmC0EkB2smVK8lPP1PIKEgL1arE3CMp9XSIQB/Y7ev5nnnyuM/PizKJ6QfLD08QCYoopSC9SFdbYglDomYg==", + "resolved": "10.0.11", + "contentHash": "PSmotV19c7E3lKed++uYo1kSiXFI+uTl37CBSrhq+CfLC3FCHjG7R91+xPnNehQfHS1b0Tzo/CCLPWH3qaEheg==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "bwGMrRcAMWx2s/RDgja97p27rxSz2pEQW0+rX5cWAUWVETVJ/eyxGfjAl8vuG5a+lckWmPIE+vcuaZNVB5YDdw==" + "resolved": "10.0.11", + "contentHash": "/a1aJz4m7ylhEDf25ugQChLQoN5XwoGjWw/BoR/ZWWKsO1v4DdJElS1uyngahz4B/eOzjFk1KNTkarRLE5wsIg==" }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "31kRjr1fgdJO1UZ/AsjL2noqwht+juHMQ8c/oh8CEsDhlM2+0zwVZVsZjxSfOFiPtn5+6kRGuvSbLAufAPT0kA==" + "resolved": "10.0.11", + "contentHash": "PJPtFYsZ+r+uz9qqXWUTEyKeJ1EiBGIJtqavkg9ZXijjGSFAk4Fgi5sqIxj+uAyLZwEKgexDUQXhWhvU6l3+og==" }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "8D9Er1cGXNjNDIB+VLBNHn386L5ls2FoiG9a6o12gyn+GG3w6jdfUhzT8dtBnKcevE7/fsVA8MS3FBgFfClFtQ==", + "resolved": "10.0.11", + "contentHash": "nUOJwgFkSiLHiVGFpU22pIJtuWYewuSYQ3JVuP/gdK8ASMT807Px+TYQiRWs6uSsOmoyFTaVCwKXTasczV6BpA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.3", - "Microsoft.Extensions.Options": "10.0.3" + "Microsoft.Extensions.DependencyInjection": "10.0.11", + "Microsoft.Extensions.Logging.Abstractions": "10.0.11", + "Microsoft.Extensions.Options": "10.0.11" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "lxl0WLk7ROgBFAsjcOYjQ8/DVK+VMszxGBzUhgtQmAsTNldLL5pk9NG/cWTsXHq0lUhUEAtZkEE7jOGOA8bGKQ==", + "resolved": "10.0.11", + "contentHash": "Ljd0Uxoq5XpScD2Bg0nM/r3mwx7Ao5Uq24eo2ARxbGvqJ7Zht6rt2cJtwVRH4Cv+1ZVMdXz6TB43KbpmsxRrvQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "hU6WzGTPvPoLA2ng1ILvWQb3g0qORdlHNsxI8IcPLumJb3suimYUl+bbDzdo1V4KFsvVhnMWzysHpKbZaoDQPQ==", + "resolved": "10.0.11", + "contentHash": "eY1GAKcTfD2maP27J84X9IovT3yjHJ2dVDzPmDg6/XqYvt3jMzJhtfQCLjG9pVsZGAd+8DQ2QrjaDcs2+VQLGw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.3", - "Microsoft.Extensions.Primitives": "10.0.3" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.11", + "Microsoft.Extensions.Primitives": "10.0.11" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "10.0.3", - "contentHash": "GEcpTwo7sUoLGGNTqV1FZEuL+tTD9m81NX/mh099dqGNna07/UGZShKQNZRw4hv6nlliSUwYQgSYc7OR99Jufg==" + "resolved": "10.0.11", + "contentHash": "SXcz+kF+4Oo9b1+55zntpJFYfwb1jw66ioxptyNOOTDc8g2FHnBFWjZpsWfCvZIhzr0x+4e2trVTs4OKwQfBtw==" }, "Microsoft.Identity.Client": { "type": "Transitive", - "resolved": "4.73.1", - "contentHash": "NnDLS8QwYqO5ZZecL2oioi1LUqjh5Ewk4bMLzbgiXJbQmZhDLtKwLxL3DpGMlQAJ2G4KgEnvGPKa+OOgffeJbw==", + "resolved": "4.84.2", + "contentHash": "Va9FjmABSgj/lcUfHH0pBNQkmS7SNyepz2ERV7Yynp6QgEYpFdSqR6Vzy1WWipN8GS5pBHuXoZbqaDWuARCrHQ==", "dependencies": { - "Microsoft.IdentityModel.Abstractions": "6.35.0" + "Microsoft.IdentityModel.Abstractions": "8.14.0" + } + }, + "Microsoft.Identity.Client.Broker": { + "type": "Transitive", + "resolved": "4.84.2", + "contentHash": "928ySLeGz1xtvydwq6h9tv7TbxrtA4ZzavLKUqRJh0PW6BGcEJwpI8W8vf2PlQdYemGm/oyGjdzYzdY/I8r/4A==", + "dependencies": { + "Microsoft.Identity.Client": "4.84.2", + "Microsoft.Identity.Client.NativeInterop": "0.20.6" } }, "Microsoft.Identity.Client.Extensions.Msal": { "type": "Transitive", - "resolved": "4.73.1", - "contentHash": "xDztAiV2F0wI0W8FLKv5cbaBefyLD6JVaAsvgSN7bjWNCzGYzHbcOEIP5s4TJXUpQzMfUyBsFl1mC6Zmgpz0PQ==", + "resolved": "4.78.0", + "contentHash": "DYU9o+DrDQuyZxeq91GBA9eNqBvA3ZMkLzQpF7L9dTk6FcIBM1y1IHXWqiKXTvptPF7CZE59upbyUoa+FJ5eiA==", "dependencies": { - "Microsoft.Identity.Client": "4.73.1", + "Microsoft.Identity.Client": "4.78.0", "System.Security.Cryptography.ProtectedData": "4.5.0" } }, + "Microsoft.Identity.Client.NativeInterop": { + "type": "Transitive", + "resolved": "0.20.6", + "contentHash": "noyfdMfVxyWpM6xUDmlW6gUvZ5ci24z7Qoy15JvynTQfZV5vTD2NEdsnT5Gy4ngViwgdIp5iRjRy10jAKXFAgw==" + }, "Microsoft.IdentityModel.Abstractions": { "type": "Transitive", - "resolved": "7.7.1", - "contentHash": "S7sHg6gLg7oFqNGLwN1qSbJDI+QcRRj8SuJ1jHyCmKSipnF6ZQL+tFV2NzVfGj/xmGT9TykQdQiBN+p5Idl4TA==" + "resolved": "8.14.0", + "contentHash": "iwbCpSjD3ehfTwBhtSNEtKPK0ICun6ov7Ibx6ISNA9bfwIyzI2Siwyi9eJFCJBwxowK9xcA1mj+jBWiigeqgcQ==" }, "Microsoft.IdentityModel.JsonWebTokens": { "type": "Transitive", @@ -375,54 +386,48 @@ }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "qT/mwMcLF9BieRkzOBPL2qCopl8hQu6A1P7JWAoj/FMu5i9vds/7cjbJ/LLtaiwWevWLAeD5v5wjQJ/l6jvhWQ==" + "resolved": "18.9.0", + "contentHash": "Fz/qXC52VXXopzHj7v2reCKcCqSxkTDCkEDfkNAH0vni/7qK/KLMiEYtRmnUanxO2F2g2zZZ60qCpxF0AF7URA==" }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "18.0.1", - "contentHash": "uDJKAEjFTaa2wHdWlfo6ektyoh+WD4/Eesrwb4FpBFKsLGehhACVnwwTI4qD3FrIlIEPlxdXg3SyrYRIcO+RRQ==", + "resolved": "18.9.0", + "contentHash": "Oq+Pma/J86aZL72t71bcESvDszDsTjUfl6PIsuDdPoKTHZfNMhKamCfLD5fKx51W/u0KozXtJo2/3fnt0sgPxg==", "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "18.0.1", - "Newtonsoft.Json": "13.0.3" + "Microsoft.TestPlatform.ObjectModel": "18.9.0" } }, - "Newtonsoft.Json": { - "type": "Transitive", - "resolved": "13.0.3", - "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" - }, "SQLitePCLRaw.bundle_e_sqlite3": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "DC4nA7yWnf4UZdgJDF+9Mus4/cb0Y3Sfgi3gDnAoKNAIBwzkskNAbNbyu+u4atT0ruVlZNJfwZmwiEwE5oz9LQ==", + "resolved": "2.1.12", + "contentHash": "mAgscpQMLw5/nfA1Q5oJVAT29yROUo1ifZGbbTpx/lwZpSxMUGoYbKfmvdm8oXER+RzxqBmmQzeBEVKfeHv2nw==", "dependencies": { - "SQLitePCLRaw.lib.e_sqlite3": "2.1.11", - "SQLitePCLRaw.provider.e_sqlite3": "2.1.11" + "SQLitePCLRaw.lib.e_sqlite3": "2.1.12", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.12" } }, "SQLitePCLRaw.core": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "PK0GLFkfhZzLQeR3PJf71FmhtHox+U3vcY6ZtswoMjrefkB9k6ErNJEnwXqc5KgXDSjige2XXrezqS39gkpQKA==" + "resolved": "2.1.12", + "contentHash": "ETpNw9DY3ckWLgRRAeCHj+GKOuPi61aeczkXhgHexUvqoZBAYg8RYESE2J7O1M7+o6QbdSEZwrw9bfqztUVWXg==" }, "SQLitePCLRaw.lib.e_sqlite3": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "Ev2ytaXiOlWZ4b3R67GZBsemTINslLD1DCJr2xiacpn4tbapu0Q4dHEzSvZSMnVWeE5nlObU3VZN2p81q3XOYQ==" + "resolved": "2.1.12", + "contentHash": "fWi8Dbknuhgg72fWinIdjXVaqO1hHL4YBBwVLnr7e1c9TAZwJ0QE38j9syW1hwx6HaqEVTwI+O07WPdZn8Rp0w==" }, "SQLitePCLRaw.provider.e_sqlite3": { "type": "Transitive", - "resolved": "2.1.11", - "contentHash": "Y/0ZkR+r0Cg3DQFuCl1RBnv/tmxpIZRU3HUvelPw6MVaKHwYYR8YNvgs0vuNuXCMvlyJ+Fh88U1D4tah1tt6qw==", + "resolved": "2.1.12", + "contentHash": "W3oH4XIfCzFrgUSDKHhN6N+dgzA5YHOR2VxX8GB6Qy7CyrJJgxPEG8NirgYWlPQC5P2jz2knSsexWu4tDUL33g==", "dependencies": { - "SQLitePCLRaw.core": "2.1.11" + "SQLitePCLRaw.core": "2.1.12" } }, "System.ClientModel": { "type": "Transitive", - "resolved": "1.5.1", - "contentHash": "k2jKSO0X45IqhVOT9iQB4xralNN9foRQsRvXBTyRpAVxyzCJlG895T9qYrQWbcJ6OQXxOouJQ37x5nZH5XKK+A==", + "resolved": "1.8.0", + "contentHash": "AqRzhn0v29GGGLj/Z6gKq4lGNtvPHT4nHdG5PDJh9IfVjv/nYUVmX11hwwws1vDFeIAzrvmn0dPu8IjLtu6fAw==", "dependencies": { "Microsoft.Extensions.Logging.Abstractions": "8.0.3", "System.Memory.Data": "8.0.1" @@ -430,17 +435,17 @@ }, "System.Configuration.ConfigurationManager": { "type": "Transitive", - "resolved": "9.0.4", - "contentHash": "dvjqKp+2LpGid6phzrdrS/2mmEPxFl3jE1+L7614q4ZChKbLJCpHXg6sBILlCCED1t//EE+un/UdAetzIMpqnw==", + "resolved": "9.0.11", + "contentHash": "vPinosRgEk1CGjohbv0pDuz6gPprkWo5xvBTjOJjks5JOgOEWvPndq5NngGxiTcBcy1+k9GwzZ1c3Bp2fU5ezw==", "dependencies": { - "System.Diagnostics.EventLog": "9.0.4", - "System.Security.Cryptography.ProtectedData": "9.0.4" + "System.Diagnostics.EventLog": "9.0.11", + "System.Security.Cryptography.ProtectedData": "9.0.11" } }, "System.Diagnostics.EventLog": { "type": "Transitive", - "resolved": "9.0.4", - "contentHash": "getRQEXD8idlpb1KW56XuxImMy0FKp2WJPDf3Qr0kI/QKxxJSftqfDFVo0DZ3HCJRLU73qHSruv5q2l5O47jQQ==" + "resolved": "9.0.11", + "contentHash": "2Us/NchH6SM69NYWzf8NRyeftdv3ILso8LMiMdAjT7ECTiZKzddiXWhAyQj6ZzbddoAOHS9GdlPbAAkwCPID3Q==" }, "System.IdentityModel.Tokens.Jwt": { "type": "Transitive", @@ -458,13 +463,13 @@ }, "System.Security.Cryptography.Pkcs": { "type": "Transitive", - "resolved": "9.0.4", - "contentHash": "cUFTcMlz/Qw9s90b2wnWSCvHdjv51Bau9FQqhsr4TlwSe1OX+7SoXUqphis5G74MLOvMOCghxPPlEqOdCrVVGA==" + "resolved": "9.0.11", + "contentHash": "YS2YqtN6fFjlTDIQI+ucjbbrEvwMn796r+VLTQRr/5Oy6g+i+m7nIou83KnJCnAcKna2I+5eVJRks6SoHSpetQ==" }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", - "resolved": "9.0.4", - "contentHash": "o94k2RKuAce3GeDMlUvIXlhVa1kWpJw95E6C9LwW0KlG0nj5+SgCiIxJ2Eroqb9sLtG1mEMbFttZIBZ13EJPvQ==" + "resolved": "9.0.11", + "contentHash": "s8yUYuYYu+PAwvBdhLG1KyrGrk9gkYeuPxfAsXsTqqWyepwSyEw8hAaflW4nO98NG52YpYI1am2+9o+79h2RtQ==" }, "xunit.abstractions": { "type": "Transitive", @@ -509,7 +514,7 @@ "efpagination": { "type": "Project", "dependencies": { - "Microsoft.EntityFrameworkCore": "[10.0.3, )" + "Microsoft.EntityFrameworkCore": "[10.0.11, )" } }, "efpagination.analyzers": {