diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientSettingsProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientSettingsProvider.cs
index af0741751ea..5f7e3d38390 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientSettingsProvider.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientSettingsProvider.cs
@@ -163,13 +163,13 @@ protected override MethodProvider[] BuildMethods()
if (EndpointProperty != null)
{
- AppendBindingForProperty(body, sectionParam, EndpointProperty.Name, EndpointProperty.Name.ToVariableName(), EndpointProperty.Type);
+ AppendBindingForProperty(body, sectionParam, GetSettingPropertyName(EndpointProperty.Name), EndpointProperty.Name.ToVariableName(), EndpointProperty.Type, EndpointProperty.Name);
}
foreach (var param in OtherRequiredParams)
{
var propName = param.Name.ToIdentifierName();
- AppendBindingForProperty(body, sectionParam, propName, param.Name.ToVariableName(), param.Type);
+ AppendBindingForProperty(body, sectionParam, GetSettingPropertyName(propName), param.Name.ToVariableName(), param.Type, propName);
}
// Bind custom constructor parameters from custom code.
@@ -212,7 +212,7 @@ protected override MethodProvider[] BuildMethods()
var clientOptions = _clientProvider.EffectiveClientOptions;
if (clientOptions != null)
{
- AppendComplexObjectBinding(body, sectionParam, "Options", "options", clientOptions.Type);
+ AppendComplexObjectBinding(body, sectionParam, GetSettingPropertyName("Options"), "options", clientOptions.Type, "Options");
}
var bindCoreMethod = new MethodProvider(
@@ -229,6 +229,24 @@ protected override MethodProvider[] BuildMethods()
return [bindCoreMethod];
}
+ ///
+ /// Resolves the effective property name to assign to during binding, honoring custom code
+ /// replacements (e.g., a property renamed via [CodeGenMember("OriginalName")]).
+ /// The configuration key used to read the value remains the original generated name.
+ ///
+ private string GetSettingPropertyName(string generatedName)
+ {
+ foreach (var property in CanonicalView.Properties)
+ {
+ if (property.OriginalName == generatedName)
+ {
+ return property.Name;
+ }
+ }
+
+ return generatedName;
+ }
+
///
/// Dispatches to the appropriate binding method based on the property type.
///
@@ -237,8 +255,11 @@ internal static void AppendBindingForProperty(
ParameterProvider sectionParam,
string propName,
string varName,
- CSharpType type)
+ CSharpType type,
+ string? configKey = null)
{
+ configKey ??= propName;
+
// Handle non-framework types (enums, complex objects)
if (!type.IsFrameworkType)
{
@@ -246,11 +267,11 @@ internal static void AppendBindingForProperty(
{
if (type.IsStruct)
{
- AppendEnumBinding(body, sectionParam, propName, varName, type);
+ AppendEnumBinding(body, sectionParam, propName, varName, type, configKey);
}
else
{
- AppendFixedEnumBinding(body, sectionParam, propName, varName, type);
+ AppendFixedEnumBinding(body, sectionParam, propName, varName, type, configKey);
}
}
else if (type.IsStruct && TryGetStructUnderlyingType(type) is { } underlyingType)
@@ -259,24 +280,24 @@ internal static void AppendBindingForProperty(
// Use the constructor's parameter type to pick the correct binding.
if (underlyingType.FrameworkType == typeof(string))
{
- AppendEnumBinding(body, sectionParam, propName, varName, type);
+ AppendEnumBinding(body, sectionParam, propName, varName, type, configKey);
}
else if (underlyingType.FrameworkType == typeof(int) || underlyingType.FrameworkType == typeof(long))
{
- AppendTryParseBinding(body, sectionParam, propName, varName, typeof(int));
+ AppendTryParseBinding(body, sectionParam, propName, varName, typeof(int), configKey);
}
else if (underlyingType.FrameworkType == typeof(float) || underlyingType.FrameworkType == typeof(double))
{
- AppendTryParseBinding(body, sectionParam, propName, varName, typeof(double));
+ AppendTryParseBinding(body, sectionParam, propName, varName, typeof(double), configKey);
}
else
{
- AppendComplexObjectBinding(body, sectionParam, propName, varName, type);
+ AppendComplexObjectBinding(body, sectionParam, propName, varName, type, configKey);
}
}
else
{
- AppendComplexObjectBinding(body, sectionParam, propName, varName, type);
+ AppendComplexObjectBinding(body, sectionParam, propName, varName, type, configKey);
}
return;
}
@@ -284,7 +305,7 @@ internal static void AppendBindingForProperty(
// Handle collection types (string[]/List)
if (type.IsList)
{
- AppendStringListBinding(body, sectionParam, propName, varName, type);
+ AppendStringListBinding(body, sectionParam, propName, varName, type, configKey);
return;
}
@@ -292,39 +313,39 @@ internal static void AppendBindingForProperty(
if (frameworkType == typeof(string))
{
- AppendStringBinding(body, sectionParam, propName, varName);
+ AppendStringBinding(body, sectionParam, propName, varName, configKey);
}
else if (frameworkType == typeof(bool))
{
- AppendTryParseBinding(body, sectionParam, propName, varName, typeof(bool));
+ AppendTryParseBinding(body, sectionParam, propName, varName, typeof(bool), configKey);
}
else if (frameworkType == typeof(int))
{
- AppendTryParseBinding(body, sectionParam, propName, varName, typeof(int));
+ AppendTryParseBinding(body, sectionParam, propName, varName, typeof(int), configKey);
}
else if (frameworkType == typeof(long))
{
- AppendTryParseBinding(body, sectionParam, propName, varName, typeof(long));
+ AppendTryParseBinding(body, sectionParam, propName, varName, typeof(long), configKey);
}
else if (frameworkType == typeof(float))
{
- AppendTryParseBinding(body, sectionParam, propName, varName, typeof(float));
+ AppendTryParseBinding(body, sectionParam, propName, varName, typeof(float), configKey);
}
else if (frameworkType == typeof(double))
{
- AppendTryParseBinding(body, sectionParam, propName, varName, typeof(double));
+ AppendTryParseBinding(body, sectionParam, propName, varName, typeof(double), configKey);
}
else if (frameworkType == typeof(TimeSpan))
{
- AppendTryParseBinding(body, sectionParam, propName, varName, typeof(TimeSpan));
+ AppendTryParseBinding(body, sectionParam, propName, varName, typeof(TimeSpan), configKey);
}
else if (frameworkType == typeof(Uri))
{
- AppendUriTryCreateBinding(body, sectionParam, propName, varName);
+ AppendUriTryCreateBinding(body, sectionParam, propName, varName, configKey);
}
else
{
- AppendComplexObjectBinding(body, sectionParam, propName, varName, type);
+ AppendComplexObjectBinding(body, sectionParam, propName, varName, type, configKey);
}
}
@@ -335,9 +356,10 @@ internal static void AppendStringBinding(
List body,
ParameterProvider sectionParam,
string propName,
- string varName)
+ string varName,
+ string? configKey = null)
{
- body.Add(Declare(varName, new CSharpType(typeof(string), isNullable: true), new IndexerExpression(sectionParam, Literal(propName)), out var valVar));
+ body.Add(Declare(varName, new CSharpType(typeof(string), isNullable: true), new IndexerExpression(sectionParam, Literal(configKey ?? propName)), out var valVar));
var ifStatement = new IfStatement(Not(StringSnippets.IsNullOrEmpty(valVar.As())));
ifStatement.Add(This.Property(propName).Assign(valVar).Terminate());
body.Add(ifStatement);
@@ -351,13 +373,14 @@ internal static void AppendTryParseBinding(
ParameterProvider sectionParam,
string propName,
string varName,
- Type parseType)
+ Type parseType,
+ string? configKey = null)
{
var outDecl = new DeclarationExpression(parseType, varName, out var parsedVar, isOut: true);
var ifStatement = new IfStatement(Static(parseType).Invoke("TryParse",
new ValueExpression[]
{
- new IndexerExpression(sectionParam, Literal(propName)),
+ new IndexerExpression(sectionParam, Literal(configKey ?? propName)),
outDecl
}));
ifStatement.Add(This.Property(propName).Assign(parsedVar).Terminate());
@@ -371,13 +394,14 @@ internal static void AppendUriTryCreateBinding(
List body,
ParameterProvider sectionParam,
string propName,
- string varName)
+ string varName,
+ string? configKey = null)
{
var outUriDecl = new DeclarationExpression(typeof(Uri), varName, out var uriVar, isOut: true);
var ifStatement = new IfStatement(Static(typeof(Uri)).Invoke("TryCreate",
new ValueExpression[]
{
- new IndexerExpression(sectionParam, Literal(propName)),
+ new IndexerExpression(sectionParam, Literal(configKey ?? propName)),
new MemberExpression(typeof(UriKind), nameof(UriKind.Absolute)),
outUriDecl
}));
@@ -394,7 +418,8 @@ internal static void AppendStringListBinding(
ParameterProvider sectionParam,
string propName,
string varName,
- CSharpType type)
+ CSharpType type,
+ string? configKey = null)
{
// Only handle List for now
if (type.Arguments.Count == 0 ||
@@ -405,7 +430,7 @@ internal static void AppendStringListBinding(
}
// IConfigurationSection listSection = section.GetSection("PropName");
- body.Add(Declare((propName + "Section").ToVariableName(), IConfigurationSectionType, sectionParam.Invoke("GetSection", Literal(propName)), out var sectionVar));
+ body.Add(Declare((propName + "Section").ToVariableName(), IConfigurationSectionType, sectionParam.Invoke("GetSection", Literal(configKey ?? propName)), out var sectionVar));
// if (listSection.Exists())
var ifExistsStatement = new IfStatement(sectionVar.Invoke("Exists"));
@@ -437,10 +462,11 @@ internal static void AppendEnumBinding(
ParameterProvider sectionParam,
string propName,
string varName,
- CSharpType type)
+ CSharpType type,
+ string? configKey = null)
{
var decl = Declare(varName, new CSharpType(typeof(string)), out var declVar);
- var ifStatement = new IfStatement(new IndexerExpression(sectionParam, Literal(propName)).Is(decl));
+ var ifStatement = new IfStatement(new IndexerExpression(sectionParam, Literal(configKey ?? propName)).Is(decl));
ifStatement.Add(This.Property(propName).Assign(New.Instance(type, declVar)).Terminate());
body.Add(ifStatement);
}
@@ -453,13 +479,14 @@ internal static void AppendFixedEnumBinding(
ParameterProvider sectionParam,
string propName,
string varName,
- CSharpType type)
+ CSharpType type,
+ string? configKey = null)
{
var outDecl = new DeclarationExpression(type, varName, out var parsedVar, isOut: true);
var ifStatement = new IfStatement(Static(typeof(Enum)).Invoke("TryParse",
new ValueExpression[]
{
- new IndexerExpression(sectionParam, Literal(propName)),
+ new IndexerExpression(sectionParam, Literal(configKey ?? propName)),
outDecl
}));
ifStatement.Add(This.Property(propName).Assign(parsedVar).Terminate());
@@ -475,10 +502,11 @@ internal static void AppendComplexObjectBinding(
ParameterProvider sectionParam,
string propName,
string varName,
- CSharpType type)
+ CSharpType type,
+ string? configKey = null)
{
// IConfigurationSection {name}Section = section.GetSection("PropName");
- body.Add(Declare((propName + "Section").ToVariableName(), IConfigurationSectionType, sectionParam.Invoke("GetSection", Literal(propName)), out var sectionVar));
+ body.Add(Declare((propName + "Section").ToVariableName(), IConfigurationSectionType, sectionParam.Invoke("GetSection", Literal(configKey ?? propName)), out var sectionVar));
// if ({name}Section.Exists()) { PropName = new TypeName({name}Section); }
var ifExistsStatement = new IfStatement(sectionVar.Invoke("Exists"));
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientSettingsProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientSettingsProviderTests.cs
index 4cf2db505d6..cf258937615 100644
--- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientSettingsProviderTests.cs
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientSettingsProviderTests.cs
@@ -1141,6 +1141,46 @@ await MockHelpers.LoadMockGeneratorAsync(
"BindCore should bind the non-credential parameter 'TenantId'");
}
+ [Test]
+ public async Task TestBindCoreMethod_HonorsCustomPropertyRename()
+ {
+ // Custom code renames the generated 'Endpoint' settings property to 'ServiceUri'
+ // via [CodeGenMember("Endpoint")]. BindCore should assign to the renamed property
+ // while still reading from the original configuration key.
+ await MockHelpers.LoadMockGeneratorAsync(
+ compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());
+
+ var inputParameters = new[]
+ {
+ InputFactory.EndpointParameter(
+ "endpoint",
+ InputPrimitiveType.Url,
+ scope: InputParameterScope.Client,
+ isEndpoint: true)
+ };
+ var client = InputFactory.Client("TestClient", clientNamespace: "SampleNamespace", parameters: inputParameters);
+ var clientProvider = ScmCodeModelGenerator.Instance.TypeFactory.CreateClient(client);
+ Assert.IsNotNull(clientProvider);
+
+ var settingsProvider = clientProvider!.ClientSettings;
+ Assert.IsNotNull(settingsProvider);
+ Assert.IsNotNull(settingsProvider!.CustomCodeView,
+ "CustomCodeView should be available from the compilation");
+
+ // The generated 'Endpoint' property should be replaced by the custom 'ServiceUri' property.
+ Assert.IsNull(settingsProvider.Properties.FirstOrDefault(p => p.Name == "Endpoint"),
+ "Generated 'Endpoint' property should be replaced by the custom code rename");
+
+ var bindCoreMethod = settingsProvider.Methods.FirstOrDefault(m => m.Signature.Name == "BindCore");
+ Assert.IsNotNull(bindCoreMethod);
+
+ // Validate full generated output: BindCore should assign to the renamed 'ServiceUri'
+ // property while still reading from the original 'Endpoint' configuration key.
+ var writer = new TypeProviderWriter(settingsProvider);
+ var file = writer.Write();
+ Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
+ }
+
[Test]
public async Task TestGeneratedSettings_WithCustomizedBindCore()
{
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ClientSettingsProviderTests/TestBindCoreMethod_HonorsCustomPropertyRename.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ClientSettingsProviderTests/TestBindCoreMethod_HonorsCustomPropertyRename.cs
new file mode 100644
index 00000000000..93ac5b7e27b
--- /dev/null
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ClientSettingsProviderTests/TestBindCoreMethod_HonorsCustomPropertyRename.cs
@@ -0,0 +1,30 @@
+//
+
+#nullable disable
+
+using System;
+using System.ClientModel.Primitives;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.Extensions.Configuration;
+
+namespace SampleNamespace
+{
+ [global::System.Diagnostics.CodeAnalysis.ExperimentalAttribute("SCME0002")]
+ public partial class TestClientSettings : global::System.ClientModel.Primitives.ClientSettings
+ {
+ public global::SampleNamespace.TestClientOptions Options { get; set; }
+
+ protected override void BindCore(global::Microsoft.Extensions.Configuration.IConfigurationSection section)
+ {
+ if (global::System.Uri.TryCreate(section["Endpoint"], global::System.UriKind.Absolute, out global::System.Uri endpoint))
+ {
+ this.ServiceUri = endpoint;
+ }
+ global::Microsoft.Extensions.Configuration.IConfigurationSection optionsSection = section.GetSection("Options");
+ if (optionsSection.Exists())
+ {
+ this.Options = new global::SampleNamespace.TestClientOptions(optionsSection);
+ }
+ }
+ }
+}
diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ClientSettingsProviderTests/TestBindCoreMethod_HonorsCustomPropertyRename/TestClientSettings.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ClientSettingsProviderTests/TestBindCoreMethod_HonorsCustomPropertyRename/TestClientSettings.cs
new file mode 100644
index 00000000000..313c84ba6d1
--- /dev/null
+++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/TestData/ClientSettingsProviderTests/TestBindCoreMethod_HonorsCustomPropertyRename/TestClientSettings.cs
@@ -0,0 +1,13 @@
+#nullable disable
+
+using System;
+using Microsoft.TypeSpec.Generator.Customizations;
+
+namespace SampleNamespace
+{
+ public partial class TestClientSettings
+ {
+ [CodeGenMember("Endpoint")]
+ public Uri ServiceUri { get; set; }
+ }
+}