Advanced REST Concepts series: migrate the samples to CodeMazeGuides on .NET 10 - #2148
Merged
vladimir-pecanac-main merged 3 commits intoSep 2, 2026
Conversation
…on .NET 10 Brings the six-part advanced REST concepts series (paging, filtering, searching, sorting, data shaping, HATEOAS) into aspnetcore-webapi/AdvancedRestConceptsSeries, one folder per article plus the starting project, following the dotnet-testing/AspNetCoreTestingSeries layout. The source branches were last touched in 2020 and target netcoreapp3.0 with EF Core 2.2, Pomelo 2.2 and a Startup.cs, so this is a rewrite rather than a copy: - net10.0 across all projects, nullable enabled, file-scoped namespaces. - Minimal hosting in a single Program.cs; Startup.cs is gone. - EF Core 10 on Microsoft.EntityFrameworkCore.SqlServer in place of Pomelo/MySQL, with a SQL Server init script and LocalDB defaults. - System.Text.Json throughout; the Newtonsoft package reference is gone, and the HATEOAS custom media types register against SystemTextJsonOutputFormatter. - System.Linq.Dynamic.Core 1.7.4, NLog.Extensions.Logging 6.2.0. The folders are built to accumulate, so each one carries every fix the earlier articles make rather than reverting them: - One paging entry point in every folder, PagedList<T>.ToPagedListAsync over an IQueryable<T>, so paging happens in the database everywhere. DataShaping and Hateoas page first and shape the returned page; the X-Pagination metadata comes from the paged query. - QueryStringParameters is abstract everywhere and clamps both page number and page size; Response.Headers uses the indexer; CORS exposes X-Pagination. - The year filter is nullable int with [Range] and a composed half-open date range, so no index-defeating DATEPART wrapper and no clock read in a property initializer. - SearchByName(ref ...) is replaced by a Search extension method with no ToLowerInvariant (which EF Core 10 cannot translate) and no !Any() round trip. - SortHelper drops !entities.Any(), restores the empty-query guard and compares " desc" case-insensitively; the repository keeps a default ordering, so an explicitly empty orderBy still pages an ordered query. - DataShaper holds its PropertyInfo array private readonly and is registered as a singleton; property matching is ordinal. Each article folder ships an xUnit project. The in-memory tests always run; the repository tests that need a real SQL Server start one through Testcontainers and are skipped unless CODEMAZE_SQLSERVER_TESTS=1 is set.
…nstructor so XML responses serialize
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Migrates the six-part advanced REST concepts series from
CodeMazeBlog/advanced-rest-concepts-aspnetcoreintoaspnetcore-webapi/AdvancedRestConceptsSeries/, one folder per article plus the starting project, following thedotnet-testing/AspNetCoreTestingSeries/layout. The aggregateAdvancedRestConceptsSeries.slnsits at the two-level path CI builds.StartingProjectpaging-startPagingpaging-endFilteringfiltering-endSearchingsearching-endSortingsorting-endDataShapingdatashaping-endHateoashateoas-endWhy this is a rewrite and not a copy
All twelve source branches predate .NET 5, and ten of them share one commit dated 2020-10-02:
netcoreapp3.0, EF Core 2.2,Pomelo.EntityFrameworkCore.MySql2.2,System.Linq.Dynamic.Core1.0.19,Startup.csandWebHost.CreateDefaultBuilder.net10.0on every project,Nullableenabled, file-scoped namespaces.Startup.csfolded into a minimal-hostingProgram.csand deleted.Microsoft.EntityFrameworkCore.SqlServer10.0.11 replaces Pomelo.Pomelo.EntityFrameworkCore.MySqltops out at 9.0.0 with a hard[9.0.0, 9.0.999]bound on EF Core Relational, so there is no EF Core 10 MySQL path. The MySQL dump is replaced by_SqlServer_Init_Script/init.sql, andappsettings.jsonpoints at LocalDB.System.Text.Jsoneverywhere.Microsoft.AspNetCore.Mvc.NewtonsoftJsonis gone, andAddCustomMediaTypes()registers the HATEOAS media types againstSystemTextJsonOutputFormatter.System.Text.JsonserialisesExpandoObjectcorrectly on .NET 10, so nothing needs Newtonsoft any more.System.Linq.Dynamic.Core1.7.4,NLog.Extensions.Logging6.2.0.The folders accumulate rather than revert
Five of the six source branches carry the earlier parts' defects verbatim, so a faithful copy produces a chain in which part N+1 undoes part N four times over. Every folder here carries every fix the earlier articles make.
PagedList<T>.ToPagedListAsync(IQueryable<T>, ...)built onCountAsync()/ToListAsync(), in all six folders. TheIEnumerable<T>overload thatdatashaping-endintroduced is not carried over:DataShapingandHateoaspage the sortedIQueryablefirst and shape the returned page, so paging happens in the database in every folder rather than only the first four, and theX-Paginationvalues come from the paged query and never from the shaped list.QueryStringParametersisabstractin all six folders (filtering-endsilently dropped the keyword andsearching-endrestored it), and it clamps both ends:Math.Max(value, 1)on the page number andMath.Clamp(value, 1, 50)on the page size.?pageNumber=0used to becomeSkip(-10), which SQL Server rejects outright with a 500.Response.Headers["X-Pagination"] = ...in place of.Add(...), which throwsArgumentExceptionon a second write, plus.WithExposedHeaders("X-Pagination")inConfigureCorsso a browser can actually read the header the articles tell readers to consume.int?with[Range(1900, 2100)]and a composed half-open date range, instead ofuint, aDateTime.Now.Yearproperty initializer ando.DateOfBirth.Year >= ... && <= ..., which translates toDATEPART(year, ...)on both sides and defeats any index on the column.ValidYearRangecompares with>=, so a single-year filter is no longer a 400, and the controller returnsProblemDetailsrather than a bare string.SearchByName(ref IQueryable<Owner>, string)becomes aSearchextension method. That removes the!owners.Any()guard, which fires aSELECT CASE WHEN EXISTS ...round trip purely to decide whether to add aWHEREto a query that has not run, the duplicated deadIsNullOrEmptycheck, and theToLowerInvariant()that EF Core 10 cannot translate. Onsorting-end,datashaping-endandhateoas-end, any request carrying a search term returned 500.SortHelper<T>drops!entities.Any(), restores the empty-query guard the generic refactor lost (OrderBy("")throwsArgumentExceptionin Dynamic LINQ, so?orderBy=agewas an unhandled 500), and compares" desc"withStringComparison.OrdinalIgnoreCaseso?orderBy=name DESCsorts descending. Because the restored guard hands the default ordering back to the caller, the repository keeps one, so an explicitly emptyorderBystill pages an ordered query instead of letting EF Core emitORDER BY (SELECT 1).DataShaper<T>holds itsPropertyInfo[]private readonlyand is registered as a singleton rather than scoped, sotypeof(T).GetProperties()stops running on every request; property-name matching isOrdinalIgnoreCase;FetchData's accumulating loop becomes aSelect.RepositoryBaseandRepositoryWrapper,usingblocks, andSortHelper'ssortingOrdervariable, whichhateoas-endrenamed for no behavioural reason.Tests
Every article folder gets an
AccountOwnerServer.TestsxUnit project. The in-memory tests need nothing but the assembly under test and always run: the page clamp, the year-range validation, the search extension, the sort helper (including the unknown-field and upper-caseDESCcases), the data shaper, and the HATEOAS media-type filter. On the installed SDK (10.0.302) that is 117 passing tests across the six folders, none of which touch a database.The repository tests that need a real database start a SQL Server through Testcontainers. Those 28 tests are skipped unless
CODEMAZE_SQLSERVER_TESTS=1is set, and they are deliberately gated rather than left to run on CI: themcr.microsoft.com/mssql/serverimage has never been shown to start on these runners, and a suite that silently fails to reach a database is worse than one that says it did not try. Run them locally with:They have not been executed in this branch: no Docker daemon was available on the machine that prepared it. They compile, they are skipped by name in the run output, and the container path is unproven. If someone with Docker confirms them, the gate can be relaxed to run by default on CI.
Verification
dotnet buildanddotnet testare green on .NET SDK 10.0.302 for each of the seven per-article solutions and for the aggregateAdvancedRestConceptsSeries.sln, with no warnings.