Skip to content

[.NET Generator] Auto-generate ClientSettings class and IConfiguration-enabled constructors#9920

Open
Copilot wants to merge 26 commits intomainfrom
copilot/auto-generate-clientsettings-class
Open

[.NET Generator] Auto-generate ClientSettings class and IConfiguration-enabled constructors#9920
Copilot wants to merge 26 commits intomainfrom
copilot/auto-generate-clientsettings-class

Conversation

Copy link
Contributor

Copilot AI commented Mar 4, 2026

  • Create ClientSettingsProvider.cs - new TypeProvider generating {Client}Settings class extending ClientSettings
  • Modify ClientOptionsProvider.cs - add internal {Client}Options(IConfigurationSection section) constructor
  • Modify ClientProvider.cs - add public {Client}({Client}Settings settings) constructor and internal (AuthenticationPolicy?, endpoint, options) implementation constructor
  • Modify ScmOutputLibrary.cs - register ClientSettingsProvider in the output
  • All previous review feedback addressed
  • Restore global.json to base branch value (10.0.103)
  • Apply all code review nits from @jorgerangel-msft
  • Revert StubLibraryVisitor to keep internal ctors (per @JoshLove-msft)
  • Sort LatestVersionsFields dictionary before consuming in ClientOptionsProvider
  • Add XML docs with see cref link to ClientSettings on generated Settings class
  • Update ClientSettings class doc to follow pattern: "Represents the settings used to configure a {Client} that can be loaded from an IConfigurationSection."
  • Fix OAuth2 Spector tests: _flows is now static readonly, use BindingFlags.Static and GetValue(null) instead of BindingFlags.Instance
  • Extract CredentialParamName and SettingsParamName constants in ClientProvider.cs for consistency
  • Add bool, int, TimeSpan TryParse bindings in ClientSettingsProvider.BindCore() for supported property types
  • Add unit tests for bool, int, string param binding and multi-type property scenarios
  • Run Generate.ps1 and check in generated changes
  • Ensure all Spector tests pass
Original prompt

This section details on the original issue you should resolve

<issue_title>[.NET Generator] Auto-generate ClientSettings class and IConfiguration-enabled constructors for clients</issue_title>
<issue_description>## Overview

Parent issue: Azure/azure-sdk-for-net#55491

The .NET generator should automatically produce IConfiguration-based client construction support for every generated client. This enables developers to configure clients using standard .NET configuration patterns (appsettings.json, environment variables) and register them with dependency injection containers.

Two services have been manually implemented as reference:

The generator should produce these automatically so every client gets configuration support without manual customization.

What the Generator Needs to Produce

Three artifacts per client:

A. {Client}Settings class

  • Extends ClientSettings (from System.ClientModel.Primitives)
  • Properties derived from the client's primary constructor parameters (excluding credential)
  • Implements BindCore(IConfigurationSection section) override
  • Marked [Experimental("SCME0002")]

B. internal {Client}Options(IConfigurationSection section) constructor

  • Calls base(section) — the base options class handles common properties (see Forwarding Binding)
  • Guards with if (section is null || !section.Exists()) { return; } before binding any properties
  • Binds each option-specific property from the section
  • Marked [Experimental("SCME0002")]

C. public {Client}({Client}Settings settings) constructor on the client

  • Delegates to the client's primary constructor (the one with the body implementation) — e.g., (AuthenticationPolicy, Options) or (Uri, AuthenticationPolicy, Options) depending on the client
  • Creates the authentication policy via AuthenticationPolicy.Create(settings) to pass to the primary constructor
  • Extracts other properties from settings and maps them to the primary constructor parameters (e.g., settings?.Endpoint, settings?.Options)
  • Not all clients have a URI parameter — the settings class properties mirror whatever the primary constructor requires
  • Marked [Experimental("SCME0002")]

How to Determine Settings Properties

The generator should inspect the client's primary public constructor (the one with the body implementation) parameters:

  • Endpoint/URI parameters (if present) → become Uri? properties on Settings (e.g., VaultUri, Endpoint)
  • Options parameter → becomes {Client}Options? property on Settings
  • Credential/AuthenticationPolicy parameter → handled by base ClientSettings.Credential / CredentialProvider, NOT duplicated
  • Any other non-credential constructor parameters may become settings properties

How to Implement BindCore

`csharp
protected override void BindCore(IConfigurationSection section)
{
if (section is null || !section.Exists())
{
return;
}

// String → use 'is string' pattern match
if (section["Endpoint"] is string endpoint)
{
    Endpoint = new Uri(endpoint);
}

// Options → GetSection + Exists check + internal ctor
IConfigurationSection optionsSection = section.GetSection("Options");
if (optionsSection.Exists())
{
    Options = new {Client}Options(optionsSection);
}

}
`

Key rule: If a configuration segment doesn't exist, bail early — don't set the property. No validation in BindCore. Let the client constructor that receives the settings do all required parameter validation (e.g., Argument.AssertNotNull).

Type Binding Patterns

The generator should use these patterns to bind properties from IConfigurationSection:

Type Pattern Example
string section[name] is string val TenantId = val
bool bool.TryParse(section[name], out bool val) DisableChallengeResourceVerification = val
Uri Uri.TryCreate(section[name], UriKind.Absolute, out Uri val) RedirectUri = val
TimeSpan TimeSpan.TryParse(section[name], out TimeSpan val) NetworkTimeout = val
int int.TryParse(section[name], out int val) MaxRetries = val
string[] / List<string> section.GetSection(name).GetChildren().Where(c => c.Value is not null).Select(c => c.Value!).ToList() AdditionallyAllowedTenants
Complex object section.GetSection(name) + .Exists() + new Type(section) BrowserCustomization = new BrowserCustomizationOptions(browserSection)
Custom struct/enum (extensible) new TypeName(section[name]) after null check Audience = new AppConfigurationAudience(audience)

For a comprehensive example of bin...


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 2 commits March 4, 2026 20:51
…onfiguration-enabled constructors

- Add new ClientSettingsProvider class that generates {Client}Settings classes
  - Extends ClientSettings from System.ClientModel.Primitives (future type)
  - Properties for endpoint (Uri?) and options ({Client}Options?)
  - BindCore override method binding from IConfigurationSection
  - Marked [Experimental("SCME0002")]
- Add IConfigurationSection constructor to ClientOptionsProvider
  - Internal constructor calling base(section)
  - Guards with null/Exists check
  - Binds non-version string properties from configuration
  - Marked [Experimental("SCME0002")]
- Add settings constructor to ClientProvider for root clients with configurable endpoints
  - Calls primary constructor via this() initializer
  - Passes endpoint, credential (if auth), and options from settings
  - Marked [Experimental("SCME0002")]
- Add ClientSettingsProvider output in ScmOutputLibrary.BuildClient
- Add CSharpType.FromExternalType() public factory method for types not in current NuGet
- Update tests to account for new constructors and generated code

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Settings feature

- Use HashSet for O(1) version property name lookup in BuildConfigurationSectionConstructor
- Rename local variable from 'propValue' suffix to 'FromConfig' for clarity
- Extract IsSettingsConstructor helper in test for maintainability

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI changed the title [WIP] Add auto-generation of ClientSettings class and IConfiguration constructors [.NET Generator] Auto-generate ClientSettings class and IConfiguration-enabled constructors Mar 4, 2026
@microsoft-github-policy-service microsoft-github-policy-service bot added the emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp label Mar 4, 2026
@JoshLove-msft
Copy link
Contributor

@copilot run Generate.ps1 and investigate any failures.

…ClientModel 1.9.0 instead of string-based type reference

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
Copilot AI and others added 2 commits March 5, 2026 04:04
…guard, fix API key credential in settings constructor, fix missing default ctor

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
… fix API key credential extraction, add default ctor, add package dep

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 5, 2026

@copilot run Generate.ps1 and investigate any failures.

Run complete in 04121e3. Generate.ps1 succeeded and the full solution builds with 0 errors. Fixed issues found:

  • Missing default constructor in ClientOptionsProvider when no API versions (suppressed the auto-generated one)
  • Invalid CredentialProvider as ApiKeyCredential cast (replaced with settings?.Credential?.Key != null ? new ApiKeyCredential(settings?.Credential?.Key) : null)
  • Missing non-auth required parameters in settings constructor (e.g. ClientType client, string serviceDeploymentVersion) — ClientSettingsProvider now collects and exposes OtherRequiredParams, generating properties and correct constructor args for them

…on constructor before guard

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
@JoshLove-msft JoshLove-msft marked this pull request as ready for review March 10, 2026 22:05
Copilot AI and others added 2 commits March 11, 2026 20:14
…uration constructor body

Add test coverage for the new configuration-based client initialization features:

- ClientOptionsProviderTests: TestConfigurationSectionConstructorBody validates
  the IConfigurationSection constructor has proper guard statements, version
  initialization, and config section reading.

- ClientProviderTests: TestBuildConstructors_SettingsConstructor validates the
  settings constructor is public, has correct parameter, uses this() initializer,
  and has empty body. TestBuildConstructors_NoSettingsConstructor_WhenNoEndpoint
  validates settings constructor is not generated without an endpoint.

- ClientSettingsProviderTests: New test class validating name, base type,
  properties, BindCore method, experimental attribute, and namespace.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ests

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 11, 2026

@copilot please address the remaining comments

All 3 remaining comments addressed in be7e618. Added 13 new tests total — all passing with no regressions (248 related tests pass).

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>

namespace SampleTypeSpec
{
/// <summary></summary>
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot we should include docs and also link to the docs for the ClientSettings type.

Copy link
Contributor

Choose a reason for hiding this comment

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

IT should follow this pattern - Represents the settings used to configure a SecretClient that can be loaded from an IConfigurationSection.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added in 0d1d6cf. The generated {Client}Settings class now has a summary doc: Settings for the {Client} client, extending <see cref="ClientSettings"/>.

Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot please update to this pattern:
Represents the settings used to configure a SecretClient that can be loaded from an IConfigurationSection.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Applied in 272ad43. The generated {Client}Settings class summary now follows the pattern:

/// <summary> Represents the settings used to configure a <see cref="SampleTypeSpecClient"/> that can be loaded from an <see cref="IConfigurationSection"/>. </summary>

…gsProvider

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
…settings used to configure a {Client} that can be loaded from an IConfigurationSection"

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
// Add non-auth required parameters from the SAME parameter list (requiredParameters)
// to ensure the initializer references the same objects as the constructor signature.
string? authParamName = authFields != null
? (authFields.AuthField.Name != TokenProviderFieldName ? "credential" : authFields.AuthField.AsParameter.Name)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: could we move the param name to a constant for consistency with the existing code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added SettingsParamName constant in e3998e6.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Applied in e3998e6 — added SettingsParamName constant at line 41 and CredentialParamName constant at line 40, referenced throughout.

if (authFields is ApiKeyFields keyFields)
{
// ApiKeyAuthenticationPolicy.CreateHeaderApiKeyPolicy(credential, AuthorizationHeader, prefix?)
var credParam = requiredParameters.FirstOrDefault(p => p.Name == "credential");
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: can we reference the credential string here as a const?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added CredentialParamName constant and replaced all 4 "credential" string literals in e3998e6.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Applied in e3998e6 — added CredentialParamName constant and replaced all 4 "credential" string literals.


/// <summary> Binds configuration values from the given section. </summary>
/// <param name="section"> The configuration section. </param>
protected override void BindCore(IConfigurationSection section)
Copy link
Contributor

Choose a reason for hiding this comment

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

it would be great if we could test parsing the other supported property types like in https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/src/Credentials/DefaultAzureCredentialOptions.cs#L73. Specifically, maybe just the ones outlined in the issue for starters #9842. This can go as a follow up if we don't want to block this pr

Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot please add the additional test cases

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added bool, int, and TimeSpan TryParse bindings in ec76257, with 4 new tests (TestBindCoreMethod_WithBoolParam, TestBindCoreMethod_WithIntParam, TestBindCoreMethod_WithStringParam, TestProperties_WithMultipleParamTypes). All 1187 tests pass.

@JoshLove-msft
Copy link
Contributor

@copilot the Oauth spector tests are failing

Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
Failed Invalid [23 ms]
Error Message:
Flows field should not be null
Expected: not null
But was: null

Stack Trace:
at TestProjects.Spector.Tests.Http.Authentication.Oauth2.OAuthTests.<>c.b__1_0(Uri host) in /mnt/vss/_work/1/s/packages/http-client-csharp/generator/TestProjects/Spector.Tests/Http/Authentication/OAuth2/OAuth2Tests.cs:line 66
at TestProjects.Spector.Tests.SpectorTestBase.Test(Func2 test) in /mnt/vss/_work/1/s/packages/http-client-csharp/generator/TestProjects/Spector.Tests/SpectorTestBase.cs:line 17 at TestProjects.Spector.Tests.SpectorTestBase.Test(Func2 test) in /mnt/vss/_work/1/s/packages/http-client-csharp/generator/TestProjects/Spector.Tests/SpectorTestBase.cs:line 30
at NUnit.Framework.Internal.TaskAwaitAdapter.GenericAdapter1.GetResult() at NUnit.Framework.Internal.AsyncToSyncAdapter.Await(Func1 invoke)
at NUnit.Framework.Internal.Commands.TestMethodCommand.RunTestMethod(TestExecutionContext context)
at NUnit.Framework.Internal.Commands.TestMethodCommand.Execute(TestExecutionContext context)
at NUnit.Framework.Internal.Execution.SimpleWorkItem.<>c__DisplayClass4_0.b__0()
at NUnit.Framework.Internal.ContextUtils.<>c__DisplayClass1_0`1.b__0(Object _)

  1. at TestProjects.Spector.Tests.Http.Authentication.Oauth2.OAuthTests.<>c.b__1_0(Uri host) in /mnt/vss/_work/1/s/packages/http-client-csharp/generator/TestProjects/Spector.Tests/Http/Authentication/OAuth2/OAuth2Tests.cs:line 66
    at TestProjects.Spector.Tests.SpectorTestBase.Test(Func`2 test) in /mnt/vss/_work/1/s/packages/http-client-csharp/generator/TestProjects/Spector.Tests/SpectorTestBase.cs:line 17
    at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)

@JoshLove-msft
Copy link
Contributor

@copilot you need to run Generate.ps1 and check in the changes

…redential/settings param name constants

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 11, 2026

@copilot the Oauth spector tests are failing

...

Fixed in e3998e6. The _flows field is now private static readonly (required for this() initializer compatibility), so the reflection in OAuth2Tests needed BindingFlags.Static and GetValue(null) instead of BindingFlags.Instance and GetValue(testClient). Updated both the Valid and Invalid test methods.

…ider with tests

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 12, 2026

@copilot the Oauth spector tests are failing

...

The OAuth2 test failure was fixed in e3998e6_flows is now static readonly so the reflection needed BindingFlags.Static and GetValue(null). Generate.ps1 confirms all generated code is up to date (no diff after regeneration).

@copilot you need to run Generate.ps1 and check in the changes

Ran Generate.ps1 for all http specs — the generated code is already up to date (no diff). The OAuth2 fix was in the test file (e3998e6), not in generated code. Also added bool/int/TimeSpan TryParse support and tests in ec76257.

@github-actions
Copy link
Contributor

No changes needing a change description found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[.NET Generator] Auto-generate ClientSettings class and IConfiguration-enabled constructors for clients

4 participants