Skip to content

V10.3.0/support for minimal api#147

Merged
gimlichael merged 8 commits intomainfrom
v10.3.0/support-for-minimal-api
Feb 17, 2026
Merged

V10.3.0/support for minimal api#147
gimlichael merged 8 commits intomainfrom
v10.3.0/support-for-minimal-api

Conversation

@gimlichael
Copy link
Member

@gimlichael gimlichael commented Feb 17, 2026

This pull request ensures that options configuration for formatters and services is registered only once, even if the registration method is called multiple times. It updates the relevant extension methods to use TryConfigure instead of Configure, and adds tests to verify this singleton registration behavior.

Dependency Injection Improvements:

  • Changed AddJsonFormatterOptions and AddXmlFormatterOptions in their respective ServiceCollectionExtensions to use TryConfigure, preventing duplicate IConfigureOptions registrations for formatter options. [1] [2]
  • Updated TryAdd<TOptions> extension methods in ServiceCollectionExtensions to use TryConfigure for options setup, ensuring only one registration per options type. [1] [2]
  • Added Cuemon.Extensions.DependencyInjection using statements where needed to support these changes. [1] [2]

Testing Enhancements:

  • Added new tests for AddJsonFormatterOptions and AddXmlFormatterOptions to confirm that multiple calls result in only a single IConfigureOptions registration. [1] [2]
  • Added tests for Add and TryAdd extension methods to verify that options are registered only once, regardless of how many times the methods are called with the same options type.

Summary by CodeRabbit

  • New Features

    • Added minimal JSON options to map formatter settings into ASP.NET Core JSON configuration and an extension to register it.
  • Bug Fixes

    • Made options registration resilient to repeated initialization to prevent duplicate registrations.
  • Tests

    • Added tests ensuring idempotent registration for JSON, XML and DI extensions and comprehensive tests verifying JSON option propagation.

@gimlichael gimlichael self-assigned this Feb 17, 2026
Copilot AI review requested due to automatic review settings February 17, 2026 04:23
@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Replaced unconditional Configure registrations with TryConfigure to prevent duplicate option registrations; added MinimalJsonOptions and AddMinimalJsonOptions to map JsonFormatterOptions into JsonOptions; added tests verifying idempotent registrations and JsonOptions propagation.

Changes

Cohort / File(s) Summary
Formatter extensions (Configure → TryConfigure)
src/Cuemon.Extensions.AspNetCore.Text.Json/Formatters/ServiceCollectionExtensions.cs, src/Cuemon.Extensions.AspNetCore.Xml/Formatters/ServiceCollectionExtensions.cs
Added using Cuemon.Extensions.DependencyInjection; and switched services.Configure(...) to services.TryConfigure(...) for formatter option registration.
Core DI extensions
src/Cuemon.Extensions.DependencyInjection/ServiceCollectionExtensions.cs
Replaced Configure(setup) with TryConfigure(setup) across overloads to guard against duplicate option registrations.
Minimal JSON options feature
src/Cuemon.Extensions.AspNetCore.Text.Json/MinimalJsonOptions.cs, src/Cuemon.Extensions.AspNetCore.Text.Json/ServiceCollectionExtensions.cs
Introduced MinimalJsonOptions : ConfigureOptions<JsonOptions> that maps JsonFormatterOptions into JsonOptions.SerializerOptions; added AddMinimalJsonOptions extension which registers it via TryAddEnumerable and wires formatter setup.
Text.Json tests — options propagation
test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/MinimalJsonOptionsTest.cs
New tests asserting many JsonFormatterOptions fields propagate into JsonOptions.SerializerOptions (policies, flags, converters, encoder, TypeInfoResolver, indent settings, etc.).
Text.Json tests — registration behavior
test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/ServiceCollectionExtensionsTest.cs, test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/Formatters/ServiceCollectionExtensionsTest.cs
Added tests verifying MinimalJsonOptions registered as singleton, idempotency of AddMinimalJsonOptions, presence of formatter configuration, and exception formatter registration.
Xml formatter tests — registration behavior
test/Cuemon.Extensions.AspNetCore.Tests/Xml/Formatters/ServiceCollectionExtensionsTest.cs
Added test asserting AddXmlFormatterOptions registers a single IConfigureOptions<XmlFormatterOptions> when called multiple times.
DI tests — registration behavior
test/Cuemon.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTest.cs
Added tests ensuring Add/TryAdd/TryAdd-with-factory produce only one IConfigureOptions<FakeOptions> when invoked repeatedly.
Project files
*.csproj
Manifest updated to include new/changed files.

Sequence Diagram(s)

sequenceDiagram
    participant Dev as Developer/Startup
    participant SC as IServiceCollection
    participant DI as DI Container
    participant M as MinimalJsonOptions
    participant JO as JsonOptions

    Dev->>SC: Call AddMinimalJsonOptions(setup?)
    SC->>SC: TryAddEnumerable(IConfigureOptions<JsonOptions>=MinimalJsonOptions)
    SC->>SC: TryConfigure(JsonFormatterOptions) + AddJsonExceptionResponseFormatter
    DI->>M: Resolve MinimalJsonOptions (IOptions<JsonFormatterOptions>)
    M->>JO: Apply JsonFormatterOptions -> JsonOptions.SerializerOptions
    JO-->>DI: Configured JsonOptions available to framework
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hopped through lines with nimble paws,
Swapped Configure for TryConfigure because.
MinimalJson shapes serializer light,
Tests ensure registrations stay polite.
A carrot nod for changes done right. 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'V10.3.0/support for minimal api' partially relates to the changeset. While minimal API support is mentioned, it refers to 'minimal api' which appears in test files and new extensions, but the primary changes focus on preventing duplicate options registration via TryConfigure. Consider a more specific title that emphasizes the main change, such as 'Replace Configure with TryConfigure to prevent duplicate options registration' or 'Ensure idempotent registration of formatter options'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch v10.3.0/support-for-minimal-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Cuemon.Extensions.DependencyInjection/ServiceCollectionExtensions.cs (1)

73-81: ⚠️ Potential issue | 🔴 Critical

Add<TOptions> still uses Configure — the new test expects idempotent behavior that this code does not provide.

Line 79 calls services.Configure(setup), which unconditionally adds an IConfigureOptions<TOptions> registration on every call. However, the new test Add_ShouldOnlyRegisterOptionsOnce_WhenCalledMultipleTimesWithSameOptionsType (Line 627-642 in the test file) asserts that calling Add three times with the same TOptions results in only one IConfigureOptions<FakeOptions> registration. This test will fail.

Either the test expectation is wrong (should assert 3 registrations), or this line should also switch to TryConfigure:

Option A: Make Add idempotent for options too
         services.AddServices(service, implementation, lifetime, false);
-            services.Configure(setup);
+            services.TryConfigure(setup);
         return services;

Apply the same change to Line 149 (Add with factory overload).

Option B: Fix the test to match current behavior
-            Assert.Equal(1, configureOptionsCount);
+            Assert.Equal(3, configureOptionsCount);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Cuemon.Extensions.DependencyInjection/ServiceCollectionExtensions.cs`
around lines 73 - 81, The Add<TOptions>(this IServiceCollection services, Type
service, Type implementation, ServiceLifetime lifetime, Action<TOptions> setup)
method currently calls services.Configure(setup) which registers
IConfigureOptions<TOptions> on every call; change this to
services.TryConfigure(setup) to make option registrations idempotent (so
repeated calls only add one IConfigureOptions<TOptions>). Apply the same change
to the other Add overload that accepts a factory (the overload referenced as the
Add with factory) so both Add<TOptions> code paths use TryConfigure instead of
Configure; keep existing null checks (Validator.ThrowIfNull) and service
registration via AddServices unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@test/Cuemon.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTest.cs`:
- Around line 627-642: The test fails because Add<TOptions> in
ServiceCollectionExtensions still calls services.Configure(setup), causing a new
IConfigureOptions<TOptions> to be registered on each call; change the
registration to use the "try" variant so the options setup is only registered
once — replace the Configure call with the idempotent TryConfigure (or the
equivalent TryAdd/TryAddEnumerable registration) inside the Add<TOptions>
implementation so multiple calls to Add<TOptions> only result in a single
IConfigureOptions<TOptions] registration.

---

Outside diff comments:
In `@src/Cuemon.Extensions.DependencyInjection/ServiceCollectionExtensions.cs`:
- Around line 73-81: The Add<TOptions>(this IServiceCollection services, Type
service, Type implementation, ServiceLifetime lifetime, Action<TOptions> setup)
method currently calls services.Configure(setup) which registers
IConfigureOptions<TOptions> on every call; change this to
services.TryConfigure(setup) to make option registrations idempotent (so
repeated calls only add one IConfigureOptions<TOptions>). Apply the same change
to the other Add overload that accepts a factory (the overload referenced as the
Add with factory) so both Add<TOptions> code paths use TryConfigure instead of
Configure; keep existing null checks (Validator.ThrowIfNull) and service
registration via AddServices unchanged.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request enhances dependency injection options registration to prevent duplicate IConfigureOptions registrations when methods are called multiple times. The changes support minimal API scenarios where formatter and service options may be registered in multiple places. The PR modifies the TryAdd<TOptions> extension methods to use TryConfigure instead of Configure, and updates the AddJsonFormatterOptions and AddXmlFormatterOptions methods to ensure singleton registration behavior.

Changes:

  • Modified TryAdd<TOptions> methods in ServiceCollectionExtensions.cs to use TryConfigure for preventing duplicate option registrations
  • Updated AddJsonFormatterOptions and AddXmlFormatterOptions to use TryConfigure for singleton registration behavior
  • Added comprehensive tests to verify single registration behavior for formatter options and service options

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Cuemon.Extensions.DependencyInjection/ServiceCollectionExtensions.cs Updated two TryAdd<TOptions> overloads to use TryConfigure instead of Configure
src/Cuemon.Extensions.AspNetCore.Text.Json/Formatters/ServiceCollectionExtensions.cs Changed AddJsonFormatterOptions to use TryConfigure and added required using statement
src/Cuemon.Extensions.AspNetCore.Xml/Formatters/ServiceCollectionExtensions.cs Changed AddXmlFormatterOptions to use TryConfigure and added required using statement
test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/Formatters/ServiceCollectionExtensionsTest.cs New test file verifying AddJsonFormatterOptions only registers once
test/Cuemon.Extensions.AspNetCore.Tests/Xml/Formatters/ServiceCollectionExtensionsTest.cs New test file verifying AddXmlFormatterOptions only registers once
test/Cuemon.Extensions.DependencyInjection.Tests/ServiceCollectionExtensionsTest.cs Added three new tests verifying single registration for Add and TryAdd methods

@codecov
Copy link

codecov bot commented Feb 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.59%. Comparing base (275a35b) to head (8b580dc).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #147      +/-   ##
==========================================
+ Coverage   80.53%   80.59%   +0.06%     
==========================================
  Files         598      600       +2     
  Lines       18839    18886      +47     
  Branches     1936     1937       +1     
==========================================
+ Hits        15172    15222      +50     
+ Misses       3601     3598       -3     
  Partials       66       66              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/Cuemon.Extensions.AspNetCore.Text.Json/MinimalJsonOptions.cs (1)

14-17: Add <param> XML doc for the formatterOptions constructor parameter.

The constructor is public API but the parameter lacks documentation.

As per coding guidelines, public and protected members must have XML doc comments following existing wording and style.

📝 Proposed doc addition
         /// <summary>
         /// Creates a new <see cref="MinimalJsonOptions"/>.
         /// </summary>
+        /// <param name="formatterOptions">The <see cref="IOptions{JsonFormatterOptions}"/> to configure <see cref="JsonOptions"/> from.</param>
         public MinimalJsonOptions(IOptions<JsonFormatterOptions> formatterOptions) : base(mo =>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Cuemon.Extensions.AspNetCore.Text.Json/MinimalJsonOptions.cs` around
lines 14 - 17, Add an XML <param> tag for the constructor parameter
`formatterOptions` on the public constructor
MinimalJsonOptions(IOptions<JsonFormatterOptions> formatterOptions), documenting
that it supplies the JsonFormatterOptions via IOptions and describing the
expected role (e.g., options used to configure JSON formatting behavior); ensure
the wording and tag style match existing XML docs in the file for public
members.
test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/MinimalJsonOptionsTest.cs (1)

34-47: Consider testing WriteIndented = true to verify actual propagation.

Setting WriteIndented = false tests the default value of JsonSerializerOptions, so this test would pass even if propagation were broken. Setting it to true would confirm the value is actually being applied.

Proposed change
-                o.Settings.WriteIndented = false;
+                o.Settings.WriteIndented = true;
             });
 
             var provider = services.BuildServiceProvider();
             var jsonOptions = provider.GetRequiredService<IOptions<JsonOptions>>().Value;
 
-            Assert.False(jsonOptions.SerializerOptions.WriteIndented);
+            Assert.True(jsonOptions.SerializerOptions.WriteIndented);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/MinimalJsonOptionsTest.cs`
around lines 34 - 47, Update the test
MinimalJsonOptions_ShouldPropagateWriteIndented_FromJsonFormatterOptions to set
o.Settings.WriteIndented = true instead of false and assert that
jsonOptions.SerializerOptions.WriteIndented is true; locate the test method by
name and the call to services.AddMinimalJsonOptions (and the retrieval via
provider.GetRequiredService<IOptions<JsonOptions>>().Value) and change the
arranged value and the Assert.False to Assert.True so the test verifies actual
propagation of the non-default setting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Cuemon.Extensions.AspNetCore.Text.Json/MinimalJsonOptions.cs`:
- Around line 17-23: The Configure delegate in MinimalJsonOptions' constructor
mutates the shared formatterOptions.Value.Settings collection causing duplicate
converters; fix by cloning the JsonFormatterOptions.Settings (or the
Settings.Converters collection) before calling
AddHttpExceptionDescriptorConverter so you modify a per-call copy, then pass
that cloned converters list into
Decorator.Enclose(mo.SerializerOptions.Converters).AddRange(...); alternatively,
perform the AddHttpExceptionDescriptorConverter call once during initialization
(outside the Configure lambda) to avoid repeated mutation of
formatterOptions.Value; key symbols:
MinimalJsonOptions(IOptions<JsonFormatterOptions>),
formatterOptions.Value.Settings, AddHttpExceptionDescriptorConverter, and
Decorator.Enclose(...).AddRange.

In `@test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/MinimalJsonOptionsTest.cs`:
- Around line 259-274: The test
MinimalJsonOptions_ShouldNotOverrideTypeInfoResolver_WhenNull only logs the
resolver state and has no assertion, so change it to assert the expected
behavior: after calling services.AddMinimalJsonOptions(o =>
o.Settings.TypeInfoResolver = null) and building provider, verify
jsonOptions.SerializerOptions.TypeInfoResolver was not overridden by asserting
it is not null (e.g.,
Assert.NotNull(jsonOptions.SerializerOptions.TypeInfoResolver)) so the test
actually fails if the resolver gets cleared.

---

Nitpick comments:
In `@src/Cuemon.Extensions.AspNetCore.Text.Json/MinimalJsonOptions.cs`:
- Around line 14-17: Add an XML <param> tag for the constructor parameter
`formatterOptions` on the public constructor
MinimalJsonOptions(IOptions<JsonFormatterOptions> formatterOptions), documenting
that it supplies the JsonFormatterOptions via IOptions and describing the
expected role (e.g., options used to configure JSON formatting behavior); ensure
the wording and tag style match existing XML docs in the file for public
members.

In `@test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/MinimalJsonOptionsTest.cs`:
- Around line 34-47: Update the test
MinimalJsonOptions_ShouldPropagateWriteIndented_FromJsonFormatterOptions to set
o.Settings.WriteIndented = true instead of false and assert that
jsonOptions.SerializerOptions.WriteIndented is true; locate the test method by
name and the call to services.AddMinimalJsonOptions (and the retrieval via
provider.GetRequiredService<IOptions<JsonOptions>>().Value) and change the
arranged value and the Assert.False to Assert.True so the test verifies actual
propagation of the non-default setting.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Cuemon.Extensions.AspNetCore.Text.Json/MinimalJsonOptions.cs`:
- Around line 15-17: Update the XML doc comment for the MinimalJsonOptions
constructor to include a <param name="formatterOptions"> entry describing the
constructor parameter; locate the public constructor for the MinimalJsonOptions
type (the constructor that accepts formatterOptions) and add a concise
description for formatterOptions consistent with existing doc wording/style so
the public member parameter is documented.

---

Duplicate comments:
In `@test/Cuemon.Extensions.AspNetCore.Tests/Text.Json/MinimalJsonOptionsTest.cs`:
- Around line 280-295: The test lacks an assertion and should verify that a
pre-existing target resolver is preserved when the source resolver is null;
before calling AddMinimalJsonOptions in
MinimalJsonOptions_ShouldNotOverrideTypeInfoResolver_WhenNull, register a known
sentinel resolver on JsonOptions (e.g., services.Configure<JsonOptions>(opts =>
opts.SerializerOptions.TypeInfoResolver = mySentinelResolver) or set it via
AddOptions), then call services.AddMinimalJsonOptions(o =>
o.Settings.TypeInfoResolver = null), build the provider, retrieve jsonOptions
via provider.GetRequiredService<IOptions<JsonOptions>>().Value and assert that
jsonOptions.SerializerOptions.TypeInfoResolver is the same sentinel instance (or
remains unchanged) to ensure the null source does not overwrite the target.

@sonarqubecloud
Copy link

@gimlichael gimlichael merged commit 018af1c into main Feb 17, 2026
6 of 7 checks passed
@gimlichael gimlichael deleted the v10.3.0/support-for-minimal-api branch February 17, 2026 21:55
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

Comments