Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ internal PipelineMessage CreateGetWidgetMetricsRequest(string day, RequestOption
{
ClientUriBuilder uri = new ClientUriBuilder();
uri.Reset(_endpoint);
uri.AppendPath("/metrics/widgets/daysOfWeek/", false);
uri.AppendPath("/metrics/", false);
uri.AppendPath(_metricsNamespace, true);
uri.AppendPath("/widgets/daysOfWeek/", false);
uri.AppendPath(day, true);
PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200);
PipelineRequest request = message.Request;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace SampleTypeSpec
public partial class Metrics
{
private readonly Uri _endpoint;
private readonly string _metricsNamespace;

/// <summary> Initializes a new instance of Metrics for mocking. </summary>
protected Metrics()
Expand All @@ -23,30 +24,38 @@ protected Metrics()
/// <summary> Initializes a new instance of Metrics. </summary>
/// <param name="pipeline"> The HTTP pipeline for sending and receiving REST requests and responses. </param>
/// <param name="endpoint"> Service endpoint. </param>
internal Metrics(ClientPipeline pipeline, Uri endpoint)
/// <param name="metricsNamespace"></param>
internal Metrics(ClientPipeline pipeline, Uri endpoint, string metricsNamespace)
{
_endpoint = endpoint;
Pipeline = pipeline;
_metricsNamespace = metricsNamespace;
}

/// <summary> Initializes a new instance of Metrics. </summary>
/// <param name="endpoint"> Service endpoint. </param>
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/> is null. </exception>
public Metrics(Uri endpoint) : this(endpoint, new SampleTypeSpecClientOptions())
/// <param name="metricsNamespace"></param>
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/> or <paramref name="metricsNamespace"/> is null. </exception>
/// <exception cref="ArgumentException"> <paramref name="metricsNamespace"/> is an empty string, and was expected to be non-empty. </exception>
public Metrics(Uri endpoint, string metricsNamespace) : this(endpoint, metricsNamespace, new SampleTypeSpecClientOptions())
{
}

/// <summary> Initializes a new instance of Metrics. </summary>
/// <param name="endpoint"> Service endpoint. </param>
/// <param name="metricsNamespace"></param>
/// <param name="options"> The options for configuring the client. </param>
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/> is null. </exception>
public Metrics(Uri endpoint, SampleTypeSpecClientOptions options)
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/> or <paramref name="metricsNamespace"/> is null. </exception>
/// <exception cref="ArgumentException"> <paramref name="metricsNamespace"/> is an empty string, and was expected to be non-empty. </exception>
public Metrics(Uri endpoint, string metricsNamespace, SampleTypeSpecClientOptions options)
{
Argument.AssertNotNull(endpoint, nameof(endpoint));
Argument.AssertNotNullOrEmpty(metricsNamespace, nameof(metricsNamespace));

options ??= new SampleTypeSpecClientOptions();

_endpoint = endpoint;
_metricsNamespace = metricsNamespace;
Pipeline = ClientPipeline.Create(options, Array.Empty<PipelinePolicy>(), new PipelinePolicy[] { new UserAgentPolicy(typeof(Metrics).Assembly) }, Array.Empty<PipelinePolicy>());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public partial class SampleTypeSpecClient
}
};
private readonly string _apiVersion;
private readonly string _metricsNamespace;
private AnimalOperations _cachedAnimalOperations;
private PetOperations _cachedPetOperations;
private DogOperations _cachedDogOperations;
Expand All @@ -45,51 +46,63 @@ protected SampleTypeSpecClient()

/// <summary> Initializes a new instance of SampleTypeSpecClient. </summary>
/// <param name="endpoint"> Service endpoint. </param>
/// <param name="metricsNamespace"></param>
/// <param name="credential"> A credential used to authenticate to the service. </param>
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/> or <paramref name="credential"/> is null. </exception>
public SampleTypeSpecClient(Uri endpoint, ApiKeyCredential credential) : this(endpoint, credential, new SampleTypeSpecClientOptions())
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/>, <paramref name="metricsNamespace"/> or <paramref name="credential"/> is null. </exception>
/// <exception cref="ArgumentException"> <paramref name="metricsNamespace"/> is an empty string, and was expected to be non-empty. </exception>
public SampleTypeSpecClient(Uri endpoint, string metricsNamespace, ApiKeyCredential credential) : this(endpoint, metricsNamespace, credential, new SampleTypeSpecClientOptions())
Comment thread
JoshLove-msft marked this conversation as resolved.
{
}

/// <summary> Initializes a new instance of SampleTypeSpecClient. </summary>
/// <param name="endpoint"> Service endpoint. </param>
/// <param name="metricsNamespace"></param>
/// <param name="tokenProvider"> A credential provider used to authenticate to the service. </param>
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/> or <paramref name="tokenProvider"/> is null. </exception>
public SampleTypeSpecClient(Uri endpoint, AuthenticationTokenProvider tokenProvider) : this(endpoint, tokenProvider, new SampleTypeSpecClientOptions())
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/>, <paramref name="metricsNamespace"/> or <paramref name="tokenProvider"/> is null. </exception>
/// <exception cref="ArgumentException"> <paramref name="metricsNamespace"/> is an empty string, and was expected to be non-empty. </exception>
public SampleTypeSpecClient(Uri endpoint, string metricsNamespace, AuthenticationTokenProvider tokenProvider) : this(endpoint, metricsNamespace, tokenProvider, new SampleTypeSpecClientOptions())
{
}

/// <summary> Initializes a new instance of SampleTypeSpecClient. </summary>
/// <param name="endpoint"> Service endpoint. </param>
/// <param name="metricsNamespace"></param>
/// <param name="credential"> A credential used to authenticate to the service. </param>
/// <param name="options"> The options for configuring the client. </param>
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/> or <paramref name="credential"/> is null. </exception>
public SampleTypeSpecClient(Uri endpoint, ApiKeyCredential credential, SampleTypeSpecClientOptions options)
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/>, <paramref name="metricsNamespace"/> or <paramref name="credential"/> is null. </exception>
/// <exception cref="ArgumentException"> <paramref name="metricsNamespace"/> is an empty string, and was expected to be non-empty. </exception>
public SampleTypeSpecClient(Uri endpoint, string metricsNamespace, ApiKeyCredential credential, SampleTypeSpecClientOptions options)
{
Argument.AssertNotNull(endpoint, nameof(endpoint));
Argument.AssertNotNullOrEmpty(metricsNamespace, nameof(metricsNamespace));
Argument.AssertNotNull(credential, nameof(credential));

options ??= new SampleTypeSpecClientOptions();

_endpoint = endpoint;
_metricsNamespace = metricsNamespace;
_keyCredential = credential;
Pipeline = ClientPipeline.Create(options, Array.Empty<PipelinePolicy>(), new PipelinePolicy[] { new UserAgentPolicy(typeof(SampleTypeSpecClient).Assembly), ApiKeyAuthenticationPolicy.CreateHeaderApiKeyPolicy(_keyCredential, AuthorizationHeader) }, Array.Empty<PipelinePolicy>());
_apiVersion = options.Version;
}

/// <summary> Initializes a new instance of SampleTypeSpecClient. </summary>
/// <param name="endpoint"> Service endpoint. </param>
/// <param name="metricsNamespace"></param>
/// <param name="tokenProvider"> A credential provider used to authenticate to the service. </param>
/// <param name="options"> The options for configuring the client. </param>
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/> or <paramref name="tokenProvider"/> is null. </exception>
public SampleTypeSpecClient(Uri endpoint, AuthenticationTokenProvider tokenProvider, SampleTypeSpecClientOptions options)
/// <exception cref="ArgumentNullException"> <paramref name="endpoint"/>, <paramref name="metricsNamespace"/> or <paramref name="tokenProvider"/> is null. </exception>
/// <exception cref="ArgumentException"> <paramref name="metricsNamespace"/> is an empty string, and was expected to be non-empty. </exception>
public SampleTypeSpecClient(Uri endpoint, string metricsNamespace, AuthenticationTokenProvider tokenProvider, SampleTypeSpecClientOptions options)
{
Argument.AssertNotNull(endpoint, nameof(endpoint));
Argument.AssertNotNullOrEmpty(metricsNamespace, nameof(metricsNamespace));
Argument.AssertNotNull(tokenProvider, nameof(tokenProvider));

options ??= new SampleTypeSpecClientOptions();

_endpoint = endpoint;
_metricsNamespace = metricsNamespace;
_tokenProvider = tokenProvider;
Pipeline = ClientPipeline.Create(options, Array.Empty<PipelinePolicy>(), new PipelinePolicy[] { new UserAgentPolicy(typeof(SampleTypeSpecClient).Assembly), new BearerTokenPolicy(_tokenProvider, _flows) }, Array.Empty<PipelinePolicy>());
_apiVersion = options.Version;
Expand Down Expand Up @@ -3317,7 +3330,7 @@ public virtual PlantOperations GetPlantOperationsClient()
/// <summary> Initializes a new instance of Metrics. </summary>
public virtual Metrics GetMetricsClient()
{
return Volatile.Read(ref _cachedMetrics) ?? Interlocked.CompareExchange(ref _cachedMetrics, new Metrics(Pipeline, _endpoint), null) ?? _cachedMetrics;
return Volatile.Read(ref _cachedMetrics) ?? Interlocked.CompareExchange(ref _cachedMetrics, new Metrics(Pipeline, _endpoint, _metricsNamespace), null) ?? _cachedMetrics;
}
}
}
9 changes: 7 additions & 2 deletions docs/samples/client/csharp/SampleService/main.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -833,14 +833,19 @@ interface PlantOperations {
};
}

model MetricsClientParams {
metricsNamespace: string;
}

@clientInitialization({
Comment thread
JoshLove-msft marked this conversation as resolved.
initializedBy: InitializedBy.individually | InitializedBy.parent,
parameters: MetricsClientParams,
})
interface Metrics {
@doc("Get Widget metrics for given day of week")
@get
@route("/metrics/widgets/daysOfWeek")
getWidgetMetrics(@path day: DaysOfWeekExtensibleEnum): {
@route("/metrics/{metricsNamespace}/widgets/daysOfWeek")
getWidgetMetrics(@path metricsNamespace: string, @path day: DaysOfWeekExtensibleEnum): {
numSold: int32;
averagePrice: float32;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,22 @@ private IReadOnlyList<ParameterProvider> GetSubClientInternalConstructorParamete
return subClientParameters;
}

/// <summary>
/// Determines whether this subclient has non-infrastructure parameters
/// (not API versions, not endpoint) that are not present on the parent's InputClient.Parameters.
/// Uses the raw <see cref="InputClient.Parameters"/> to avoid circular lazy-initialization dependencies.
/// </summary>
internal bool HasAccessorOnlyParameters(InputClient parentInputClient)
Comment thread
JoshLove-msft marked this conversation as resolved.
{
var parentParamNames = parentInputClient.Parameters
.Select(p => p.Name)
.ToHashSet(StringComparer.OrdinalIgnoreCase);

return _inputClient.Parameters
.Where(p => !p.IsApiVersion && !(p is InputEndpointParameter ep && ep.IsEndpoint))
.Any(p => !parentParamNames.Contains(p.Name));
}

private Lazy<IReadOnlyList<ParameterProvider>> _clientParameters;
internal IReadOnlyList<ParameterProvider> ClientParameters => _clientParameters.Value;
private IReadOnlyList<ParameterProvider> GetClientParameters()
Expand Down Expand Up @@ -397,7 +413,10 @@ protected override FieldProvider[] BuildFields()
// add sub-client caching fields
foreach (var subClient in _subClients.Value)
{
if (subClient._clientCachingField != null)
// Only add caching field when the accessor does not require additional parameters.
// If the subclient has parameters that are not on the parent, each accessor call may
// produce a different client instance, so caching is not appropriate.
if (subClient._clientCachingField != null && !subClient.HasAccessorOnlyParameters(_inputClient))
{
fields.Add(subClient._clientCachingField);
}
Expand Down Expand Up @@ -899,8 +918,19 @@ protected override ScmMethodProvider[] BuildMethods()

var cachedClientFieldVar = new VariableExpression(subClient.Type, subClient._clientCachingField.Declaration, IsRef: true);
List<ValueExpression> subClientConstructorArgs = new(3);

// Populate constructor arguments
List<ParameterProvider> accessorMethodParams = [];

// Identify subclient-specific parameters by comparing with the parent's input parameters.
// Parameters present on both parent and subclient are shared (sourced from parent fields/properties).
var parentInputParamNames = _inputClient.Parameters
.Select(p => p.Name)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var subClientSpecificParamNames = subClient._inputClient.Parameters
.Where(p => !parentInputParamNames.Contains(p.Name))
.Select(p => p.Name)
.ToHashSet(StringComparer.OrdinalIgnoreCase);

// Populate constructor arguments, collecting subclient-specific params for the accessor method signature
foreach (var param in subClient._subClientInternalConstructorParams.Value)
{
if (parentClientProperties.TryGetValue(param.Name, out var parentProperty))
Expand All @@ -920,30 +950,57 @@ protected override ScmMethodProvider[] BuildMethods()
subClientConstructorArgs.Add(correspondingApiVersionField.Field);
}
}
else if (subClientSpecificParamNames.Contains(param.Name))
{
// This parameter is subclient-specific — expose it as an accessor method parameter.
accessorMethodParams.Add(param);
subClientConstructorArgs.Add(param);
}
}

// Create the interlocked compare exchange expression for the body
var interlockedCompareExchange = Static(typeof(Interlocked)).Invoke(
nameof(Interlocked.CompareExchange),
[cachedClientFieldVar, New.Instance(subClient.Type, subClientConstructorArgs), Null]);
var factoryMethodName = subClient.Name.EndsWith(ClientSuffix, StringComparison.OrdinalIgnoreCase)
? $"Get{subClient.Name}"
: $"Get{subClient.Name}{ClientSuffix}";

var factoryMethod = new ScmMethodProvider(
new(
factoryMethodName,
$"Initializes a new instance of {subClient.Type.Name}",
MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual,
subClient.Type,
null,
[]),
// return Volatile.Read(ref _cachedClient) ?? Interlocked.CompareExchange(ref _cachedClient, new Client(_pipeline, _keyCredential, _endpoint), null) ?? _cachedClient;
Return(
Static(typeof(Volatile)).Invoke(nameof(Volatile.Read), cachedClientFieldVar)
.NullCoalesce(interlockedCompareExchange.NullCoalesce(subClient._clientCachingField))),
this,
ScmMethodKind.Convenience);
ScmMethodProvider factoryMethod;
if (accessorMethodParams.Count > 0)
{
// When the accessor requires extra parameters, caching is not appropriate
// (different parameter values may produce different client instances).
// Return a new instance directly.
factoryMethod = new ScmMethodProvider(
new(
factoryMethodName,
$"Initializes a new instance of {subClient.Type.Name}",
MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual,
subClient.Type,
null,
[.. accessorMethodParams]),
Return(New.Instance(subClient.Type, subClientConstructorArgs)),
this,
ScmMethodKind.Convenience);
}
else
{
// No extra params - use the existing caching pattern
var interlockedCompareExchange = Static(typeof(Interlocked)).Invoke(
nameof(Interlocked.CompareExchange),
[cachedClientFieldVar, New.Instance(subClient.Type, subClientConstructorArgs), Null]);
factoryMethod = new ScmMethodProvider(
new(
factoryMethodName,
$"Initializes a new instance of {subClient.Type.Name}",
MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual,
subClient.Type,
null,
[]),
// return Volatile.Read(ref _cachedClient) ?? Interlocked.CompareExchange(ref _cachedClient, new Client(_pipeline, _keyCredential, _endpoint), null) ?? _cachedClient;
Return(
Static(typeof(Volatile)).Invoke(nameof(Volatile.Read), cachedClientFieldVar)
.NullCoalesce(interlockedCompareExchange.NullCoalesce(subClient._clientCachingField))),
this,
ScmMethodKind.Convenience);
}
methods.Add(factoryMethod);
}

Expand Down
Loading
Loading