Skip to content

Commit 8ce87d2

Browse files
authored
API for setting dynamic package data for the UserAgent header per HttpMessage (Azure#27079)
* API for SetUserAgentString
1 parent 11e1eb1 commit 8ce87d2

14 files changed

+264
-47
lines changed

sdk/core/Azure.Core/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
- Added `AddClassifier` methods to `RequestContext`. These methods allow callers to change the response classification behavior for a given method invocation.
88
- Added type `RequestOptions` to the `Azure` namespace and made `RequestContext` a subclass of `RequestOptions`. This enables `RequestOptions` to be exposed in methods that take `CancellationToken` without causing confusion regarding which cancellation token will take effect.
9+
- Added the `SetUserAgentString` extension method to `HttpMessage` accepting a `UserAgentValue` and an optional application Id string. This allows assembly specific user agent header information to be set with proper formatting on a per-message basis.
910

1011
### Breaking Changes
1112

sdk/core/Azure.Core/api/Azure.Core.net461.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,10 @@ public void Dispose() { }
891891
public override void Process(Azure.Core.HttpMessage message) { }
892892
public override System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message) { throw null; }
893893
}
894+
public static partial class HttpMessageExtensions
895+
{
896+
public static void SetUserAgentString(this Azure.Core.HttpMessage message, Azure.Core.Pipeline.UserAgentValue userAgentValue) { }
897+
}
894898
public partial class HttpPipeline
895899
{
896900
public HttpPipeline(Azure.Core.Pipeline.HttpPipelineTransport transport, Azure.Core.Pipeline.HttpPipelinePolicy[]? policies = null, Azure.Core.ResponseClassifier? responseClassifier = null) { }
@@ -947,6 +951,12 @@ public ServerCertificateCustomValidationArgs(System.Security.Cryptography.X509Ce
947951
public System.Security.Cryptography.X509Certificates.X509Chain? CertificateAuthorityChain { get { throw null; } }
948952
public System.Net.Security.SslPolicyErrors SslPolicyErrors { get { throw null; } }
949953
}
954+
public partial class UserAgentValue
955+
{
956+
public UserAgentValue(System.Type type, string? applicationId = null) { }
957+
public static Azure.Core.Pipeline.UserAgentValue FromType<T>(string? applicationId = null) { throw null; }
958+
public override string ToString() { throw null; }
959+
}
950960
}
951961
namespace Azure.Core.Serialization
952962
{

sdk/core/Azure.Core/api/Azure.Core.net5.0.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,10 @@ public void Dispose() { }
891891
public override void Process(Azure.Core.HttpMessage message) { }
892892
public override System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message) { throw null; }
893893
}
894+
public static partial class HttpMessageExtensions
895+
{
896+
public static void SetUserAgentString(this Azure.Core.HttpMessage message, Azure.Core.Pipeline.UserAgentValue userAgentValue) { }
897+
}
894898
public partial class HttpPipeline
895899
{
896900
public HttpPipeline(Azure.Core.Pipeline.HttpPipelineTransport transport, Azure.Core.Pipeline.HttpPipelinePolicy[]? policies = null, Azure.Core.ResponseClassifier? responseClassifier = null) { }
@@ -947,6 +951,12 @@ public ServerCertificateCustomValidationArgs(System.Security.Cryptography.X509Ce
947951
public System.Security.Cryptography.X509Certificates.X509Chain? CertificateAuthorityChain { get { throw null; } }
948952
public System.Net.Security.SslPolicyErrors SslPolicyErrors { get { throw null; } }
949953
}
954+
public partial class UserAgentValue
955+
{
956+
public UserAgentValue(System.Type type, string? applicationId = null) { }
957+
public static Azure.Core.Pipeline.UserAgentValue FromType<T>(string? applicationId = null) { throw null; }
958+
public override string ToString() { throw null; }
959+
}
950960
}
951961
namespace Azure.Core.Serialization
952962
{

sdk/core/Azure.Core/api/Azure.Core.netcoreapp2.1.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,10 @@ public void Dispose() { }
891891
public override void Process(Azure.Core.HttpMessage message) { }
892892
public override System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message) { throw null; }
893893
}
894+
public static partial class HttpMessageExtensions
895+
{
896+
public static void SetUserAgentString(this Azure.Core.HttpMessage message, Azure.Core.Pipeline.UserAgentValue userAgentValue) { }
897+
}
894898
public partial class HttpPipeline
895899
{
896900
public HttpPipeline(Azure.Core.Pipeline.HttpPipelineTransport transport, Azure.Core.Pipeline.HttpPipelinePolicy[]? policies = null, Azure.Core.ResponseClassifier? responseClassifier = null) { }
@@ -947,6 +951,12 @@ public ServerCertificateCustomValidationArgs(System.Security.Cryptography.X509Ce
947951
public System.Security.Cryptography.X509Certificates.X509Chain? CertificateAuthorityChain { get { throw null; } }
948952
public System.Net.Security.SslPolicyErrors SslPolicyErrors { get { throw null; } }
949953
}
954+
public partial class UserAgentValue
955+
{
956+
public UserAgentValue(System.Type type, string? applicationId = null) { }
957+
public static Azure.Core.Pipeline.UserAgentValue FromType<T>(string? applicationId = null) { throw null; }
958+
public override string ToString() { throw null; }
959+
}
950960
}
951961
namespace Azure.Core.Serialization
952962
{

sdk/core/Azure.Core/api/Azure.Core.netstandard2.0.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,10 @@ public void Dispose() { }
891891
public override void Process(Azure.Core.HttpMessage message) { }
892892
public override System.Threading.Tasks.ValueTask ProcessAsync(Azure.Core.HttpMessage message) { throw null; }
893893
}
894+
public static partial class HttpMessageExtensions
895+
{
896+
public static void SetUserAgentString(this Azure.Core.HttpMessage message, Azure.Core.Pipeline.UserAgentValue userAgentValue) { }
897+
}
894898
public partial class HttpPipeline
895899
{
896900
public HttpPipeline(Azure.Core.Pipeline.HttpPipelineTransport transport, Azure.Core.Pipeline.HttpPipelinePolicy[]? policies = null, Azure.Core.ResponseClassifier? responseClassifier = null) { }
@@ -947,6 +951,12 @@ public ServerCertificateCustomValidationArgs(System.Security.Cryptography.X509Ce
947951
public System.Security.Cryptography.X509Certificates.X509Chain? CertificateAuthorityChain { get { throw null; } }
948952
public System.Net.Security.SslPolicyErrors SslPolicyErrors { get { throw null; } }
949953
}
954+
public partial class UserAgentValue
955+
{
956+
public UserAgentValue(System.Type type, string? applicationId = null) { }
957+
public static Azure.Core.Pipeline.UserAgentValue FromType<T>(string? applicationId = null) { throw null; }
958+
public override string ToString() { throw null; }
959+
}
950960
}
951961
namespace Azure.Core.Serialization
952962
{

sdk/core/Azure.Core/src/HttpMessage.cs

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ namespace Azure.Core
1414
/// </summary>
1515
public sealed class HttpMessage : IDisposable
1616
{
17-
private Dictionary<string, object>? _properties;
17+
/// <summary>
18+
/// This dictionary is keyed with <c>Type</c> for a couple of reasons. Primarily, it allows values to be stored such that even if the accessor methods
19+
/// become public, storing values keyed by internal types make them inaccessible to other assemblies. This protects internal values from being overwritten
20+
/// by external code. See the <see cref="UserAgentValue"/> and <see cref="UserAgentValueKey"/> types for an example of this usage.
21+
/// </summary>
22+
private Dictionary<Type, object>? _typeProperties;
1823

1924
private Response? _response;
2025

@@ -112,7 +117,12 @@ internal void ApplyRequestContext(RequestContext? context, CoreResponseClassifie
112117
public bool TryGetProperty(string name, out object? value)
113118
{
114119
value = null;
115-
return _properties?.TryGetValue(name, out value) == true;
120+
if (_typeProperties == null || !_typeProperties.TryGetValue(typeof(MessagePropertyKey), out var rawValue))
121+
{
122+
return false;
123+
}
124+
var properties = (Dictionary<string, object>)rawValue!;
125+
return properties.TryGetValue(name, out value);
116126
}
117127

118128
/// <summary>
@@ -122,9 +132,42 @@ public bool TryGetProperty(string name, out object? value)
122132
/// <param name="value">The property value.</param>
123133
public void SetProperty(string name, object value)
124134
{
125-
_properties ??= new Dictionary<string, object>();
135+
_typeProperties ??= new Dictionary<Type, object>();
136+
Dictionary<string, object> properties;
137+
if (!_typeProperties.TryGetValue(typeof(MessagePropertyKey), out var rawValue))
138+
{
139+
properties = new Dictionary<string, object>();
140+
_typeProperties[typeof(MessagePropertyKey)] = properties;
141+
}
142+
else
143+
{
144+
properties = (Dictionary<string, object>)rawValue!;
145+
}
146+
properties[name] = value;
147+
}
148+
149+
/// <summary>
150+
/// Gets a property that is stored with this <see cref="HttpMessage"/> instance and can be used for modifying pipeline behavior.
151+
/// </summary>
152+
/// <param name="type">The property type.</param>
153+
/// <param name="value">The property value.</param>
154+
/// <returns><c>true</c> if property exists, otherwise. <c>false</c>.</returns>
155+
internal bool TryGetInternalProperty(Type type, out object? value)
156+
{
157+
value = null;
158+
return _typeProperties?.TryGetValue(type, out value) == true;
159+
}
126160

127-
_properties[name] = value;
161+
/// <summary>
162+
/// Sets a property that is stored with this <see cref="HttpMessage"/> instance and can be used for modifying pipeline behavior.
163+
/// Internal properties can be keyed with internal types to prevent external code from overwriting these values.
164+
/// </summary>
165+
/// <param name="type">The key for the value.</param>
166+
/// <param name="value">The property value.</param>
167+
internal void SetInternalProperty(Type type, object value)
168+
{
169+
_typeProperties ??= new Dictionary<Type, object>();
170+
_typeProperties[type] = value;
128171
}
129172

130173
/// <summary>
@@ -204,5 +247,10 @@ public override long Position
204247
set => throw CreateException();
205248
}
206249
}
250+
251+
/// <summary>
252+
/// Exists as a private key entry into the <see cref="HttpMessage._typeProperties"/> dictionary for stashing string keyed entries in the Type keyed dictionary.
253+
/// </summary>
254+
private class MessagePropertyKey {}
207255
}
208256
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
namespace Azure.Core.Pipeline
5+
{
6+
/// <summary>
7+
/// Extension methods for <see cref="HttpMessage"/>.
8+
/// </summary>
9+
public static class HttpMessageExtensions
10+
{
11+
/// <summary>
12+
/// Sets the package name and version portion of the UserAgent telemetry value.
13+
/// Note: If <see cref="DiagnosticsOptions.IsTelemetryEnabled"/> is false, this value is never used.
14+
/// </summary>
15+
/// <param name="message">The <see cref="HttpMessage"/>.</param>
16+
/// <param name="userAgentValue">The <see cref="SetUserAgentString"/>.</param>
17+
public static void SetUserAgentString(this HttpMessage message, UserAgentValue userAgentValue)
18+
{
19+
message.SetInternalProperty(typeof(UserAgentValueKey), userAgentValue.ToString());
20+
}
21+
}
22+
}

sdk/core/Azure.Core/src/Pipeline/HttpPipelineBuilder.cs

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -176,31 +176,8 @@ void AddCustomerPolicies(HttpPipelinePosition position)
176176
// internal for testing
177177
internal static TelemetryPolicy CreateTelemetryPolicy(ClientOptions options)
178178
{
179-
const string PackagePrefix = "Azure.";
180-
181-
Assembly clientAssembly = options.GetType().Assembly!;
182-
183-
AssemblyInformationalVersionAttribute? versionAttribute = clientAssembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
184-
if (versionAttribute == null)
185-
{
186-
throw new InvalidOperationException($"{nameof(AssemblyInformationalVersionAttribute)} is required on client SDK assembly '{clientAssembly.FullName}' (inferred from the use of options type '{options.GetType().FullName}').");
187-
}
188-
189-
string version = versionAttribute.InformationalVersion;
190-
191-
string assemblyName = clientAssembly.GetName().Name!;
192-
if (assemblyName.StartsWith(PackagePrefix, StringComparison.Ordinal))
193-
{
194-
assemblyName = assemblyName.Substring(PackagePrefix.Length);
195-
}
196-
197-
int hashSeparator = version.IndexOfOrdinal('+');
198-
if (hashSeparator != -1)
199-
{
200-
version = version.Substring(0, hashSeparator);
201-
}
202-
203-
return new TelemetryPolicy(assemblyName, version, options.Diagnostics.ApplicationId);
179+
var userAgentValue = new UserAgentValue(options.GetType(), options.Diagnostics.ApplicationId);
180+
return new TelemetryPolicy(userAgentValue);
204181
}
205182
}
206183
}
Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,27 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
using System.Runtime.InteropServices;
5-
64
namespace Azure.Core.Pipeline
75
{
86
internal class TelemetryPolicy : HttpPipelineSynchronousPolicy
97
{
10-
private readonly string _header;
8+
private readonly string _defaultHeader;
119

12-
public TelemetryPolicy(string componentName, string componentVersion, string? applicationId)
10+
public TelemetryPolicy(UserAgentValue userAgentValue)
11+
{
12+
_defaultHeader = userAgentValue.ToString();
13+
}
14+
15+
public override void OnSendingRequest(HttpMessage message)
1316
{
14-
var platformInformation = $"({RuntimeInformation.FrameworkDescription}; {RuntimeInformation.OSDescription})";
15-
if (applicationId != null)
17+
if (message.TryGetInternalProperty(typeof(UserAgentValueKey), out var userAgent))
1618
{
17-
_header = $"{applicationId} azsdk-net-{componentName}/{componentVersion} {platformInformation}";
19+
message.Request.Headers.Add(HttpHeader.Names.UserAgent, ((string)userAgent!));
1820
}
1921
else
2022
{
21-
_header = $"azsdk-net-{componentName}/{componentVersion} {platformInformation}";
23+
message.Request.Headers.Add(HttpHeader.Names.UserAgent, _defaultHeader);
2224
}
2325
}
24-
25-
public override void OnSendingRequest(HttpMessage message)
26-
{
27-
message.Request.Headers.Add(HttpHeader.Names.UserAgent, _header);
28-
}
2926
}
3027
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
namespace Azure.Core.Pipeline
5+
{
6+
/// <summary>
7+
/// Class that serves as the key for <see cref="UserAgentValue"/> UserAgent strings on <see cref="HttpMessage"/>.
8+
/// </summary>
9+
internal class UserAgentValueKey { }
10+
}

0 commit comments

Comments
 (0)