Skip to content

Add .NET 11 core and native OpenAPI support (RC validation) - #237

Draft
avgalex wants to merge 12 commits into
masterfrom
feature/issue-236-net11
Draft

avgalex wants to merge 12 commits into
masterfrom
feature/issue-236-net11

Conversation

@avgalex

@avgalex avgalex commented Oct 31, 2026

Copy link
Copy Markdown
Member

Problem and resulting behavior

The native adapter has no .NET 11 asset and its .NET 10 dependency range excludes Microsoft.AspNetCore.OpenApi 11. This adds net11.0 assets to the shared core and native adapter, including Microsoft.OpenApi 3.x compatibility, so FluentValidation constraints are retained in native OpenAPI 3.2 and explicitly selected 3.1 documents.

Refs #236. Draft: RC validation, not ready to merge or publish.

Changes

  • Preserve existing target frameworks and dependency combinations. The only production C# change narrows a form media type to its mutable concrete type, safely skipping references.
  • Add runtime/serialization coverage for nullability, required properties, identity child validators, collections, conditional rules, shared references, parameters and forms. Verify both build-time documents on the existing sample.
  • Add .NET 11 test-host rows for the existing Swashbuckle and NSwag assets. This PR does not add their net11 package assets or include the separate Swashbuckle 11 preview experiment.
  • Install the RC SDK in CI, generate artifacts before tests, and disable package upload on both OSes. Disable the sample's automatic document-generation hook; explicit generation remains available.

Compatibility decisions

  • global.json becomes tracked for the first time. It requires an eligible SDK 11.0 at or above 11.0.100-rc.3.26522.148, using latestFeature; it does not require exactly RC3 or roll automatically to SDK 12.
  • Keep runtime float/double conversion without added library rounding. Runtime 11 emits more precise numeric bounds; strict per-runtime test expectations document this difference, while net8–10 expectations remain unchanged.

Validation

  • Independent local Release solution run: exit 0, 781/781 across ten project/framework combinations on macOS with SDK 11 RC3 (2ea75ab; this head adds README documentation only).
  • Native OpenAPI 3.2.0 / Staging and 3.1.2 / Production build-time artifacts retain FV constraints. Additional explicit ru-RU native runs: 44/44 and 54/54.
  • Linux/Windows CI is pending this PR. No CI success is claimed from local results.

Before merge/release

Keep this draft until GA alignment: stable SDK and Microsoft.AspNetCore.OpenApi 11 dependency floor, version 7.3.0, final compatibility documentation/changelog and green Linux/Windows CI. Package publishing must remain disabled during RC validation; release actions require a separate decision.

avgalex added 10 commits October 31, 2026 14:15
…rals

The four expected values in SampleValidator_FromSampleApi_Test were
written as casts of float/double literals. The .NET 11 SDK folds
(decimal)5.1f and (decimal)5.1d to the exact binary expansion of the
source literal instead of the 15-significant-digit rounding used by
earlier SDKs, so the expected literals drifted with the compiler while
the runtime contract stayed at 5.1 / 10.2 for both the float and the
double series. The double drift was masked until now because
FluentAssertions stops at the first failed assertion.

Replace the casts with explicit decimal literals (5.1m, 10.2m) that pin
the SDK-independent runtime cast / Convert.ToDecimal semantics.

Verified: full suite 569/569 green under SDK 10.0.100 and under SDK
11.0.100-rc.3.26522.148; test counts unchanged.
Phase 1 of ADR-008 (.NET 11 support, issue #236):

- core MicroElements.OpenApi.FluentValidation: TargetFrameworks += net11.0.
  The core package has no Microsoft.OpenApi dependency and compiles clean
  on net11.0 (ADR 1.2(5)).
- workflow: setup-dotnet installs the exact SDK 11.0.100-rc.3.26522.148
  alongside 8.0.x/9.0.x/10.0.x.
- workflow: the Linux build step now passes --ForceUploadPackages=false
  --UploadPackages=false. The pull_request trigger runs the same command
  as the publishing master push, so a feature-branch PR must not be able
  to publish. Revert at GA alignment (ADR delivery gate).

Note: the exact global.json SDK pin is applied in the local checkout;
the file is untracked in this repository (listed in .git/info/exclude),
so tracking it needs a separate maintainer decision.

Verified locally (SDK 11.0.100-rc.3.26522.148): dotnet build / pack
exit 0, 0 errors; packed 7.2.3 package has a net11.0 asset whose nuspec
dependency group (FluentValidation 12.0.0 + AspNetCore.App framework
reference) matches the existing net9.0/net10.0 groups; no new restore
warning classes (NU1902 audit warnings are pre-existing).
Phase 1 of ADR-008 follow-up (coordinator-approved): the repository did
not track global.json at all (locally excluded), so the exact SDK pin
decided in ADR 2.6 could not reach CI and contributors. Track the file
with version 11.0.100-rc.3.26522.148, rollForward: disable,
allowPrerelease: true.

The RC pin moves to the GA SDK at the Phase 4 gate.
Phase 2 of ADR-008 (.NET 11 support, issue #236):

- TargetFrameworks += net11.0; the OPENAPI_V2 define condition becomes
  'net10.0' Or 'net11.0' — the V2 code shape is the one that compiles
  against both Microsoft.OpenApi 2.x and 3.x. No OPENAPI_V3 is
  introduced (ADR Decision 1).
- net11.0 dependency: Microsoft.AspNetCore.OpenApi [11.0.0-rc.3.26522.148,
  12.0.0) (dev range while .NET 11 is at RC; the shipped range
  [11.0.0,12.0.0) and version 7.3.0 land at the Phase 4 GA gate).
- FluentValidationOperationTransformer: the one narrowing the OpenApi 3.x
  media-type abstraction requires — content.Value as OpenApiMediaType —
  next to the existing null guard, which now also skips reference media
  types (OpenApiMediaTypeReference), matching the requestBody-reference
  skip policy in the same method. Signatures and the compatibility layer
  are unchanged.

Verified locally (SDK 11.0.100-rc.3.26522.148): build exit 0 with 0
errors on net9.0/net10.0/net11.0; native test suites on net9.0 (38) and
net10.0 (39) green with unchanged counts; public-surface inventory
(normalized reflection listing, net10.0 vs net11.0) shows no differences
for the native adapter and core.
… sample

Phase 3 of ADR-008 (.NET 11 support, issue #236):

Native tests (net9/net10/net11):
- net11.0 target; OPENAPI_V2 extended; Mvc.Testing 11.0.0-rc.3 line;
  InterceptorsNamespaces extended to net11 (CS9137 evidence: the
  Microsoft.AspNetCore.OpenApi.Generated interceptors are emitted on
  net11 as well).
- test host: third named document v31 (OpenApi3_1, net11-only; v1 stays
  at the default 3.2); invariant culture for document generation (the
  net9/OpenApi 1.x writer formats fractional decimal bounds with the
  host culture, e.g. ru_RU produces invalid JSON); FvRules:Enabled
  switch for the binary control host.
- new fixtures: scalar SetValidator case on the identity fixture
  (WiredCode), TestRuleForEachModel, TestSharedDto, TestDecimalBoundsModel
  (decimal bounds 5.1m/10.2m guarding the SDK folding class).
- new tests: conditional rule semantics (default ConditionalRulesMode
  .Exclude), shared schema across two endpoints, exact decimal strings
  (raw JSON 5.1/10.2 on both documents), net11-only document versions
  (3.2.0 / 3.1.2), nullability shape on both documents, reference media
  type skip (no write attempt, no warning), binary response smoke
  (FileContentResult -> type: string, format: binary, identical with and
  without our transformers), build-time artifact assertions
  (env-Staging / env-Production markers).

Swashbuckle tests: net11.0 test-host row only (Swashbuckle 10.2.1 +
Microsoft.OpenApi 2.7.5 pins); the package keeps net8/net9/net10 assets.
Recorded result: 137/138 - the float-range test fails because the .NET 11
runtime changed float/double-to-decimal conversion to the exact binary
expansion (verified by probe; the .NET 8-10 runtimes round to 15
significant digits). Swashbuckle-on-net11 stays 'not supported' per the
ADR until this row is green.

NSwag tests: net11.0 target + a narrow serialized-document assertion;
net11 transitive runtime compatibility confirmed (8/8).

SampleAspNetCoreOpenApi: net11.0; Microsoft.Extensions.ApiDescription
.Server 11.0.0-rc.3 (PrivateAssets=all); env-marker document transformer.
Build-time generation: on a multi-targeted project 'dotnet build -f'
skips the inner generation hook (ADR 4 commands verified literally, exit
0, no documents); documents were generated by invoking the same
GenerateOpenApiDocuments target directly on the net11.0 inner build:
openapi 3.2.0 / env-Staging and 3.1.2 / env-Production.

Known gap recorded for the coordinator: RuleForEach child rules do not
reach the collection items schema on any TFM; adding the mapping is a
behavior change outside the ADR scope.
…dening

Coordinator-directed fix-up for ADR-008 Phase 3 (issue #236):

- The RuleForEach fixture now uses the direct RuleForEach form (which
  creates a CollectionPropertyRule mapped to the items schema on every
  TFM); the collection-level NotEmpty stays a separate RuleFor. The
  positive items test runs on v1 and (net11) v31. The RuleFor(...).ForEach
  (...) shape is a separate pre-existing limitation recorded for a
  follow-up; no core or adapter mapping change.
- Float/double bounds characterization (net11-only): pins the
  net11-runtime visible bound values (exact binary expansion, verified by
  an independent conversion probe; the .NET 8-10 runtimes round to 15
  significant digits). No rounding added; the Swashbuckle net11 host row
  stays as recorded (137/138, 'not supported' until green).
- v31 constraint parity: a parameterized test asserts required, length,
  pattern, numeric bounds, shared $ref, collection items, query and
  header parameters on both the default (3.2) and explicit (3.1)
  documents; a representative form check runs against the isolated v2
  document.
- Binary smoke: the control host is verified to really run without the
  FluentValidation rules (Customer.firstName unconstrained there,
  constrained in the main host) before the binary schema comparison.
- The invariant-culture stabilization is narrowed to the net9/OpenApi 1.x
  path; net10/net11 run unpinned under the host culture.
- Sample: Microsoft.Extensions.ApiDescription.Server is restricted to the
  net11.0 ItemGroup.
- Build-time generation erratum: on this multi-targeted sample the ADR's
  literal 'dotnet build -f' commands do not trigger the generation hook;
  the verified invocation is a direct GenerateOpenApiDocuments SDK-target
  call (Release), and the per-project OpenApiFiles cache is removed before
  the 3.1 run (the incremental check otherwise skips the second
  generation). Both artifacts now assert the FluentValidation constraints
  (Customer.firstName minLength/maxLength/required, age bounds) in
  addition to version/title/environment markers.
- Workflow: a generation step before the test build on both OS (the one
  authorized Phase 3 dependency), with explicit existence checks; nothing
  is published and no run is triggered from here.
…ep hardening

Final coordinator-directed package for ADR-008 Phase 3 (issue #236):

- Swashbuckle net11 test-oracle correction: under NET11_0_OR_GREATER the
  four float/double range expectations use the values observed on the
  .NET 11 runtime by the independent conversion probes (exact binary
  expansion of the source literal); net8-net10 keep 5.1m/10.2m. All
  assertions and their count unchanged, no tolerance/skip/removal; the
  former red 137/138 run is preserved as evidence. The host row is now
  138/138: local host-level compatibility on net11 is verified - this is
  not a net11 package asset, a release permission, or a full
  compatibility claim. Comments state observed results per runtime;
  no general rounding formula is claimed. No library code change.
- ConstraintParity_AcrossDocuments extended: the When-guarded phone rule
  stays excluded and not required, and the wiredCode scalar SetValidator
  case (minLength 1 / maxLength 10, not required) on both documents.
- Form constraints: the isolated v2 document is exercised as 3.2 (host
  default) and as 3.1 via a test-only named PostConfigure on the same
  host; the actual openapi prefix is asserted per variant. GroupName=v2
  isolation is kept; no new web app, no product option.
- Culture_Diagnostic test records the culture the test host runs under
  (value recorded, not asserted - a host may resolve to invariant).
  net10/net11 runs under LC_ALL=ru_RU.UTF-8 are green (44/44, 54/54).
- Workflow generation step: the OpenApiFiles cache is removed before
  EACH generation run (a stale cache makes the first run an incremental
  no-op on re-runs), and the Windows step checks $LASTEXITCODE after
  every dotnet invocation. Nothing published, nothing triggered.
…ample

Follow-up to ADR-008 Phase 3 (issue #236), coordinator-authorized narrow
fix: with the ApiDescription.Server package referenced on net11.0 only,
the package's outer-build hook (_GenerateOpenApiDocuments, on by default)
picks the FIRST TFM of a multi-targeted project (net9.0) and the net9
inner build fails with MSB4057 ('GenerateOpenApiDocuments' target does
not exist) during an ordinary solution build/test with restore.

Set OpenApiGenerateDocumentsOnBuild=false in the sample: this
multi-targeted sample generates its build-time documents through the
explicit Phase 3 commands (dotnet msbuild -target:Build,
GenerateOpenApiDocuments -p:TargetFramework=net11.0), which are
unaffected by this property - it only disables the automatic hook.

Verified: clean-obj solution build exit 0 (no MSB4057); both explicit
generation runs exit 0 with artifacts; the previously failing
'dotnet test <sln> -c Release' exits 0. No tests disabled, no TFM
reorder, no custom targets.
Use latestFeature instead of requiring one exact RC SDK. Keep the validated 11.0 RC baseline and allow newer installed SDKs in the same major/minor line without rolling to SDK 12.
Distinguish net11 assets from runtime-host verification, document inherited numeric bounds and RC validation limits, and correct the Swashbuckle default document version.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant