Skip to content

Commit b2b6044

Browse files
Enable UserAgentPolicy in generated client libraries (#8983)
## Enable UserAgentPolicy in Generated Libraries ### Completed Work - [x] Research UserAgentPolicy API and usage patterns - [x] Modify `ClientProvider` to create UserAgentPolicy through abstraction layer - [x] Verify all tests pass successfully - [x] Address feedback: Make UserAgentPolicy a virtual method with default null value - [x] Address feedback: Use nameof(System.Type.Assembly) for property reference - [x] Address feedback: Remove optional null parameter from UserAgentPolicy constructor - [x] Address feedback: Refactor variable names for clarity ### Implementation Details The UserAgentPolicy is now automatically added to the pipeline for all generated clients: - Added `UserAgentPolicy` as a **virtual method** in `ClientPipelineApi` with default return value of `null` - Implemented in `ClientPipelineProvider` to return `new UserAgentPolicy(typeof(ClientType).Assembly)` - `ClientProvider` builds a list of per-retry policies, conditionally adding UserAgentPolicy first if not null, then auth policies - Uses `nameof(System.Type.Assembly)` for compile-time safety - Omits optional applicationId parameter (cleaner code) - Policy is added as the first item in the `perRetryPolicies` array, before authentication policies - Works for clients with no auth, API key auth, and OAuth2 auth - No configuration option needed - always enabled by default - **Optional via abstractions** - implementations can override the virtual method to return null and disable UserAgentPolicy ### Code Structure The refactored code uses a `perRetryPoliciesList` that is built up conditionally: 1. UserAgentPolicy is added first (if not null) 2. Authentication policies are added based on the auth type 3. The list is converted to an array for the pipeline This makes it clear that all policies in the list are per-retry policies, not just auth-specific policies. ### Generated Code Example ```csharp Pipeline = ClientPipeline.Create( options, Array.Empty<PipelinePolicy>(), new PipelinePolicy[] { new UserAgentPolicy(typeof(SampleClient).Assembly), ApiKeyAuthenticationPolicy.CreateHeaderApiKeyPolicy(_keyCredential, AuthorizationHeader) }, Array.Empty<PipelinePolicy>()); ``` - Fixes #7863 <!-- START COPILOT CODING AGENT SUFFIX --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>Enable UserAgentPolicy once SCM ships</issue_title> > <issue_description>Once Azure/azure-sdk-for-net#51113 ships, we should integrate the UserAgentPolicy into generated libraries. We should add an option so that users can opt out of it, but this can be on by default so users don't have to know to turn it on. We can document this behavior clearly.</issue_description> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > <comment_new><author>@JoshLove-msft</author><body> > For Azure, we would just turn it off as the Azure.Core pipeline has its own UserAgentPolicy.</body></comment_new> > <comment_new><author>@JoshLove-msft</author><body> > > For Azure, we would just turn it off as the Azure.Core pipeline has its own UserAgentPolicy. > > Actually, we don't need to add config for this until we hear feedback that users don't want it on. For Azure, it will not use the same pipeline so there is no need to turn it off.</body></comment_new> > </comments> > </details> - Fixes #7863 <!-- START COPILOT CODING AGENT TIPS --> --- 💡 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](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: JoshLove-msft <[email protected]>
1 parent 2f78f49 commit b2b6044

16 files changed

+31
-17
lines changed

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/Abstractions/ClientPipelineApi.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public abstract MethodBodyStatement[] CreateMessage(
3636

3737
public abstract ValueExpression KeyAuthorizationPolicy(ValueExpression credential, ValueExpression headerName, ValueExpression? keyPrefix = null);
3838
public abstract ValueExpression TokenAuthorizationPolicy(ValueExpression credential, ValueExpression scopes);
39+
public virtual ValueExpression? UserAgentPolicy(CSharpType clientType) => null;
3940
public abstract ClientPipelineApi FromExpression(ValueExpression expression);
4041
public abstract ClientPipelineApi ToExpression();
4142
}

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientPipelineProvider.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ public override ValueExpression KeyAuthorizationPolicy(ValueExpression credentia
5858
public override ValueExpression TokenAuthorizationPolicy(ValueExpression credential, ValueExpression scopes)
5959
=> New.Instance(typeof(BearerTokenPolicy), credential, scopes);
6060

61+
public override ValueExpression? UserAgentPolicy(CSharpType clientType)
62+
=> New.Instance(typeof(System.ClientModel.Primitives.UserAgentPolicy), [TypeOf(clientType).Property(nameof(System.Type.Assembly))]);
63+
6164
public override ClientPipelineApi ToExpression() => this;
6265

6366
public override MethodBodyStatement[] SendMessage(HttpMessageApi message, HttpRequestOptionsApi options)

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ClientProvider.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -570,18 +570,28 @@ private MethodBodyStatement[] BuildPrimaryConstructorBody(IReadOnlyList<Paramete
570570
}
571571

572572
// handle pipeline property
573+
var userAgentPolicy = This.ToApi<ClientPipelineApi>().UserAgentPolicy(Type);
574+
575+
List<ValueExpression> perRetryPoliciesList = [];
576+
if (userAgentPolicy != null)
577+
{
578+
perRetryPoliciesList.Add(userAgentPolicy);
579+
}
580+
573581
ValueExpression perRetryPolicies;
574582
switch (authFields)
575583
{
576584
case ApiKeyFields keyAuthFields:
577585
ValueExpression? keyPrefixExpression = keyAuthFields.AuthorizationApiKeyPrefixField != null ? (ValueExpression)keyAuthFields.AuthorizationApiKeyPrefixField : null;
578-
perRetryPolicies = New.Array(ScmCodeModelGenerator.Instance.TypeFactory.ClientPipelineApi.PipelinePolicyType, isInline: true, This.ToApi<ClientPipelineApi>().KeyAuthorizationPolicy(keyAuthFields.AuthField, keyAuthFields.AuthorizationHeaderField, keyPrefixExpression));
586+
perRetryPoliciesList.Add(This.ToApi<ClientPipelineApi>().KeyAuthorizationPolicy(keyAuthFields.AuthField, keyAuthFields.AuthorizationHeaderField, keyPrefixExpression));
587+
perRetryPolicies = New.Array(ScmCodeModelGenerator.Instance.TypeFactory.ClientPipelineApi.PipelinePolicyType, isInline: true, [.. perRetryPoliciesList]);
579588
break;
580589
case OAuth2Fields oauth2AuthFields:
581-
perRetryPolicies = New.Array(ScmCodeModelGenerator.Instance.TypeFactory.ClientPipelineApi.PipelinePolicyType, isInline: true, This.ToApi<ClientPipelineApi>().TokenAuthorizationPolicy(oauth2AuthFields.AuthField, oauth2AuthFields.AuthorizationScopesField));
590+
perRetryPoliciesList.Add(This.ToApi<ClientPipelineApi>().TokenAuthorizationPolicy(oauth2AuthFields.AuthField, oauth2AuthFields.AuthorizationScopesField));
591+
perRetryPolicies = New.Array(ScmCodeModelGenerator.Instance.TypeFactory.ClientPipelineApi.PipelinePolicyType, isInline: true, [.. perRetryPoliciesList]);
582592
break;
583593
default:
584-
perRetryPolicies = New.Array(ScmCodeModelGenerator.Instance.TypeFactory.ClientPipelineApi.PipelinePolicyType);
594+
perRetryPolicies = New.Array(ScmCodeModelGenerator.Instance.TypeFactory.ClientPipelineApi.PipelinePolicyType, isInline: true, [.. perRetryPoliciesList]);
585595
break;
586596
}
587597

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,False,False,0).cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
options ??= new global::Sample.TestClientOptions();
44

55
_endpoint = endpoint;
6-
Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>());
6+
Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>());

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,False,True,0).cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55

66
_endpoint = endpoint;
77
_tokenProvider = tokenProvider;
8-
Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.BearerTokenPolicy(_tokenProvider, _flows) }, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>());
8+
Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), new global::System.ClientModel.Primitives.BearerTokenPolicy(_tokenProvider, _flows) }, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>());

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,True,False,0).cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55

66
_endpoint = endpoint;
77
_keyCredential = credential;
8-
Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), new global::System.ClientModel.Primitives.PipelinePolicy[] { global::System.ClientModel.Primitives.ApiKeyAuthenticationPolicy.CreateHeaderApiKeyPolicy(_keyCredential, AuthorizationHeader) }, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>());
8+
Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), global::System.ClientModel.Primitives.ApiKeyAuthenticationPolicy.CreateHeaderApiKeyPolicy(_keyCredential, AuthorizationHeader) }, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>());

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,True,True,0).cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55

66
_endpoint = endpoint;
77
_keyCredential = credential;
8-
Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), new global::System.ClientModel.Primitives.PipelinePolicy[] { global::System.ClientModel.Primitives.ApiKeyAuthenticationPolicy.CreateHeaderApiKeyPolicy(_keyCredential, AuthorizationHeader) }, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>());
8+
Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), global::System.ClientModel.Primitives.ApiKeyAuthenticationPolicy.CreateHeaderApiKeyPolicy(_keyCredential, AuthorizationHeader) }, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>());

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithDefault,True,True,1).cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55

66
_endpoint = endpoint;
77
_tokenProvider = tokenProvider;
8-
Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.BearerTokenPolicy(_tokenProvider, _flows) }, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>());
8+
Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), new global::System.ClientModel.Primitives.BearerTokenPolicy(_tokenProvider, _flows) }, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>());

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,False,False,0).cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
options ??= new global::Sample.TestClientOptions();
44

55
_endpoint = endpoint;
6-
Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>());
6+
Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly) }, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>());

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ClientProviders/TestData/ClientProviderTests/TestBuildConstructors_PrimaryConstructor(WithRequired,False,True,0).cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55

66
_endpoint = endpoint;
77
_tokenProvider = tokenProvider;
8-
Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.BearerTokenPolicy(_tokenProvider, _flows) }, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>());
8+
Pipeline = global::System.ClientModel.Primitives.ClientPipeline.Create(options, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>(), new global::System.ClientModel.Primitives.PipelinePolicy[] { new global::System.ClientModel.Primitives.UserAgentPolicy(typeof(global::Sample.TestClient).Assembly), new global::System.ClientModel.Primitives.BearerTokenPolicy(_tokenProvider, _flows) }, Array.Empty<global::System.ClientModel.Primitives.PipelinePolicy>());

0 commit comments

Comments
 (0)