From 85e52ff8d50408330157f827729d7cbd36423331 Mon Sep 17 00:00:00 2001 From: David Burg Date: Fri, 1 May 2026 19:11:00 -0700 Subject: [PATCH 1/6] refactor!: generated clients inherit ConnectorClientBase (#88) Breaking change: generated connector clients now inherit from ConnectorClientBase instead of implementing IDisposable directly. SDK core changes: - ConnectorClientBase: add convenience constructors (connectionRuntimeUrl + TokenCredential), CallConnectorAsync, ResolveUrl with SSRF protection, shared ApiHubScopes and JsonOptions - TokenCredentialTokenProvider: adapts Azure.Core.TokenCredential to ITokenProvider - ConnectorException: unified exception with ConnectorName, Operation, StatusCode, ResponseBody (replaces per-connector exception types) Generated files (regenerated via updated CodefulSdkGenerator): - All 11 clients inherit ConnectorClientBase, override ConnectorName - Accept optional ConnectorClientOptions for retry/timeout - Namespace changed: DirectClient.{Connector} -> Sdk.{Connector} - DirectClientConnectors -> SdkConnectors Tests updated for ConnectorException, new namespaces, SdkConnectors. Closes #88 --- CHANGELOG.md | 10 + README.md | 21 +- .../TokenCredentialTokenProvider.cs | 46 ++++ .../ConnectorClientBase.cs | 225 +++++++++++++++++- .../ConnectorException.cs | 61 +++++ .../Generated/AzureblobExtensions.cs | 188 +-------------- .../Generated/AzuremonitorlogsExtensions.cs | 188 +-------------- .../Generated/ConnectorNames.cs | 4 +- .../Generated/KustoExtensions.cs | 188 +-------------- .../Generated/ManagedConnectors.cs | 30 +-- .../Generated/MqExtensions.cs | 189 +-------------- .../MsgraphgroupsanduserExtensions.cs | 189 +-------------- .../Generated/Office365Extensions.cs | 188 +-------------- .../Generated/Office365usersExtensions.cs | 188 +-------------- .../OnedriveforbusinessExtensions.cs | 188 +-------------- .../Generated/SharepointonlineExtensions.cs | 188 +-------------- .../Generated/SmtpExtensions.cs | 203 +++------------- .../Generated/TeamsExtensions.cs | 188 +-------------- .../AzureblobClientTests.cs | 8 +- .../AzuremonitorlogsClientTests.cs | 8 +- .../ConnectorConstantsTests.cs | 18 +- .../DynamicSchemaTests.cs | 2 +- .../KustoClientTests.cs | 8 +- .../MqClientTests.cs | 6 +- .../MsgraphgroupsanduserClientTests.cs | 4 +- .../Office365ClientTests.cs | 4 +- .../Office365TriggerPayloadTests.cs | 2 +- .../Office365usersClientTests.cs | 4 +- .../OnedriveforbusinessClientTests.cs | 8 +- .../SharepointonlineClientTests.cs | 8 +- .../SmtpClientTests.cs | 6 +- .../TeamsClientTests.cs | 4 +- .../TriggerCallbackPayloadTests.cs | 2 +- 33 files changed, 563 insertions(+), 2011 deletions(-) create mode 100644 src/Microsoft.Azure.Connectors.Sdk/Authentication/TokenCredentialTokenProvider.cs create mode 100644 src/Microsoft.Azure.Connectors.Sdk/ConnectorException.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index d290276..d98417c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- **Breaking:** Generated connector clients now inherit from `ConnectorClientBase` instead of implementing `IDisposable` directly (#88) +- **Breaking:** Per-connector exception types (e.g., `Office365ConnectorException`, `TeamsConnectorException`) replaced with unified `ConnectorException` base type with `ConnectorName`, `Operation`, `StatusCode`, and `ResponseBody` properties (#88) +- **Breaking:** Generated client constructors accept a new optional `ConnectorClientOptions` parameter for configuring retry policy, timeout, and exponential backoff — the `HttpClient` parameter moved from position 3 to position 4 (#88) +- Generated clients now use SDK infrastructure (`ConnectorHttpClient`) for authentication, retry with exponential backoff, OpenTelemetry instrumentation, and SSRF-protected URL resolution (#88) + ### Added - Azure Monitor Logs (`azuremonitorlogs`) generated typed client for querying Log Analytics workspaces and Application Insights — includes QueryData, QueryDataV2, VisualizeQuery, VisualizeQueryV2 operations with dynamic schema support for query results +- `TokenCredentialTokenProvider` adapter — wraps any `Azure.Core.TokenCredential` as an `ITokenProvider` for the SDK's HTTP pipeline (#88) +- `ConnectorException` — unified exception type for all connector API failures (#88) +- `ConnectorClientBase` now provides `CallConnectorAsync`, `ResolveUrl`, shared JSON options, and convenience constructors accepting `connectionRuntimeUrl` + `TokenCredential` (#88) ### Removed diff --git a/README.md b/README.md index d6e3805..6d10c69 100644 --- a/README.md +++ b/README.md @@ -76,13 +76,24 @@ Copy the generated `*Extensions.cs` files to your project. ```csharp using Microsoft.Azure.Connectors.DirectClient.Office365; +using Microsoft.Azure.Connectors.Sdk; // Get connection runtime URL from Azure Portal var connectionRuntimeUrl = "https://..."; -// Create client (uses DefaultAzureCredential by default) +// Create client with default settings (uses DefaultAzureCredential) using var client = new Office365Client(connectionRuntimeUrl); +// Or configure retry, timeout, and backoff +using var clientWithOptions = new Office365Client( + connectionRuntimeUrl, + options: new ConnectorClientOptions + { + MaxRetryAttempts = 5, + Timeout = TimeSpan.FromSeconds(60), + UseExponentialBackoff = true + }); + // Call typed operations await client.SendEmailAsync(new SendEmailInput { @@ -96,12 +107,20 @@ var categories = await client.GetOutlookCategoryNamesAsync(); ## SDK Components +### Client Base +| Component | Description | +|-----------|-------------| +| `ConnectorClientBase` | Abstract base class for all generated clients — provides authentication, retry, OTel tracing, JSON serialization, and SSRF-protected URL resolution | +| `ConnectorClientOptions` | Configuration for retry count, timeout, exponential backoff, and initial retry delay | +| `ConnectorException` | Unified exception for connector API failures with `ConnectorName`, `StatusCode`, and `ResponseBody` | + ### Authentication | Provider | Use Case | |----------|----------| | `ManagedIdentityTokenProvider` | Azure-hosted apps (App Service, Functions) | | `ConnectionStringTokenProvider` | Local development with API keys | +| `TokenCredentialTokenProvider` | Wraps any `Azure.Core.TokenCredential` (DefaultAzureCredential, etc.) | ### HTTP diff --git a/src/Microsoft.Azure.Connectors.Sdk/Authentication/TokenCredentialTokenProvider.cs b/src/Microsoft.Azure.Connectors.Sdk/Authentication/TokenCredentialTokenProvider.cs new file mode 100644 index 0000000..6d9045d --- /dev/null +++ b/src/Microsoft.Azure.Connectors.Sdk/Authentication/TokenCredentialTokenProvider.cs @@ -0,0 +1,46 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using global::Azure.Core; + +namespace Microsoft.Azure.Connectors.Sdk.Authentication +{ + /// + /// Adapts an (from Azure.Core) to the SDK's + /// interface, enabling generated clients to accept + /// any Azure credential (DefaultAzureCredential, ManagedIdentityCredential, etc.). + /// + public class TokenCredentialTokenProvider : ITokenProvider + { + private readonly TokenCredential _credential; + + /// + /// Initializes a new instance of the class. + /// + /// The Azure credential to wrap. + public TokenCredentialTokenProvider(TokenCredential credential) + { + ArgumentNullException.ThrowIfNull(credential); + this._credential = credential; + } + + /// + public async Task GetAccessTokenAsync(string[] scopes, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(scopes); + + if (scopes.Length == 0) + { + throw new ArgumentException(message: "At least one scope must be provided.", paramName: nameof(scopes)); + } + + var context = new TokenRequestContext(scopes); + var token = await this._credential + .GetTokenAsync(context, cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + return token.Token; + } + } +} diff --git a/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs b/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs index 22c4e5d..b653ce6 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs @@ -3,6 +3,12 @@ //------------------------------------------------------------ using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using global::Azure.Core; +using global::Azure.Identity; using Microsoft.Azure.Connectors.Sdk.Authentication; using Microsoft.Azure.Connectors.Sdk.Http; using Microsoft.Extensions.Logging; @@ -12,15 +18,81 @@ namespace Microsoft.Azure.Connectors.Sdk { /// /// Abstract base class for generated connector clients. + /// Provides shared infrastructure: authentication, HTTP, JSON serialization, + /// URL resolution with SSRF protection, and configurable retry/timeout. /// public abstract class ConnectorClientBase : IConnectorClient { + /// + /// The default OAuth scopes for API Hub authentication. + /// + protected static readonly string[] ApiHubScopes = ["https://apihub.azure.com/.default"]; + + /// + /// The default JSON serializer options for connector operations. + /// + protected static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + private readonly ConnectorHttpClient _httpClient; private readonly ILogger _logger; + private readonly string _connectionRuntimeUrl; private bool _disposed; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class + /// with a connection runtime URL and optional Azure credential. + /// + /// The connection runtime URL from Azure Portal. + /// Optional Azure credential. Defaults to . + /// Optional client options for retry, timeout, etc. + /// Optional externally managed HttpClient. + protected ConnectorClientBase( + string connectionRuntimeUrl, + TokenCredential? credential = null, + ConnectorClientOptions? options = null, + HttpClient? httpClient = null) + : this( + new TokenCredentialTokenProvider(credential ?? new DefaultAzureCredential()), + ConnectorClientBase.ApplyBaseUri(options, connectionRuntimeUrl), + logger: null, + httpClient) + { + this._connectionRuntimeUrl = connectionRuntimeUrl?.TrimEnd('/') + ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl)); + } + + /// + /// Initializes a new instance of the class + /// with a connection runtime URL and managed identity. + /// + /// The connection runtime URL from Azure Portal. + /// + /// The client ID for user-assigned managed identity. + /// Use null or empty string for system-assigned identity. + /// + /// Optional client options for retry, timeout, etc. + /// Optional externally managed HttpClient. + protected ConnectorClientBase( + string connectionRuntimeUrl, + string managedIdentityClientId, + ConnectorClientOptions? options = null, + HttpClient? httpClient = null) + : this( + connectionRuntimeUrl, + ConnectorClientBase.CreateManagedIdentityCredential(managedIdentityClientId), + options, + httpClient) + { + } + + /// + /// Initializes a new instance of the class + /// with a custom token provider. /// /// The token provider for authentication. /// The connector client options. @@ -29,16 +101,26 @@ protected ConnectorClientBase( ITokenProvider tokenProvider, ConnectorClientOptions? options = null, ILogger? logger = null) + : this(tokenProvider, options, logger, httpClient: null) + { + } + + private ConnectorClientBase( + ITokenProvider tokenProvider, + ConnectorClientOptions? options, + ILogger? logger, + HttpClient? httpClient) { ArgumentNullException.ThrowIfNull(tokenProvider); options ??= new ConnectorClientOptions(); this._logger = logger ?? NullLogger.Instance; + this._connectionRuntimeUrl = options.BaseUri?.ToString().TrimEnd('/') ?? string.Empty; this._httpClient = new ConnectorHttpClient( tokenProvider, options, this._logger, - httpClient: null, + httpClient, connectorNameProvider: () => this.ConnectorName); } @@ -55,6 +137,128 @@ protected ConnectorClientBase( /// protected ILogger Logger => this._logger; + /// + /// Sends a connector API request and deserializes the JSON response. + /// + /// The response type. + /// The HTTP method. + /// The relative path or absolute URL. + /// Optional request body (will be JSON-serialized). + /// Cancellation token. + /// The deserialized response. + protected async Task CallConnectorAsync( + HttpMethod method, + string path, + object? body = null, + CancellationToken cancellationToken = default) + { + var url = this.ResolveUrl(path); + var operation = $"{method} {path}"; + + using var request = new HttpRequestMessage(method, url); + + if (body != null) + { + var json = JsonSerializer.Serialize(body, ConnectorClientBase.JsonOptions); + request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + } + + using var response = await this._httpClient + .SendAsync(request, ConnectorClientBase.ApiHubScopes, cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + if (!response.IsSuccessStatusCode) + { + var errorBody = await response.Content + .ReadAsStringAsync(cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + throw new ConnectorException(this.ConnectorName, operation, (int)response.StatusCode, errorBody); + } + + if (typeof(TResponse) == typeof(byte[])) + { + var bytes = await response.Content + .ReadAsByteArrayAsync(cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + return (TResponse)(object)bytes; + } + + var responseBody = await response.Content + .ReadAsStringAsync(cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + if (string.IsNullOrEmpty(responseBody)) + { + return default!; + } + + return JsonSerializer.Deserialize(responseBody, ConnectorClientBase.JsonOptions)!; + } + + /// + /// Sends a connector API request with no response body. + /// + /// The HTTP method. + /// The relative path or absolute URL. + /// Optional request body (will be JSON-serialized). + /// Cancellation token. + protected async Task CallConnectorAsync( + HttpMethod method, + string path, + object? body = null, + CancellationToken cancellationToken = default) + { + var url = this.ResolveUrl(path); + var operation = $"{method} {path}"; + + using var request = new HttpRequestMessage(method, url); + + if (body != null) + { + var json = JsonSerializer.Serialize(body, ConnectorClientBase.JsonOptions); + request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + } + + using var response = await this._httpClient + .SendAsync(request, ConnectorClientBase.ApiHubScopes, cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + if (!response.IsSuccessStatusCode) + { + var responseBody = await response.Content + .ReadAsStringAsync(cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + throw new ConnectorException(this.ConnectorName, operation, (int)response.StatusCode, responseBody); + } + } + + /// + /// Resolves a relative path or validates an absolute URL against the connection runtime URL, + /// preventing SSRF by ensuring absolute URLs match the connection host. + /// + /// The relative path or absolute URL to resolve. + /// The resolved absolute URL. + protected string ResolveUrl(string path) + { + if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) + { + var baseUri = new Uri(this._connectionRuntimeUrl); + var nextUri = new Uri(path); + if (!string.Equals(baseUri.Scheme, nextUri.Scheme, StringComparison.OrdinalIgnoreCase) || + !string.Equals(baseUri.Host, nextUri.Host, StringComparison.OrdinalIgnoreCase) || + baseUri.Port != nextUri.Port) + { + throw new InvalidOperationException( + $"NextLink URI '{nextUri.Scheme}://{nextUri.Host}:{nextUri.Port}' does not match connection URI '{baseUri.Scheme}://{baseUri.Host}:{baseUri.Port}'. " + + "Refusing to send credentials to an unexpected host."); + } + + return path; + } + + return $"{this._connectionRuntimeUrl}{path}"; + } + /// public void Dispose() { @@ -78,5 +282,22 @@ protected virtual void Dispose(bool disposing) this._disposed = true; } } + + private static ConnectorClientOptions ApplyBaseUri(ConnectorClientOptions? options, string connectionRuntimeUrl) + { + options ??= new ConnectorClientOptions(); + options.BaseUri = new Uri(connectionRuntimeUrl?.TrimEnd('/') ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl))); + return options; + } + + private static TokenCredential CreateManagedIdentityCredential(string managedIdentityClientId) + { + if (string.IsNullOrEmpty(managedIdentityClientId)) + { + return new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned); + } + + return new ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedClientId(managedIdentityClientId)); + } } } diff --git a/src/Microsoft.Azure.Connectors.Sdk/ConnectorException.cs b/src/Microsoft.Azure.Connectors.Sdk/ConnectorException.cs new file mode 100644 index 0000000..2a930fd --- /dev/null +++ b/src/Microsoft.Azure.Connectors.Sdk/ConnectorException.cs @@ -0,0 +1,61 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Connectors.Sdk +{ + /// + /// Exception thrown when a connector API operation fails. + /// Contains the HTTP status code and response body for diagnostic purposes. + /// + public class ConnectorException : Exception + { + private const int MaxResponseBodyLength = 2000; + + /// + /// Initializes a new instance of the class. + /// + /// The connector name (e.g., "office365"). + /// The operation that failed (e.g., "POST /Mail"). + /// The HTTP status code. + /// The response body from the connector service. + public ConnectorException(string connectorName, string operation, int statusCode, string responseBody) + : base($"[{connectorName}] {operation} failed with status {statusCode}: {TruncateBody(responseBody)}") + { + this.ConnectorName = connectorName; + this.Operation = operation; + this.StatusCode = statusCode; + this.ResponseBody = responseBody; + } + + /// + /// Gets the connector name. + /// + public string ConnectorName { get; } + + /// + /// Gets the operation that failed. + /// + public string Operation { get; } + + /// + /// Gets the HTTP status code. + /// + public int StatusCode { get; } + + /// + /// Gets the response body. + /// + public string ResponseBody { get; } + + private static string TruncateBody(string body) + { + if (string.IsNullOrEmpty(body) || body.Length <= MaxResponseBodyLength) + { + return body; + } + + return body.Substring(0, MaxResponseBodyLength) + "...[truncated]"; + } + } +} diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/AzureblobExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/AzureblobExtensions.cs index ad33d18..5635209 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/AzureblobExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/AzureblobExtensions.cs @@ -1,4 +1,4 @@ -// AzureblobExtensions.cs - Auto-generated DirectClient SDK +// AzureblobExtensions.cs - Auto-generated Connectors SDK // Do not edit this file directly. #nullable disable @@ -18,7 +18,7 @@ using Azure.Identity; using Microsoft.Azure.Connectors.Sdk; -namespace Microsoft.Azure.Connectors.DirectClient.Azureblob; +namespace Microsoft.Azure.Connectors.Sdk.Azureblob; #region Types @@ -318,67 +318,21 @@ public static class OnUpdatedFiles #region Client -/// -/// Exception thrown when azureblob connector operations fail. -/// -public class AzureblobConnectorException : Exception -{ - private const int MaxResponseBodyLength = 2000; - - public string Operation { get; } - public int StatusCode { get; } - public string ResponseBody { get; } - - public AzureblobConnectorException(string operation, int statusCode, string responseBody) - : base($"{operation} failed with status {statusCode}: {TruncateBody(responseBody)}") - { - this.Operation = operation; - this.StatusCode = statusCode; - this.ResponseBody = responseBody; - } - - private static string TruncateBody(string body) - { - if (string.IsNullOrEmpty(body) || body.Length <= MaxResponseBodyLength) - return body; - return body.Substring(0, MaxResponseBodyLength) + "...[truncated]"; - } -} - /// /// Typed client for azureblob connector. /// -public class AzureblobClient : IDisposable +public class AzureblobClient : ConnectorClientBase { - private static readonly string[] ApiHubScopes = ["https://apihub.azure.com/.default"]; - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - - private readonly string _connectionRuntimeUrl; - private readonly HttpClient _httpClient; - private readonly bool _ownsHttpClient; - private readonly bool _ownsCredential; - private readonly TokenCredential _credential; - private AccessToken? _cachedToken; - /// /// Creates a new AzureblobClient. /// /// The connection runtime URL from Azure Portal. /// Optional credential. Defaults to . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public AzureblobClient(string connectionRuntimeUrl, TokenCredential credential = null, HttpClient httpClient = null) + public AzureblobClient(string connectionRuntimeUrl, TokenCredential credential = null, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, credential, options, httpClient) { - this._connectionRuntimeUrl = connectionRuntimeUrl?.TrimEnd('/') - ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl)); - this._credential = credential ?? new DefaultAzureCredential(); - this._ownsCredential = credential == null; - this._ownsHttpClient = httpClient == null; - this._httpClient = httpClient ?? new HttpClient(); } /// @@ -386,123 +340,15 @@ public AzureblobClient(string connectionRuntimeUrl, TokenCredential credential = /// /// The connection runtime URL from Azure Portal. /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public AzureblobClient(string connectionRuntimeUrl, string managedIdentityClientId, HttpClient httpClient = null) - : this(connectionRuntimeUrl, CreateManagedIdentityCredential(managedIdentityClientId), httpClient) + public AzureblobClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } - private static TokenCredential CreateManagedIdentityCredential(string managedIdentityClientId) - { - if (string.IsNullOrEmpty(managedIdentityClientId)) - { - return new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned); - } - - return new ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedClientId(managedIdentityClientId)); - } - - private async Task GetTokenAsync(CancellationToken cancellationToken) - { - if (this._cachedToken.HasValue && this._cachedToken.Value.ExpiresOn > DateTimeOffset.UtcNow.AddMinutes(5)) - { - return this._cachedToken.Value.Token; - } - - this._cachedToken = await this._credential.GetTokenAsync( - new TokenRequestContext(ApiHubScopes), cancellationToken); - return this._cachedToken.Value.Token; - } - - private string ResolveUrl(string path) - { - if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) - { - var baseUri = new Uri(this._connectionRuntimeUrl); - var nextUri = new Uri(path); - if (!string.Equals(baseUri.Scheme, nextUri.Scheme, StringComparison.OrdinalIgnoreCase) || - !string.Equals(baseUri.Host, nextUri.Host, StringComparison.OrdinalIgnoreCase) || - baseUri.Port != nextUri.Port) - { - throw new InvalidOperationException( - $"NextLink URI '{nextUri.Scheme}://{nextUri.Host}:{nextUri.Port}' does not match connection URI '{baseUri.Scheme}://{baseUri.Host}:{baseUri.Port}'. " + - "Refusing to send credentials to an unexpected host."); - } - - return path; - } - - return $"{this._connectionRuntimeUrl}{path}"; - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var errorBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new AzureblobConnectorException(operation, (int)response.StatusCode, errorBody); - } - - if (typeof(TResponse) == typeof(byte[])) - { - var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken); - return (TResponse)(object)bytes; - } - - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - - if (string.IsNullOrEmpty(responseBody)) - return default; - - return JsonSerializer.Deserialize(responseBody, JsonOptions); - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new AzureblobConnectorException(operation, (int)response.StatusCode, responseBody); - } - } + /// + public override string ConnectorName => "azureblob"; /// /// Get storage accounts @@ -824,18 +670,6 @@ public async Task UpdateFileAsync([DynamicValues("GetDataSets")] s return await this.CallConnectorAsync(HttpMethod.Put, path, input, cancellationToken); } - public void Dispose() - { - if (this._ownsHttpClient) - { - this._httpClient?.Dispose(); - } - - if (this._ownsCredential && this._credential is IDisposable disposableCredential) - { - disposableCredential.Dispose(); - } - } } #endregion Client diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/AzuremonitorlogsExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/AzuremonitorlogsExtensions.cs index 10b68de..85b354d 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/AzuremonitorlogsExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/AzuremonitorlogsExtensions.cs @@ -1,4 +1,4 @@ -// AzuremonitorlogsExtensions.cs - Auto-generated DirectClient SDK +// AzuremonitorlogsExtensions.cs - Auto-generated Connectors SDK // Do not edit this file directly. #nullable disable @@ -17,7 +17,7 @@ using Azure.Identity; using Microsoft.Azure.Connectors.Sdk; -namespace Microsoft.Azure.Connectors.DirectClient.Azuremonitorlogs; +namespace Microsoft.Azure.Connectors.Sdk.Azuremonitorlogs; #region Types @@ -231,67 +231,21 @@ public class VisualizeQueryInput #region Client -/// -/// Exception thrown when azuremonitorlogs connector operations fail. -/// -public class AzuremonitorlogsConnectorException : Exception -{ - private const int MaxResponseBodyLength = 2000; - - public string Operation { get; } - public int StatusCode { get; } - public string ResponseBody { get; } - - public AzuremonitorlogsConnectorException(string operation, int statusCode, string responseBody) - : base($"{operation} failed with status {statusCode}: {TruncateBody(responseBody)}") - { - this.Operation = operation; - this.StatusCode = statusCode; - this.ResponseBody = responseBody; - } - - private static string TruncateBody(string body) - { - if (string.IsNullOrEmpty(body) || body.Length <= MaxResponseBodyLength) - return body; - return body.Substring(0, MaxResponseBodyLength) + "...[truncated]"; - } -} - /// /// Typed client for azuremonitorlogs connector. /// -public class AzuremonitorlogsClient : IDisposable +public class AzuremonitorlogsClient : ConnectorClientBase { - private static readonly string[] ApiHubScopes = ["https://apihub.azure.com/.default"]; - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - - private readonly string _connectionRuntimeUrl; - private readonly HttpClient _httpClient; - private readonly bool _ownsHttpClient; - private readonly bool _ownsCredential; - private readonly TokenCredential _credential; - private AccessToken? _cachedToken; - /// /// Creates a new AzuremonitorlogsClient. /// /// The connection runtime URL from Azure Portal. /// Optional credential. Defaults to . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public AzuremonitorlogsClient(string connectionRuntimeUrl, TokenCredential credential = null, HttpClient httpClient = null) + public AzuremonitorlogsClient(string connectionRuntimeUrl, TokenCredential credential = null, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, credential, options, httpClient) { - this._connectionRuntimeUrl = connectionRuntimeUrl?.TrimEnd('/') - ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl)); - this._credential = credential ?? new DefaultAzureCredential(); - this._ownsCredential = credential == null; - this._ownsHttpClient = httpClient == null; - this._httpClient = httpClient ?? new HttpClient(); } /// @@ -299,123 +253,15 @@ public AzuremonitorlogsClient(string connectionRuntimeUrl, TokenCredential crede /// /// The connection runtime URL from Azure Portal. /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public AzuremonitorlogsClient(string connectionRuntimeUrl, string managedIdentityClientId, HttpClient httpClient = null) - : this(connectionRuntimeUrl, CreateManagedIdentityCredential(managedIdentityClientId), httpClient) - { - } - - private static TokenCredential CreateManagedIdentityCredential(string managedIdentityClientId) + public AzuremonitorlogsClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { - if (string.IsNullOrEmpty(managedIdentityClientId)) - { - return new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned); - } - - return new ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedClientId(managedIdentityClientId)); } - private async Task GetTokenAsync(CancellationToken cancellationToken) - { - if (this._cachedToken.HasValue && this._cachedToken.Value.ExpiresOn > DateTimeOffset.UtcNow.AddMinutes(5)) - { - return this._cachedToken.Value.Token; - } - - this._cachedToken = await this._credential.GetTokenAsync( - new TokenRequestContext(ApiHubScopes), cancellationToken); - return this._cachedToken.Value.Token; - } - - private string ResolveUrl(string path) - { - if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) - { - var baseUri = new Uri(this._connectionRuntimeUrl); - var nextUri = new Uri(path); - if (!string.Equals(baseUri.Scheme, nextUri.Scheme, StringComparison.OrdinalIgnoreCase) || - !string.Equals(baseUri.Host, nextUri.Host, StringComparison.OrdinalIgnoreCase) || - baseUri.Port != nextUri.Port) - { - throw new InvalidOperationException( - $"NextLink URI '{nextUri.Scheme}://{nextUri.Host}:{nextUri.Port}' does not match connection URI '{baseUri.Scheme}://{baseUri.Host}:{baseUri.Port}'. " + - "Refusing to send credentials to an unexpected host."); - } - - return path; - } - - return $"{this._connectionRuntimeUrl}{path}"; - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var errorBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new AzuremonitorlogsConnectorException(operation, (int)response.StatusCode, errorBody); - } - - if (typeof(TResponse) == typeof(byte[])) - { - var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken); - return (TResponse)(object)bytes; - } - - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - - if (string.IsNullOrEmpty(responseBody)) - return default; - - return JsonSerializer.Deserialize(responseBody, JsonOptions); - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new AzuremonitorlogsConnectorException(operation, (int)response.StatusCode, responseBody); - } - } + /// + public override string ConnectorName => "azuremonitorlogs"; /// /// List subscriptions @@ -659,18 +505,6 @@ public async Task VisualizeQueryAsync(VisualizeQueryInput inpu return await this.CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken); } - public void Dispose() - { - if (this._ownsHttpClient) - { - this._httpClient?.Dispose(); - } - - if (this._ownsCredential && this._credential is IDisposable disposableCredential) - { - disposableCredential.Dispose(); - } - } } #endregion Client diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/ConnectorNames.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/ConnectorNames.cs index 2491adc..208f9ed 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/ConnectorNames.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/ConnectorNames.cs @@ -1,4 +1,4 @@ -// ConnectorNames.cs - Auto-generated DirectClient SDK +// ConnectorNames.cs - Auto-generated Connectors SDK // Do not edit this file directly. namespace Microsoft.Azure.Connectors.Sdk; @@ -32,7 +32,7 @@ public static class ConnectorNames public const string Kusto = "kusto"; /// - /// The mq connector (IBM MQ). + /// The mq connector. /// public const string Mq = "mq"; diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/KustoExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/KustoExtensions.cs index 90240de..331c9fd 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/KustoExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/KustoExtensions.cs @@ -1,4 +1,4 @@ -// KustoExtensions.cs - Auto-generated DirectClient SDK +// KustoExtensions.cs - Auto-generated Connectors SDK // Do not edit this file directly. #nullable disable @@ -17,7 +17,7 @@ using Azure.Identity; using Microsoft.Azure.Connectors.Sdk; -namespace Microsoft.Azure.Connectors.DirectClient.Kusto; +namespace Microsoft.Azure.Connectors.Sdk.Kusto; #region Types @@ -249,67 +249,21 @@ public class MCPQueryRequest #region Client -/// -/// Exception thrown when kusto connector operations fail. -/// -public class KustoConnectorException : Exception -{ - private const int MaxResponseBodyLength = 2000; - - public string Operation { get; } - public int StatusCode { get; } - public string ResponseBody { get; } - - public KustoConnectorException(string operation, int statusCode, string responseBody) - : base($"{operation} failed with status {statusCode}: {TruncateBody(responseBody)}") - { - this.Operation = operation; - this.StatusCode = statusCode; - this.ResponseBody = responseBody; - } - - private static string TruncateBody(string body) - { - if (string.IsNullOrEmpty(body) || body.Length <= MaxResponseBodyLength) - return body; - return body.Substring(0, MaxResponseBodyLength) + "...[truncated]"; - } -} - /// /// Typed client for kusto connector. /// -public class KustoClient : IDisposable +public class KustoClient : ConnectorClientBase { - private static readonly string[] ApiHubScopes = ["https://apihub.azure.com/.default"]; - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - - private readonly string _connectionRuntimeUrl; - private readonly HttpClient _httpClient; - private readonly bool _ownsHttpClient; - private readonly bool _ownsCredential; - private readonly TokenCredential _credential; - private AccessToken? _cachedToken; - /// /// Creates a new KustoClient. /// /// The connection runtime URL from Azure Portal. /// Optional credential. Defaults to . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public KustoClient(string connectionRuntimeUrl, TokenCredential credential = null, HttpClient httpClient = null) + public KustoClient(string connectionRuntimeUrl, TokenCredential credential = null, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, credential, options, httpClient) { - this._connectionRuntimeUrl = connectionRuntimeUrl?.TrimEnd('/') - ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl)); - this._credential = credential ?? new DefaultAzureCredential(); - this._ownsCredential = credential == null; - this._ownsHttpClient = httpClient == null; - this._httpClient = httpClient ?? new HttpClient(); } /// @@ -317,123 +271,15 @@ public KustoClient(string connectionRuntimeUrl, TokenCredential credential = nul /// /// The connection runtime URL from Azure Portal. /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public KustoClient(string connectionRuntimeUrl, string managedIdentityClientId, HttpClient httpClient = null) - : this(connectionRuntimeUrl, CreateManagedIdentityCredential(managedIdentityClientId), httpClient) - { - } - - private static TokenCredential CreateManagedIdentityCredential(string managedIdentityClientId) + public KustoClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { - if (string.IsNullOrEmpty(managedIdentityClientId)) - { - return new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned); - } - - return new ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedClientId(managedIdentityClientId)); } - private async Task GetTokenAsync(CancellationToken cancellationToken) - { - if (this._cachedToken.HasValue && this._cachedToken.Value.ExpiresOn > DateTimeOffset.UtcNow.AddMinutes(5)) - { - return this._cachedToken.Value.Token; - } - - this._cachedToken = await this._credential.GetTokenAsync( - new TokenRequestContext(ApiHubScopes), cancellationToken); - return this._cachedToken.Value.Token; - } - - private string ResolveUrl(string path) - { - if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) - { - var baseUri = new Uri(this._connectionRuntimeUrl); - var nextUri = new Uri(path); - if (!string.Equals(baseUri.Scheme, nextUri.Scheme, StringComparison.OrdinalIgnoreCase) || - !string.Equals(baseUri.Host, nextUri.Host, StringComparison.OrdinalIgnoreCase) || - baseUri.Port != nextUri.Port) - { - throw new InvalidOperationException( - $"NextLink URI '{nextUri.Scheme}://{nextUri.Host}:{nextUri.Port}' does not match connection URI '{baseUri.Scheme}://{baseUri.Host}:{baseUri.Port}'. " + - "Refusing to send credentials to an unexpected host."); - } - - return path; - } - - return $"{this._connectionRuntimeUrl}{path}"; - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var errorBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new KustoConnectorException(operation, (int)response.StatusCode, errorBody); - } - - if (typeof(TResponse) == typeof(byte[])) - { - var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken); - return (TResponse)(object)bytes; - } - - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - - if (string.IsNullOrEmpty(responseBody)) - return default; - - return JsonSerializer.Deserialize(responseBody, JsonOptions); - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new KustoConnectorException(operation, (int)response.StatusCode, responseBody); - } - } + /// + public override string ConnectorName => "kusto"; /// /// Run KQL query @@ -530,18 +376,6 @@ public async Task McpKustoQueryManagementAsync(MCPQueryRequest return await this.CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken); } - public void Dispose() - { - if (this._ownsHttpClient) - { - this._httpClient?.Dispose(); - } - - if (this._ownsCredential && this._credential is IDisposable disposableCredential) - { - disposableCredential.Dispose(); - } - } } #endregion Client diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/ManagedConnectors.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/ManagedConnectors.cs index a8e8ef5..9c91276 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/ManagedConnectors.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/ManagedConnectors.cs @@ -1,37 +1,37 @@ -// DirectClient SDK - Generated Connectors +// Connectors SDK - Generated Connectors // Each connector client is used independently: // -// using Microsoft.Azure.Connectors.DirectClient.Azureblob; +// using Microsoft.Azure.Connectors.Sdk.Azureblob; // var client = new AzureblobClient(connectionRuntimeUrl); -// using Microsoft.Azure.Connectors.DirectClient.Azuremonitorlogs; +// using Microsoft.Azure.Connectors.Sdk.Azuremonitorlogs; // var client = new AzuremonitorlogsClient(connectionRuntimeUrl); -// using Microsoft.Azure.Connectors.DirectClient.Kusto; +// using Microsoft.Azure.Connectors.Sdk.Kusto; // var client = new KustoClient(connectionRuntimeUrl); -// using Microsoft.Azure.Connectors.DirectClient.Mq; +// using Microsoft.Azure.Connectors.Sdk.Mq; // var client = new MqClient(connectionRuntimeUrl); -// using Microsoft.Azure.Connectors.DirectClient.Msgraphgroupsanduser; +// using Microsoft.Azure.Connectors.Sdk.Msgraphgroupsanduser; // var client = new MsgraphgroupsanduserClient(connectionRuntimeUrl); -// using Microsoft.Azure.Connectors.DirectClient.Office365; +// using Microsoft.Azure.Connectors.Sdk.Office365; // var client = new Office365Client(connectionRuntimeUrl); -// using Microsoft.Azure.Connectors.DirectClient.Office365users; +// using Microsoft.Azure.Connectors.Sdk.Office365users; // var client = new Office365usersClient(connectionRuntimeUrl); -// using Microsoft.Azure.Connectors.DirectClient.Onedriveforbusiness; +// using Microsoft.Azure.Connectors.Sdk.Onedriveforbusiness; // var client = new OnedriveforbusinessClient(connectionRuntimeUrl); -// using Microsoft.Azure.Connectors.DirectClient.Sharepointonline; +// using Microsoft.Azure.Connectors.Sdk.Sharepointonline; // var client = new SharepointonlineClient(connectionRuntimeUrl); -// using Microsoft.Azure.Connectors.DirectClient.Smtp; +// using Microsoft.Azure.Connectors.Sdk.Smtp; // var client = new SmtpClient(connectionRuntimeUrl); -// using Microsoft.Azure.Connectors.DirectClient.Teams; +// using Microsoft.Azure.Connectors.Sdk.Teams; // var client = new TeamsClient(connectionRuntimeUrl); #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -namespace Microsoft.Azure.Connectors.DirectClient; +namespace Microsoft.Azure.Connectors.Sdk; /// -/// Provides a list of available DirectClient connectors. +/// Provides a list of available SDK connectors. /// -public static class DirectClientConnectors +public static class SdkConnectors { /// /// The list of available connector names. diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/MqExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/MqExtensions.cs index 0c4be3b..cc60af5 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/MqExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/MqExtensions.cs @@ -1,4 +1,4 @@ -// MqExtensions.cs - Auto-generated DirectClient SDK +// MqExtensions.cs - Auto-generated Connectors SDK // Do not edit this file directly. #nullable disable @@ -15,8 +15,9 @@ using System.Threading.Tasks; using Azure.Core; using Azure.Identity; +using Microsoft.Azure.Connectors.Sdk; -namespace Microsoft.Azure.Connectors.DirectClient.Mq; +namespace Microsoft.Azure.Connectors.Sdk.Mq; #region Types @@ -223,67 +224,21 @@ public class SendValidDataOptions #region Client -/// -/// Exception thrown when mq connector operations fail. -/// -public class MqConnectorException : Exception -{ - private const int MaxResponseBodyLength = 2000; - - public string Operation { get; } - public int StatusCode { get; } - public string ResponseBody { get; } - - public MqConnectorException(string operation, int statusCode, string responseBody) - : base($"{operation} failed with status {statusCode}: {TruncateBody(responseBody)}") - { - this.Operation = operation; - this.StatusCode = statusCode; - this.ResponseBody = responseBody; - } - - private static string TruncateBody(string body) - { - if (string.IsNullOrEmpty(body) || body.Length <= MaxResponseBodyLength) - return body; - return body.Substring(0, MaxResponseBodyLength) + "...[truncated]"; - } -} - /// /// Typed client for mq connector. /// -public class MqClient : IDisposable +public class MqClient : ConnectorClientBase { - private static readonly string[] ApiHubScopes = ["https://apihub.azure.com/.default"]; - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - - private readonly string _connectionRuntimeUrl; - private readonly HttpClient _httpClient; - private readonly bool _ownsHttpClient; - private readonly bool _ownsCredential; - private readonly TokenCredential _credential; - private AccessToken? _cachedToken; - /// /// Creates a new MqClient. /// /// The connection runtime URL from Azure Portal. /// Optional credential. Defaults to . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public MqClient(string connectionRuntimeUrl, TokenCredential credential = null, HttpClient httpClient = null) + public MqClient(string connectionRuntimeUrl, TokenCredential credential = null, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, credential, options, httpClient) { - this._connectionRuntimeUrl = connectionRuntimeUrl?.TrimEnd('/') - ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl)); - this._credential = credential ?? new DefaultAzureCredential(); - this._ownsCredential = credential == null; - this._ownsHttpClient = httpClient == null; - this._httpClient = httpClient ?? new HttpClient(); } /// @@ -291,123 +246,15 @@ public MqClient(string connectionRuntimeUrl, TokenCredential credential = null, /// /// The connection runtime URL from Azure Portal. /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public MqClient(string connectionRuntimeUrl, string managedIdentityClientId, HttpClient httpClient = null) - : this(connectionRuntimeUrl, CreateManagedIdentityCredential(managedIdentityClientId), httpClient) - { - } - - private static TokenCredential CreateManagedIdentityCredential(string managedIdentityClientId) + public MqClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { - if (string.IsNullOrEmpty(managedIdentityClientId)) - { - return new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned); - } - - return new ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedClientId(managedIdentityClientId)); } - private async Task GetTokenAsync(CancellationToken cancellationToken) - { - if (this._cachedToken.HasValue && this._cachedToken.Value.ExpiresOn > DateTimeOffset.UtcNow.AddMinutes(5)) - { - return this._cachedToken.Value.Token; - } - - this._cachedToken = await this._credential.GetTokenAsync( - new TokenRequestContext(ApiHubScopes), cancellationToken); - return this._cachedToken.Value.Token; - } - - private string ResolveUrl(string path) - { - if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) - { - var baseUri = new Uri(this._connectionRuntimeUrl); - var nextUri = new Uri(path); - if (!string.Equals(baseUri.Scheme, nextUri.Scheme, StringComparison.OrdinalIgnoreCase) || - !string.Equals(baseUri.Host, nextUri.Host, StringComparison.OrdinalIgnoreCase) || - baseUri.Port != nextUri.Port) - { - throw new InvalidOperationException( - $"NextLink URI '{nextUri.Scheme}://{nextUri.Host}:{nextUri.Port}' does not match connection URI '{baseUri.Scheme}://{baseUri.Host}:{baseUri.Port}'. " + - "Refusing to send credentials to an unexpected host."); - } - - return path; - } - - return $"{this._connectionRuntimeUrl}{path}"; - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var errorBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new MqConnectorException(operation, (int)response.StatusCode, errorBody); - } - - if (typeof(TResponse) == typeof(byte[])) - { - var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken); - return (TResponse)(object)bytes; - } - - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - - if (string.IsNullOrEmpty(responseBody)) - return default; - - return JsonSerializer.Deserialize(responseBody, JsonOptions); - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new MqConnectorException(operation, (int)response.StatusCode, responseBody); - } - } + /// + public override string ConnectorName => "mq"; /// /// Browse message @@ -500,18 +347,6 @@ public async Task SendAsync(SendValidDataOptions input, Cancellati return await this.CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken); } - public void Dispose() - { - if (this._ownsHttpClient) - { - this._httpClient?.Dispose(); - } - - if (this._ownsCredential && this._credential is IDisposable disposableCredential) - { - disposableCredential.Dispose(); - } - } } #endregion Client diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/MsgraphgroupsanduserExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/MsgraphgroupsanduserExtensions.cs index 73ab984..94c8650 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/MsgraphgroupsanduserExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/MsgraphgroupsanduserExtensions.cs @@ -1,4 +1,4 @@ -// MsgraphgroupsanduserExtensions.cs - Auto-generated DirectClient SDK +// MsgraphgroupsanduserExtensions.cs - Auto-generated Connectors SDK // Do not edit this file directly. #nullable disable @@ -15,8 +15,9 @@ using System.Threading.Tasks; using Azure.Core; using Azure.Identity; +using Microsoft.Azure.Connectors.Sdk; -namespace Microsoft.Azure.Connectors.DirectClient.Msgraphgroupsanduser; +namespace Microsoft.Azure.Connectors.Sdk.Msgraphgroupsanduser; #region Types @@ -264,67 +265,21 @@ public class GetMemberGroupsResponse #region Client -/// -/// Exception thrown when msgraphgroupsanduser connector operations fail. -/// -public class MsgraphgroupsanduserConnectorException : Exception -{ - private const int MaxResponseBodyLength = 2000; - - public string Operation { get; } - public int StatusCode { get; } - public string ResponseBody { get; } - - public MsgraphgroupsanduserConnectorException(string operation, int statusCode, string responseBody) - : base($"{operation} failed with status {statusCode}: {TruncateBody(responseBody)}") - { - this.Operation = operation; - this.StatusCode = statusCode; - this.ResponseBody = responseBody; - } - - private static string TruncateBody(string body) - { - if (string.IsNullOrEmpty(body) || body.Length <= MaxResponseBodyLength) - return body; - return body.Substring(0, MaxResponseBodyLength) + "...[truncated]"; - } -} - /// /// Typed client for msgraphgroupsanduser connector. /// -public class MsgraphgroupsanduserClient : IDisposable +public class MsgraphgroupsanduserClient : ConnectorClientBase { - private static readonly string[] ApiHubScopes = ["https://apihub.azure.com/.default"]; - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - - private readonly string _connectionRuntimeUrl; - private readonly HttpClient _httpClient; - private readonly bool _ownsHttpClient; - private readonly bool _ownsCredential; - private readonly TokenCredential _credential; - private AccessToken? _cachedToken; - /// /// Creates a new MsgraphgroupsanduserClient. /// /// The connection runtime URL from Azure Portal. /// Optional credential. Defaults to . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public MsgraphgroupsanduserClient(string connectionRuntimeUrl, TokenCredential credential = null, HttpClient httpClient = null) + public MsgraphgroupsanduserClient(string connectionRuntimeUrl, TokenCredential credential = null, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, credential, options, httpClient) { - this._connectionRuntimeUrl = connectionRuntimeUrl?.TrimEnd('/') - ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl)); - this._credential = credential ?? new DefaultAzureCredential(); - this._ownsCredential = credential == null; - this._ownsHttpClient = httpClient == null; - this._httpClient = httpClient ?? new HttpClient(); } /// @@ -332,123 +287,15 @@ public MsgraphgroupsanduserClient(string connectionRuntimeUrl, TokenCredential c /// /// The connection runtime URL from Azure Portal. /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public MsgraphgroupsanduserClient(string connectionRuntimeUrl, string managedIdentityClientId, HttpClient httpClient = null) - : this(connectionRuntimeUrl, CreateManagedIdentityCredential(managedIdentityClientId), httpClient) - { - } - - private static TokenCredential CreateManagedIdentityCredential(string managedIdentityClientId) + public MsgraphgroupsanduserClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { - if (string.IsNullOrEmpty(managedIdentityClientId)) - { - return new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned); - } - - return new ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedClientId(managedIdentityClientId)); } - private async Task GetTokenAsync(CancellationToken cancellationToken) - { - if (this._cachedToken.HasValue && this._cachedToken.Value.ExpiresOn > DateTimeOffset.UtcNow.AddMinutes(5)) - { - return this._cachedToken.Value.Token; - } - - this._cachedToken = await this._credential.GetTokenAsync( - new TokenRequestContext(ApiHubScopes), cancellationToken); - return this._cachedToken.Value.Token; - } - - private string ResolveUrl(string path) - { - if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) - { - var baseUri = new Uri(this._connectionRuntimeUrl); - var nextUri = new Uri(path); - if (!string.Equals(baseUri.Scheme, nextUri.Scheme, StringComparison.OrdinalIgnoreCase) || - !string.Equals(baseUri.Host, nextUri.Host, StringComparison.OrdinalIgnoreCase) || - baseUri.Port != nextUri.Port) - { - throw new InvalidOperationException( - $"NextLink URI '{nextUri.Scheme}://{nextUri.Host}:{nextUri.Port}' does not match connection URI '{baseUri.Scheme}://{baseUri.Host}:{baseUri.Port}'. " + - "Refusing to send credentials to an unexpected host."); - } - - return path; - } - - return $"{this._connectionRuntimeUrl}{path}"; - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var errorBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new MsgraphgroupsanduserConnectorException(operation, (int)response.StatusCode, errorBody); - } - - if (typeof(TResponse) == typeof(byte[])) - { - var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken); - return (TResponse)(object)bytes; - } - - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - - if (string.IsNullOrEmpty(responseBody)) - return default; - - return JsonSerializer.Deserialize(responseBody, JsonOptions); - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new MsgraphgroupsanduserConnectorException(operation, (int)response.StatusCode, responseBody); - } - } + /// + public override string ConnectorName => "msgraphgroupsanduser"; /// /// List Users @@ -554,18 +401,6 @@ public async Task GetMemberGroupsAsync(string objectIDO return await this.CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken); } - public void Dispose() - { - if (this._ownsHttpClient) - { - this._httpClient?.Dispose(); - } - - if (this._ownsCredential && this._credential is IDisposable disposableCredential) - { - disposableCredential.Dispose(); - } - } } #endregion Client diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365Extensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365Extensions.cs index b32a533..e4091e0 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365Extensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365Extensions.cs @@ -1,4 +1,4 @@ -// Office365Extensions.cs - Auto-generated DirectClient SDK +// Office365Extensions.cs - Auto-generated Connectors SDK // Do not edit this file directly. #nullable disable @@ -18,7 +18,7 @@ using Azure.Identity; using Microsoft.Azure.Connectors.Sdk; -namespace Microsoft.Azure.Connectors.DirectClient.Office365; +namespace Microsoft.Azure.Connectors.Sdk.Office365; #region Types @@ -2525,67 +2525,21 @@ public static class OnSharedMailboxNewEmail #region Client -/// -/// Exception thrown when office365 connector operations fail. -/// -public class Office365ConnectorException : Exception -{ - private const int MaxResponseBodyLength = 2000; - - public string Operation { get; } - public int StatusCode { get; } - public string ResponseBody { get; } - - public Office365ConnectorException(string operation, int statusCode, string responseBody) - : base($"{operation} failed with status {statusCode}: {TruncateBody(responseBody)}") - { - this.Operation = operation; - this.StatusCode = statusCode; - this.ResponseBody = responseBody; - } - - private static string TruncateBody(string body) - { - if (string.IsNullOrEmpty(body) || body.Length <= MaxResponseBodyLength) - return body; - return body.Substring(0, MaxResponseBodyLength) + "...[truncated]"; - } -} - /// /// Typed client for office365 connector. /// -public class Office365Client : IDisposable +public class Office365Client : ConnectorClientBase { - private static readonly string[] ApiHubScopes = ["https://apihub.azure.com/.default"]; - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - - private readonly string _connectionRuntimeUrl; - private readonly HttpClient _httpClient; - private readonly bool _ownsHttpClient; - private readonly bool _ownsCredential; - private readonly TokenCredential _credential; - private AccessToken? _cachedToken; - /// /// Creates a new Office365Client. /// /// The connection runtime URL from Azure Portal. /// Optional credential. Defaults to . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public Office365Client(string connectionRuntimeUrl, TokenCredential credential = null, HttpClient httpClient = null) + public Office365Client(string connectionRuntimeUrl, TokenCredential credential = null, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, credential, options, httpClient) { - this._connectionRuntimeUrl = connectionRuntimeUrl?.TrimEnd('/') - ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl)); - this._credential = credential ?? new DefaultAzureCredential(); - this._ownsCredential = credential == null; - this._ownsHttpClient = httpClient == null; - this._httpClient = httpClient ?? new HttpClient(); } /// @@ -2593,123 +2547,15 @@ public Office365Client(string connectionRuntimeUrl, TokenCredential credential = /// /// The connection runtime URL from Azure Portal. /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public Office365Client(string connectionRuntimeUrl, string managedIdentityClientId, HttpClient httpClient = null) - : this(connectionRuntimeUrl, CreateManagedIdentityCredential(managedIdentityClientId), httpClient) - { - } - - private static TokenCredential CreateManagedIdentityCredential(string managedIdentityClientId) - { - if (string.IsNullOrEmpty(managedIdentityClientId)) - { - return new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned); - } - - return new ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedClientId(managedIdentityClientId)); - } - - private async Task GetTokenAsync(CancellationToken cancellationToken) - { - if (this._cachedToken.HasValue && this._cachedToken.Value.ExpiresOn > DateTimeOffset.UtcNow.AddMinutes(5)) - { - return this._cachedToken.Value.Token; - } - - this._cachedToken = await this._credential.GetTokenAsync( - new TokenRequestContext(ApiHubScopes), cancellationToken); - return this._cachedToken.Value.Token; - } - - private string ResolveUrl(string path) + public Office365Client(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { - if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) - { - var baseUri = new Uri(this._connectionRuntimeUrl); - var nextUri = new Uri(path); - if (!string.Equals(baseUri.Scheme, nextUri.Scheme, StringComparison.OrdinalIgnoreCase) || - !string.Equals(baseUri.Host, nextUri.Host, StringComparison.OrdinalIgnoreCase) || - baseUri.Port != nextUri.Port) - { - throw new InvalidOperationException( - $"NextLink URI '{nextUri.Scheme}://{nextUri.Host}:{nextUri.Port}' does not match connection URI '{baseUri.Scheme}://{baseUri.Host}:{baseUri.Port}'. " + - "Refusing to send credentials to an unexpected host."); - } - - return path; - } - - return $"{this._connectionRuntimeUrl}{path}"; - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var errorBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new Office365ConnectorException(operation, (int)response.StatusCode, errorBody); - } - - if (typeof(TResponse) == typeof(byte[])) - { - var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken); - return (TResponse)(object)bytes; - } - - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - - if (string.IsNullOrEmpty(responseBody)) - return default; - - return JsonSerializer.Deserialize(responseBody, JsonOptions); } - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new Office365ConnectorException(operation, (int)response.StatusCode, responseBody); - } - } + /// + public override string ConnectorName => "office365"; /// /// Get Outlook category names @@ -3475,18 +3321,6 @@ public async Task SharedMailboxSendEmailAsync(SharedMailboxSendEmailInput input, await this.CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken); } - public void Dispose() - { - if (this._ownsHttpClient) - { - this._httpClient?.Dispose(); - } - - if (this._ownsCredential && this._credential is IDisposable disposableCredential) - { - disposableCredential.Dispose(); - } - } } #endregion Client diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365usersExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365usersExtensions.cs index 9fa26f7..2f67895 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365usersExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365usersExtensions.cs @@ -1,4 +1,4 @@ -// Office365usersExtensions.cs - Auto-generated DirectClient SDK +// Office365usersExtensions.cs - Auto-generated Connectors SDK // Do not edit this file directly. #nullable disable @@ -17,7 +17,7 @@ using Azure.Identity; using Microsoft.Azure.Connectors.Sdk; -namespace Microsoft.Azure.Connectors.DirectClient.Office365users; +namespace Microsoft.Azure.Connectors.Sdk.Office365users; #region Types @@ -675,67 +675,21 @@ public class GraphUserUpdateable #region Client -/// -/// Exception thrown when office365users connector operations fail. -/// -public class Office365usersConnectorException : Exception -{ - private const int MaxResponseBodyLength = 2000; - - public string Operation { get; } - public int StatusCode { get; } - public string ResponseBody { get; } - - public Office365usersConnectorException(string operation, int statusCode, string responseBody) - : base($"{operation} failed with status {statusCode}: {TruncateBody(responseBody)}") - { - this.Operation = operation; - this.StatusCode = statusCode; - this.ResponseBody = responseBody; - } - - private static string TruncateBody(string body) - { - if (string.IsNullOrEmpty(body) || body.Length <= MaxResponseBodyLength) - return body; - return body.Substring(0, MaxResponseBodyLength) + "...[truncated]"; - } -} - /// /// Typed client for office365users connector. /// -public class Office365usersClient : IDisposable +public class Office365usersClient : ConnectorClientBase { - private static readonly string[] ApiHubScopes = ["https://apihub.azure.com/.default"]; - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - - private readonly string _connectionRuntimeUrl; - private readonly HttpClient _httpClient; - private readonly bool _ownsHttpClient; - private readonly bool _ownsCredential; - private readonly TokenCredential _credential; - private AccessToken? _cachedToken; - /// /// Creates a new Office365usersClient. /// /// The connection runtime URL from Azure Portal. /// Optional credential. Defaults to . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public Office365usersClient(string connectionRuntimeUrl, TokenCredential credential = null, HttpClient httpClient = null) + public Office365usersClient(string connectionRuntimeUrl, TokenCredential credential = null, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, credential, options, httpClient) { - this._connectionRuntimeUrl = connectionRuntimeUrl?.TrimEnd('/') - ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl)); - this._credential = credential ?? new DefaultAzureCredential(); - this._ownsCredential = credential == null; - this._ownsHttpClient = httpClient == null; - this._httpClient = httpClient ?? new HttpClient(); } /// @@ -743,123 +697,15 @@ public Office365usersClient(string connectionRuntimeUrl, TokenCredential credent /// /// The connection runtime URL from Azure Portal. /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public Office365usersClient(string connectionRuntimeUrl, string managedIdentityClientId, HttpClient httpClient = null) - : this(connectionRuntimeUrl, CreateManagedIdentityCredential(managedIdentityClientId), httpClient) - { - } - - private static TokenCredential CreateManagedIdentityCredential(string managedIdentityClientId) + public Office365usersClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { - if (string.IsNullOrEmpty(managedIdentityClientId)) - { - return new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned); - } - - return new ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedClientId(managedIdentityClientId)); } - private async Task GetTokenAsync(CancellationToken cancellationToken) - { - if (this._cachedToken.HasValue && this._cachedToken.Value.ExpiresOn > DateTimeOffset.UtcNow.AddMinutes(5)) - { - return this._cachedToken.Value.Token; - } - - this._cachedToken = await this._credential.GetTokenAsync( - new TokenRequestContext(ApiHubScopes), cancellationToken); - return this._cachedToken.Value.Token; - } - - private string ResolveUrl(string path) - { - if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) - { - var baseUri = new Uri(this._connectionRuntimeUrl); - var nextUri = new Uri(path); - if (!string.Equals(baseUri.Scheme, nextUri.Scheme, StringComparison.OrdinalIgnoreCase) || - !string.Equals(baseUri.Host, nextUri.Host, StringComparison.OrdinalIgnoreCase) || - baseUri.Port != nextUri.Port) - { - throw new InvalidOperationException( - $"NextLink URI '{nextUri.Scheme}://{nextUri.Host}:{nextUri.Port}' does not match connection URI '{baseUri.Scheme}://{baseUri.Host}:{baseUri.Port}'. " + - "Refusing to send credentials to an unexpected host."); - } - - return path; - } - - return $"{this._connectionRuntimeUrl}{path}"; - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var errorBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new Office365usersConnectorException(operation, (int)response.StatusCode, errorBody); - } - - if (typeof(TResponse) == typeof(byte[])) - { - var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken); - return (TResponse)(object)bytes; - } - - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - - if (string.IsNullOrEmpty(responseBody)) - return default; - - return JsonSerializer.Deserialize(responseBody, JsonOptions); - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new Office365usersConnectorException(operation, (int)response.StatusCode, responseBody); - } - } + /// + public override string ConnectorName => "office365users"; /// /// Update my profile @@ -1078,18 +924,6 @@ public async Task UserProfileAsync(string userUPN, string selectField return await this.CallConnectorAsync(HttpMethod.Get, path, cancellationToken: cancellationToken); } - public void Dispose() - { - if (this._ownsHttpClient) - { - this._httpClient?.Dispose(); - } - - if (this._ownsCredential && this._credential is IDisposable disposableCredential) - { - disposableCredential.Dispose(); - } - } } #endregion Client diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/OnedriveforbusinessExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/OnedriveforbusinessExtensions.cs index 0d8e5be..dd5abf5 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/OnedriveforbusinessExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/OnedriveforbusinessExtensions.cs @@ -1,4 +1,4 @@ -// OnedriveforbusinessExtensions.cs - Auto-generated DirectClient SDK +// OnedriveforbusinessExtensions.cs - Auto-generated Connectors SDK // Do not edit this file directly. #nullable disable @@ -18,7 +18,7 @@ using Azure.Identity; using Microsoft.Azure.Connectors.Sdk; -namespace Microsoft.Azure.Connectors.DirectClient.Onedriveforbusiness; +namespace Microsoft.Azure.Connectors.Sdk.Onedriveforbusiness; #region Types @@ -343,67 +343,21 @@ public static class OnUpdatedFiles #region Client -/// -/// Exception thrown when onedriveforbusiness connector operations fail. -/// -public class OnedriveforbusinessConnectorException : Exception -{ - private const int MaxResponseBodyLength = 2000; - - public string Operation { get; } - public int StatusCode { get; } - public string ResponseBody { get; } - - public OnedriveforbusinessConnectorException(string operation, int statusCode, string responseBody) - : base($"{operation} failed with status {statusCode}: {TruncateBody(responseBody)}") - { - this.Operation = operation; - this.StatusCode = statusCode; - this.ResponseBody = responseBody; - } - - private static string TruncateBody(string body) - { - if (string.IsNullOrEmpty(body) || body.Length <= MaxResponseBodyLength) - return body; - return body.Substring(0, MaxResponseBodyLength) + "...[truncated]"; - } -} - /// /// Typed client for onedriveforbusiness connector. /// -public class OnedriveforbusinessClient : IDisposable +public class OnedriveforbusinessClient : ConnectorClientBase { - private static readonly string[] ApiHubScopes = ["https://apihub.azure.com/.default"]; - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - - private readonly string _connectionRuntimeUrl; - private readonly HttpClient _httpClient; - private readonly bool _ownsHttpClient; - private readonly bool _ownsCredential; - private readonly TokenCredential _credential; - private AccessToken? _cachedToken; - /// /// Creates a new OnedriveforbusinessClient. /// /// The connection runtime URL from Azure Portal. /// Optional credential. Defaults to . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public OnedriveforbusinessClient(string connectionRuntimeUrl, TokenCredential credential = null, HttpClient httpClient = null) + public OnedriveforbusinessClient(string connectionRuntimeUrl, TokenCredential credential = null, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, credential, options, httpClient) { - this._connectionRuntimeUrl = connectionRuntimeUrl?.TrimEnd('/') - ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl)); - this._credential = credential ?? new DefaultAzureCredential(); - this._ownsCredential = credential == null; - this._ownsHttpClient = httpClient == null; - this._httpClient = httpClient ?? new HttpClient(); } /// @@ -411,123 +365,15 @@ public OnedriveforbusinessClient(string connectionRuntimeUrl, TokenCredential cr /// /// The connection runtime URL from Azure Portal. /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public OnedriveforbusinessClient(string connectionRuntimeUrl, string managedIdentityClientId, HttpClient httpClient = null) - : this(connectionRuntimeUrl, CreateManagedIdentityCredential(managedIdentityClientId), httpClient) - { - } - - private static TokenCredential CreateManagedIdentityCredential(string managedIdentityClientId) - { - if (string.IsNullOrEmpty(managedIdentityClientId)) - { - return new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned); - } - - return new ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedClientId(managedIdentityClientId)); - } - - private async Task GetTokenAsync(CancellationToken cancellationToken) - { - if (this._cachedToken.HasValue && this._cachedToken.Value.ExpiresOn > DateTimeOffset.UtcNow.AddMinutes(5)) - { - return this._cachedToken.Value.Token; - } - - this._cachedToken = await this._credential.GetTokenAsync( - new TokenRequestContext(ApiHubScopes), cancellationToken); - return this._cachedToken.Value.Token; - } - - private string ResolveUrl(string path) + public OnedriveforbusinessClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { - if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) - { - var baseUri = new Uri(this._connectionRuntimeUrl); - var nextUri = new Uri(path); - if (!string.Equals(baseUri.Scheme, nextUri.Scheme, StringComparison.OrdinalIgnoreCase) || - !string.Equals(baseUri.Host, nextUri.Host, StringComparison.OrdinalIgnoreCase) || - baseUri.Port != nextUri.Port) - { - throw new InvalidOperationException( - $"NextLink URI '{nextUri.Scheme}://{nextUri.Host}:{nextUri.Port}' does not match connection URI '{baseUri.Scheme}://{baseUri.Host}:{baseUri.Port}'. " + - "Refusing to send credentials to an unexpected host."); - } - - return path; - } - - return $"{this._connectionRuntimeUrl}{path}"; - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var errorBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new OnedriveforbusinessConnectorException(operation, (int)response.StatusCode, errorBody); - } - - if (typeof(TResponse) == typeof(byte[])) - { - var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken); - return (TResponse)(object)bytes; - } - - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - - if (string.IsNullOrEmpty(responseBody)) - return default; - - return JsonSerializer.Deserialize(responseBody, JsonOptions); } - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new OnedriveforbusinessConnectorException(operation, (int)response.StatusCode, responseBody); - } - } + /// + public override string ConnectorName => "onedriveforbusiness"; /// /// Get file metadata @@ -937,18 +783,6 @@ public ConnectorPageable ListFolderAsync(string (nextLink, cancellationToken) => this.CallConnectorAsync(HttpMethod.Get, nextLink, cancellationToken: cancellationToken)); } - public void Dispose() - { - if (this._ownsHttpClient) - { - this._httpClient?.Dispose(); - } - - if (this._ownsCredential && this._credential is IDisposable disposableCredential) - { - disposableCredential.Dispose(); - } - } } #endregion Client diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/SharepointonlineExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/SharepointonlineExtensions.cs index f748d2a..5c09a93 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/SharepointonlineExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/SharepointonlineExtensions.cs @@ -1,4 +1,4 @@ -// SharepointonlineExtensions.cs - Auto-generated DirectClient SDK +// SharepointonlineExtensions.cs - Auto-generated Connectors SDK // Do not edit this file directly. #nullable disable @@ -18,7 +18,7 @@ using Azure.Identity; using Microsoft.Azure.Connectors.Sdk; -namespace Microsoft.Azure.Connectors.DirectClient.Sharepointonline; +namespace Microsoft.Azure.Connectors.Sdk.Sharepointonline; #region Types @@ -1234,67 +1234,21 @@ public static class OnUpdatedFile #region Client -/// -/// Exception thrown when sharepointonline connector operations fail. -/// -public class SharepointonlineConnectorException : Exception -{ - private const int MaxResponseBodyLength = 2000; - - public string Operation { get; } - public int StatusCode { get; } - public string ResponseBody { get; } - - public SharepointonlineConnectorException(string operation, int statusCode, string responseBody) - : base($"{operation} failed with status {statusCode}: {TruncateBody(responseBody)}") - { - this.Operation = operation; - this.StatusCode = statusCode; - this.ResponseBody = responseBody; - } - - private static string TruncateBody(string body) - { - if (string.IsNullOrEmpty(body) || body.Length <= MaxResponseBodyLength) - return body; - return body.Substring(0, MaxResponseBodyLength) + "...[truncated]"; - } -} - /// /// Typed client for sharepointonline connector. /// -public class SharepointonlineClient : IDisposable +public class SharepointonlineClient : ConnectorClientBase { - private static readonly string[] ApiHubScopes = ["https://apihub.azure.com/.default"]; - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - - private readonly string _connectionRuntimeUrl; - private readonly HttpClient _httpClient; - private readonly bool _ownsHttpClient; - private readonly bool _ownsCredential; - private readonly TokenCredential _credential; - private AccessToken? _cachedToken; - /// /// Creates a new SharepointonlineClient. /// /// The connection runtime URL from Azure Portal. /// Optional credential. Defaults to . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public SharepointonlineClient(string connectionRuntimeUrl, TokenCredential credential = null, HttpClient httpClient = null) + public SharepointonlineClient(string connectionRuntimeUrl, TokenCredential credential = null, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, credential, options, httpClient) { - this._connectionRuntimeUrl = connectionRuntimeUrl?.TrimEnd('/') - ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl)); - this._credential = credential ?? new DefaultAzureCredential(); - this._ownsCredential = credential == null; - this._ownsHttpClient = httpClient == null; - this._httpClient = httpClient ?? new HttpClient(); } /// @@ -1302,123 +1256,15 @@ public SharepointonlineClient(string connectionRuntimeUrl, TokenCredential crede /// /// The connection runtime URL from Azure Portal. /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public SharepointonlineClient(string connectionRuntimeUrl, string managedIdentityClientId, HttpClient httpClient = null) - : this(connectionRuntimeUrl, CreateManagedIdentityCredential(managedIdentityClientId), httpClient) - { - } - - private static TokenCredential CreateManagedIdentityCredential(string managedIdentityClientId) - { - if (string.IsNullOrEmpty(managedIdentityClientId)) - { - return new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned); - } - - return new ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedClientId(managedIdentityClientId)); - } - - private async Task GetTokenAsync(CancellationToken cancellationToken) - { - if (this._cachedToken.HasValue && this._cachedToken.Value.ExpiresOn > DateTimeOffset.UtcNow.AddMinutes(5)) - { - return this._cachedToken.Value.Token; - } - - this._cachedToken = await this._credential.GetTokenAsync( - new TokenRequestContext(ApiHubScopes), cancellationToken); - return this._cachedToken.Value.Token; - } - - private string ResolveUrl(string path) - { - if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) - { - var baseUri = new Uri(this._connectionRuntimeUrl); - var nextUri = new Uri(path); - if (!string.Equals(baseUri.Scheme, nextUri.Scheme, StringComparison.OrdinalIgnoreCase) || - !string.Equals(baseUri.Host, nextUri.Host, StringComparison.OrdinalIgnoreCase) || - baseUri.Port != nextUri.Port) - { - throw new InvalidOperationException( - $"NextLink URI '{nextUri.Scheme}://{nextUri.Host}:{nextUri.Port}' does not match connection URI '{baseUri.Scheme}://{baseUri.Host}:{baseUri.Port}'. " + - "Refusing to send credentials to an unexpected host."); - } - - return path; - } - - return $"{this._connectionRuntimeUrl}{path}"; - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) + public SharepointonlineClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var errorBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new SharepointonlineConnectorException(operation, (int)response.StatusCode, errorBody); - } - - if (typeof(TResponse) == typeof(byte[])) - { - var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken); - return (TResponse)(object)bytes; - } - - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - - if (string.IsNullOrEmpty(responseBody)) - return default; - - return JsonSerializer.Deserialize(responseBody, JsonOptions); } - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new SharepointonlineConnectorException(operation, (int)response.StatusCode, responseBody); - } - } + /// + public override string ConnectorName => "sharepointonline"; /// /// Get list metadata @@ -2579,18 +2425,6 @@ public async Task> ExtractFolderAsync([DynamicValues("GetData return await this.CallConnectorAsync>(HttpMethod.Post, path, cancellationToken: cancellationToken); } - public void Dispose() - { - if (this._ownsHttpClient) - { - this._httpClient?.Dispose(); - } - - if (this._ownsCredential && this._credential is IDisposable disposableCredential) - { - disposableCredential.Dispose(); - } - } } #endregion Client diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/SmtpExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/SmtpExtensions.cs index 2d1b7c3..c673345 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/SmtpExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/SmtpExtensions.cs @@ -1,4 +1,4 @@ -// SmtpExtensions.cs - Auto-generated DirectClient SDK +// SmtpExtensions.cs - Auto-generated Connectors SDK // Do not edit this file directly. #nullable disable @@ -15,13 +15,14 @@ using System.Threading.Tasks; using Azure.Core; using Azure.Identity; +using Microsoft.Azure.Connectors.Sdk; -namespace Microsoft.Azure.Connectors.DirectClient.Smtp; +namespace Microsoft.Azure.Connectors.Sdk.Smtp; #region Types /// -/// Email attachment (V2) +/// AttachmentV2 /// public class Attachment { @@ -39,7 +40,7 @@ public class Attachment } /// -/// SMTP email (v3) +/// EmailV3 /// public class Email { @@ -74,71 +75,43 @@ public class Email public List Attachments { get; set; } } -#endregion Types - -#region Client - /// -/// Exception thrown when smtp connector operations fail. +/// Item in Attachments to be sent along with the email /// -public class SmtpConnectorException : Exception +public class AttachmentV2 { - private const int MaxResponseBodyLength = 2000; + /// Content data + public string ContentData { get; set; } - public string Operation { get; } - public int StatusCode { get; } - public string ResponseBody { get; } + /// Content type + public string ContentType { get; set; } - public SmtpConnectorException(string operation, int statusCode, string responseBody) - : base($"{operation} failed with status {statusCode}: {TruncateBody(responseBody)}") - { - this.Operation = operation; - this.StatusCode = statusCode; - this.ResponseBody = responseBody; - } + /// File name + public string FileName { get; set; } - private static string TruncateBody(string body) - { - if (string.IsNullOrEmpty(body) || body.Length <= MaxResponseBodyLength) - return body; - return body.Substring(0, MaxResponseBodyLength) + "...[truncated]"; - } + /// Content id + public string ContentId { get; set; } } +#endregion Types + +#region Client + /// /// Typed client for smtp connector. /// -public class SmtpClient : IDisposable +public class SmtpClient : ConnectorClientBase { - private static readonly string[] ApiHubScopes = ["https://apihub.azure.com/.default"]; - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - - private readonly string _connectionRuntimeUrl; - private readonly HttpClient _httpClient; - private readonly bool _ownsHttpClient; - private readonly bool _ownsCredential; - private readonly TokenCredential _credential; - private AccessToken? _cachedToken; - /// /// Creates a new SmtpClient. /// /// The connection runtime URL from Azure Portal. /// Optional credential. Defaults to . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public SmtpClient(string connectionRuntimeUrl, TokenCredential credential = null, HttpClient httpClient = null) + public SmtpClient(string connectionRuntimeUrl, TokenCredential credential = null, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, credential, options, httpClient) { - this._connectionRuntimeUrl = connectionRuntimeUrl?.TrimEnd('/') - ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl)); - this._credential = credential ?? new DefaultAzureCredential(); - this._ownsCredential = credential == null; - this._ownsHttpClient = httpClient == null; - this._httpClient = httpClient ?? new HttpClient(); } /// @@ -146,123 +119,15 @@ public SmtpClient(string connectionRuntimeUrl, TokenCredential credential = null /// /// The connection runtime URL from Azure Portal. /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public SmtpClient(string connectionRuntimeUrl, string managedIdentityClientId, HttpClient httpClient = null) - : this(connectionRuntimeUrl, CreateManagedIdentityCredential(managedIdentityClientId), httpClient) - { - } - - private static TokenCredential CreateManagedIdentityCredential(string managedIdentityClientId) - { - if (string.IsNullOrEmpty(managedIdentityClientId)) - { - return new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned); - } - - return new ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedClientId(managedIdentityClientId)); - } - - private async Task GetTokenAsync(CancellationToken cancellationToken) - { - if (this._cachedToken.HasValue && this._cachedToken.Value.ExpiresOn > DateTimeOffset.UtcNow.AddMinutes(5)) - { - return this._cachedToken.Value.Token; - } - - this._cachedToken = await this._credential.GetTokenAsync( - new TokenRequestContext(ApiHubScopes), cancellationToken); - return this._cachedToken.Value.Token; - } - - private string ResolveUrl(string path) - { - if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) - { - var baseUri = new Uri(this._connectionRuntimeUrl); - var nextUri = new Uri(path); - if (!string.Equals(baseUri.Scheme, nextUri.Scheme, StringComparison.OrdinalIgnoreCase) || - !string.Equals(baseUri.Host, nextUri.Host, StringComparison.OrdinalIgnoreCase) || - baseUri.Port != nextUri.Port) - { - throw new InvalidOperationException( - $"NextLink URI '{nextUri.Scheme}://{nextUri.Host}:{nextUri.Port}' does not match connection URI '{baseUri.Scheme}://{baseUri.Host}:{baseUri.Port}'. " + - "Refusing to send credentials to an unexpected host."); - } - - return path; - } - - return $"{this._connectionRuntimeUrl}{path}"; - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) + public SmtpClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var errorBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new SmtpConnectorException(operation, (int)response.StatusCode, errorBody); - } - - if (typeof(TResponse) == typeof(byte[])) - { - var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken); - return (TResponse)(object)bytes; - } - - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - - if (string.IsNullOrEmpty(responseBody)) - return default; - - return JsonSerializer.Deserialize(responseBody, JsonOptions); } - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new SmtpConnectorException(operation, (int)response.StatusCode, responseBody); - } - } + /// + public override string ConnectorName => "smtp"; /// /// Send Email (V3) @@ -276,18 +141,6 @@ public async Task SendEmailAsync(Email input, CancellationToken cancellationToke await this.CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken); } - public void Dispose() - { - if (this._ownsHttpClient) - { - this._httpClient?.Dispose(); - } - - if (this._ownsCredential && this._credential is IDisposable disposableCredential) - { - disposableCredential.Dispose(); - } - } } #endregion Client diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/TeamsExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/TeamsExtensions.cs index 83944d4..30df4de 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/TeamsExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/TeamsExtensions.cs @@ -1,4 +1,4 @@ -// TeamsExtensions.cs - Auto-generated DirectClient SDK +// TeamsExtensions.cs - Auto-generated Connectors SDK // Do not edit this file directly. #nullable disable @@ -18,7 +18,7 @@ using Azure.Identity; using Microsoft.Azure.Connectors.Sdk; -namespace Microsoft.Azure.Connectors.DirectClient.Teams; +namespace Microsoft.Azure.Connectors.Sdk.Teams; #region Types @@ -1440,67 +1440,21 @@ public static class OnTeamMemberAdded #region Client -/// -/// Exception thrown when teams connector operations fail. -/// -public class TeamsConnectorException : Exception -{ - private const int MaxResponseBodyLength = 2000; - - public string Operation { get; } - public int StatusCode { get; } - public string ResponseBody { get; } - - public TeamsConnectorException(string operation, int statusCode, string responseBody) - : base($"{operation} failed with status {statusCode}: {TruncateBody(responseBody)}") - { - this.Operation = operation; - this.StatusCode = statusCode; - this.ResponseBody = responseBody; - } - - private static string TruncateBody(string body) - { - if (string.IsNullOrEmpty(body) || body.Length <= MaxResponseBodyLength) - return body; - return body.Substring(0, MaxResponseBodyLength) + "...[truncated]"; - } -} - /// /// Typed client for teams connector. /// -public class TeamsClient : IDisposable +public class TeamsClient : ConnectorClientBase { - private static readonly string[] ApiHubScopes = ["https://apihub.azure.com/.default"]; - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - - private readonly string _connectionRuntimeUrl; - private readonly HttpClient _httpClient; - private readonly bool _ownsHttpClient; - private readonly bool _ownsCredential; - private readonly TokenCredential _credential; - private AccessToken? _cachedToken; - /// /// Creates a new TeamsClient. /// /// The connection runtime URL from Azure Portal. /// Optional credential. Defaults to . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public TeamsClient(string connectionRuntimeUrl, TokenCredential credential = null, HttpClient httpClient = null) + public TeamsClient(string connectionRuntimeUrl, TokenCredential credential = null, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, credential, options, httpClient) { - this._connectionRuntimeUrl = connectionRuntimeUrl?.TrimEnd('/') - ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl)); - this._credential = credential ?? new DefaultAzureCredential(); - this._ownsCredential = credential == null; - this._ownsHttpClient = httpClient == null; - this._httpClient = httpClient ?? new HttpClient(); } /// @@ -1508,123 +1462,15 @@ public TeamsClient(string connectionRuntimeUrl, TokenCredential credential = nul /// /// The connection runtime URL from Azure Portal. /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . + /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public TeamsClient(string connectionRuntimeUrl, string managedIdentityClientId, HttpClient httpClient = null) - : this(connectionRuntimeUrl, CreateManagedIdentityCredential(managedIdentityClientId), httpClient) - { - } - - private static TokenCredential CreateManagedIdentityCredential(string managedIdentityClientId) - { - if (string.IsNullOrEmpty(managedIdentityClientId)) - { - return new ManagedIdentityCredential(ManagedIdentityId.SystemAssigned); - } - - return new ManagedIdentityCredential(ManagedIdentityId.FromUserAssignedClientId(managedIdentityClientId)); - } - - private async Task GetTokenAsync(CancellationToken cancellationToken) - { - if (this._cachedToken.HasValue && this._cachedToken.Value.ExpiresOn > DateTimeOffset.UtcNow.AddMinutes(5)) - { - return this._cachedToken.Value.Token; - } - - this._cachedToken = await this._credential.GetTokenAsync( - new TokenRequestContext(ApiHubScopes), cancellationToken); - return this._cachedToken.Value.Token; - } - - private string ResolveUrl(string path) + public TeamsClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { - if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) - { - var baseUri = new Uri(this._connectionRuntimeUrl); - var nextUri = new Uri(path); - if (!string.Equals(baseUri.Scheme, nextUri.Scheme, StringComparison.OrdinalIgnoreCase) || - !string.Equals(baseUri.Host, nextUri.Host, StringComparison.OrdinalIgnoreCase) || - baseUri.Port != nextUri.Port) - { - throw new InvalidOperationException( - $"NextLink URI '{nextUri.Scheme}://{nextUri.Host}:{nextUri.Port}' does not match connection URI '{baseUri.Scheme}://{baseUri.Host}:{baseUri.Port}'. " + - "Refusing to send credentials to an unexpected host."); - } - - return path; - } - - return $"{this._connectionRuntimeUrl}{path}"; - } - - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var errorBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new TeamsConnectorException(operation, (int)response.StatusCode, errorBody); - } - - if (typeof(TResponse) == typeof(byte[])) - { - var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken); - return (TResponse)(object)bytes; - } - - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - - if (string.IsNullOrEmpty(responseBody)) - return default; - - return JsonSerializer.Deserialize(responseBody, JsonOptions); } - private async Task CallConnectorAsync( - HttpMethod method, - string path, - object body = null, - CancellationToken cancellationToken = default) - { - var token = await this.GetTokenAsync(cancellationToken); - var url = this.ResolveUrl(path); - var operation = $"{method} {path}"; - - using var request = new HttpRequestMessage(method, url); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - if (body != null) - { - var json = JsonSerializer.Serialize(body, JsonOptions); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - } - - using var response = await this._httpClient.SendAsync(request, cancellationToken); - - if (!response.IsSuccessStatusCode) - { - var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); - throw new TeamsConnectorException(operation, (int)response.StatusCode, responseBody); - } - } + /// + public override string ConnectorName => "teams"; /// /// Create a Teams meeting @@ -2400,18 +2246,6 @@ public async Task HttpRequestAsync(byte[] input, Cancellation return await this.CallConnectorAsync(HttpMethod.Post, path, input, cancellationToken); } - public void Dispose() - { - if (this._ownsHttpClient) - { - this._httpClient?.Dispose(); - } - - if (this._ownsCredential && this._credential is IDisposable disposableCredential) - { - disposableCredential.Dispose(); - } - } } #endregion Client diff --git a/tests/Microsoft.Azure.Connectors.Sdk.Tests/AzureblobClientTests.cs b/tests/Microsoft.Azure.Connectors.Sdk.Tests/AzureblobClientTests.cs index cbf0f45..02a061d 100644 --- a/tests/Microsoft.Azure.Connectors.Sdk.Tests/AzureblobClientTests.cs +++ b/tests/Microsoft.Azure.Connectors.Sdk.Tests/AzureblobClientTests.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using global::Azure.Core; -using Microsoft.Azure.Connectors.DirectClient.Azureblob; +using Microsoft.Azure.Connectors.Sdk.Azureblob; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Moq.Protected; @@ -146,7 +146,7 @@ public async Task GetFileMetadataAsync_WithErrorResponse_ThrowsConnectorExceptio // Act & Assert var exception = await Assert - .ThrowsExactlyAsync(async () => + .ThrowsExactlyAsync(async () => await client .GetFileMetadataAsync( storageAccountNameOrBlobEndpoint: "mystorageaccount", @@ -160,10 +160,10 @@ await client } [TestMethod] - public void AzureblobConnectorException_ShouldContainExpectedProperties() + public void ConnectorException_ShouldContainExpectedProperties() { // Arrange & Act - var exception = new AzureblobConnectorException( + var exception = new ConnectorException("azureblob", operation: "GET /v2/datasets/mystorageaccount/files/abc", statusCode: 403, responseBody: "Access denied"); diff --git a/tests/Microsoft.Azure.Connectors.Sdk.Tests/AzuremonitorlogsClientTests.cs b/tests/Microsoft.Azure.Connectors.Sdk.Tests/AzuremonitorlogsClientTests.cs index 7f41e98..3d4e797 100644 --- a/tests/Microsoft.Azure.Connectors.Sdk.Tests/AzuremonitorlogsClientTests.cs +++ b/tests/Microsoft.Azure.Connectors.Sdk.Tests/AzuremonitorlogsClientTests.cs @@ -11,7 +11,7 @@ using System.Threading; using System.Threading.Tasks; using global::Azure.Core; -using Microsoft.Azure.Connectors.DirectClient.Azuremonitorlogs; +using Microsoft.Azure.Connectors.Sdk.Azuremonitorlogs; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Moq.Protected; @@ -151,7 +151,7 @@ public async Task QueryDataAsync_WithErrorResponse_ThrowsConnectorException() // Act & Assert var exception = await Assert - .ThrowsExactlyAsync(async () => + .ThrowsExactlyAsync(async () => await client .QueryDataAsync( input: "invalid query |||", @@ -226,10 +226,10 @@ public async Task VisualizeQueryAsync_WithMockedResponse_ReturnsExpectedResult() } [TestMethod] - public void AzuremonitorlogsConnectorException_ShouldContainExpectedProperties() + public void ConnectorException_ShouldContainExpectedProperties() { // Arrange & Act - var exception = new AzuremonitorlogsConnectorException( + var exception = new ConnectorException("azuremonitorlogs", operation: "POST /queryData", statusCode: 403, responseBody: "Access denied"); diff --git a/tests/Microsoft.Azure.Connectors.Sdk.Tests/ConnectorConstantsTests.cs b/tests/Microsoft.Azure.Connectors.Sdk.Tests/ConnectorConstantsTests.cs index a404e20..96ed04e 100644 --- a/tests/Microsoft.Azure.Connectors.Sdk.Tests/ConnectorConstantsTests.cs +++ b/tests/Microsoft.Azure.Connectors.Sdk.Tests/ConnectorConstantsTests.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Reflection; -using Microsoft.Azure.Connectors.DirectClient.Office365; +using Microsoft.Azure.Connectors.Sdk.Office365; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.Azure.Connectors.Sdk.Tests @@ -21,9 +21,9 @@ public class ConnectorConstantsTests [TestMethod] public void ConnectorNames_Office365_MatchesRegisteredConnector() { - // Assert — constant is registered in DirectClientConnectors.AvailableConnectors + // Assert — constant is registered in SdkConnectors.AvailableConnectors CollectionAssert.Contains( - Microsoft.Azure.Connectors.DirectClient.DirectClientConnectors.AvailableConnectors, + Microsoft.Azure.Connectors.Sdk.SdkConnectors.AvailableConnectors, ConnectorNames.Office365); } @@ -31,7 +31,7 @@ public void ConnectorNames_Office365_MatchesRegisteredConnector() public void ConnectorNames_SharePointOnline_MatchesRegisteredConnector() { CollectionAssert.Contains( - Microsoft.Azure.Connectors.DirectClient.DirectClientConnectors.AvailableConnectors, + Microsoft.Azure.Connectors.Sdk.SdkConnectors.AvailableConnectors, ConnectorNames.Sharepointonline); } @@ -39,7 +39,7 @@ public void ConnectorNames_SharePointOnline_MatchesRegisteredConnector() public void ConnectorNames_Teams_MatchesRegisteredConnector() { CollectionAssert.Contains( - Microsoft.Azure.Connectors.DirectClient.DirectClientConnectors.AvailableConnectors, + Microsoft.Azure.Connectors.Sdk.SdkConnectors.AvailableConnectors, ConnectorNames.Teams); } @@ -54,11 +54,11 @@ public void ConnectorNames_CoversAllRegisteredConnectors() .ToHashSet(); // Assert — every registered connector has a constant - foreach (var connector in Microsoft.Azure.Connectors.DirectClient.DirectClientConnectors.AvailableConnectors) + foreach (var connector in Microsoft.Azure.Connectors.Sdk.SdkConnectors.AvailableConnectors) { Assert.IsTrue( constants.Contains(connector), - $"Connector '{connector}' is in DirectClientConnectors.AvailableConnectors but missing from ConnectorNames."); + $"Connector '{connector}' is in SdkConnectors.AvailableConnectors but missing from ConnectorNames."); } } @@ -72,7 +72,7 @@ public void ConnectorNames_AllConstantsAreRegistered() .Select(field => (string)field.GetRawConstantValue()!) .ToList(); - var registered = Microsoft.Azure.Connectors.DirectClient.DirectClientConnectors.AvailableConnectors; + var registered = Microsoft.Azure.Connectors.Sdk.SdkConnectors.AvailableConnectors; // Assert — every constant in ConnectorNames maps to a registered connector foreach (var constant in constants) @@ -80,7 +80,7 @@ public void ConnectorNames_AllConstantsAreRegistered() CollectionAssert.Contains( registered, constant, - $"ConnectorNames has constant '{constant}' but it's not in DirectClientConnectors.AvailableConnectors."); + $"ConnectorNames has constant '{constant}' but it's not in SdkConnectors.AvailableConnectors."); } } diff --git a/tests/Microsoft.Azure.Connectors.Sdk.Tests/DynamicSchemaTests.cs b/tests/Microsoft.Azure.Connectors.Sdk.Tests/DynamicSchemaTests.cs index d6e8bb2..dceb763 100644 --- a/tests/Microsoft.Azure.Connectors.Sdk.Tests/DynamicSchemaTests.cs +++ b/tests/Microsoft.Azure.Connectors.Sdk.Tests/DynamicSchemaTests.cs @@ -7,7 +7,7 @@ using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; -using Microsoft.Azure.Connectors.DirectClient.Teams; +using Microsoft.Azure.Connectors.Sdk.Teams; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.Azure.Connectors.Sdk.Tests diff --git a/tests/Microsoft.Azure.Connectors.Sdk.Tests/KustoClientTests.cs b/tests/Microsoft.Azure.Connectors.Sdk.Tests/KustoClientTests.cs index 346d9f0..7c343e1 100644 --- a/tests/Microsoft.Azure.Connectors.Sdk.Tests/KustoClientTests.cs +++ b/tests/Microsoft.Azure.Connectors.Sdk.Tests/KustoClientTests.cs @@ -11,7 +11,7 @@ using System.Threading; using System.Threading.Tasks; using global::Azure.Core; -using Microsoft.Azure.Connectors.DirectClient.Kusto; +using Microsoft.Azure.Connectors.Sdk.Kusto; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Moq.Protected; @@ -151,7 +151,7 @@ public async Task ListKustoResultsAsync_WithErrorResponse_ThrowsConnectorExcepti // Act & Assert var exception = await Assert - .ThrowsExactlyAsync(async () => + .ThrowsExactlyAsync(async () => await client .ListKustoResultsAsync( input: new QueryAndListSchema @@ -169,10 +169,10 @@ await client } [TestMethod] - public void KustoConnectorException_ShouldContainExpectedProperties() + public void ConnectorException_ShouldContainExpectedProperties() { // Arrange & Act - var exception = new KustoConnectorException( + var exception = new ConnectorException("kusto", operation: "POST /ListKustoResults/false", statusCode: 403, responseBody: "Access denied"); diff --git a/tests/Microsoft.Azure.Connectors.Sdk.Tests/MqClientTests.cs b/tests/Microsoft.Azure.Connectors.Sdk.Tests/MqClientTests.cs index c0a8be1..cdaf360 100644 --- a/tests/Microsoft.Azure.Connectors.Sdk.Tests/MqClientTests.cs +++ b/tests/Microsoft.Azure.Connectors.Sdk.Tests/MqClientTests.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using global::Azure.Core; -using Microsoft.Azure.Connectors.DirectClient.Mq; +using Microsoft.Azure.Connectors.Sdk.Mq; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Moq.Protected; @@ -191,7 +191,7 @@ public async Task ReadAsync_WithMockedResponse_ReturnsExpectedResult() } [TestMethod] - public async Task ReceiveAsync_WithErrorResponse_ThrowsMqConnectorException() + public async Task ReceiveAsync_WithErrorResponse_ThrowsConnectorException() { // Arrange var mockHandler = new Mock(); @@ -221,7 +221,7 @@ public async Task ReceiveAsync_WithErrorResponse_ThrowsMqConnectorException() // Act & Assert var exception = await Assert - .ThrowsExactlyAsync(async () => + .ThrowsExactlyAsync(async () => await client .ReceiveAsync( new SingleGetValidOptions { Queue = "NONEXISTENT.QUEUE" }, diff --git a/tests/Microsoft.Azure.Connectors.Sdk.Tests/MsgraphgroupsanduserClientTests.cs b/tests/Microsoft.Azure.Connectors.Sdk.Tests/MsgraphgroupsanduserClientTests.cs index 427a53c..2d4b02a 100644 --- a/tests/Microsoft.Azure.Connectors.Sdk.Tests/MsgraphgroupsanduserClientTests.cs +++ b/tests/Microsoft.Azure.Connectors.Sdk.Tests/MsgraphgroupsanduserClientTests.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using global::Azure.Core; -using Microsoft.Azure.Connectors.DirectClient.Msgraphgroupsanduser; +using Microsoft.Azure.Connectors.Sdk.Msgraphgroupsanduser; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Moq.Protected; @@ -165,7 +165,7 @@ public async Task GetGroupPropertiesAsync_WithErrorResponse_ThrowsConnectorExcep // Act & Assert var exception = await Assert - .ThrowsExactlyAsync(async () => + .ThrowsExactlyAsync(async () => await client .GetGroupPropertiesAsync(objectIDOfTheMicrosoftEntraIDGroup: "test-group-id", cancellationToken: CancellationToken.None) .ConfigureAwait(continueOnCapturedContext: false)) diff --git a/tests/Microsoft.Azure.Connectors.Sdk.Tests/Office365ClientTests.cs b/tests/Microsoft.Azure.Connectors.Sdk.Tests/Office365ClientTests.cs index 452392c..b477396 100644 --- a/tests/Microsoft.Azure.Connectors.Sdk.Tests/Office365ClientTests.cs +++ b/tests/Microsoft.Azure.Connectors.Sdk.Tests/Office365ClientTests.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using global::Azure.Core; -using Microsoft.Azure.Connectors.DirectClient.Office365; +using Microsoft.Azure.Connectors.Sdk.Office365; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Moq.Protected; @@ -171,7 +171,7 @@ public async Task GetEmailAsync_WithErrorResponse_ThrowsConnectorException() // Act & Assert var exception = await Assert - .ThrowsExactlyAsync(async () => + .ThrowsExactlyAsync(async () => await client .GetEmailAsync(messageId: "test-message-id", cancellationToken: CancellationToken.None) .ConfigureAwait(continueOnCapturedContext: false)) diff --git a/tests/Microsoft.Azure.Connectors.Sdk.Tests/Office365TriggerPayloadTests.cs b/tests/Microsoft.Azure.Connectors.Sdk.Tests/Office365TriggerPayloadTests.cs index 57b5155..4322f60 100644 --- a/tests/Microsoft.Azure.Connectors.Sdk.Tests/Office365TriggerPayloadTests.cs +++ b/tests/Microsoft.Azure.Connectors.Sdk.Tests/Office365TriggerPayloadTests.cs @@ -3,7 +3,7 @@ //------------------------------------------------------------ using System.Text.Json; -using Microsoft.Azure.Connectors.DirectClient.Office365; +using Microsoft.Azure.Connectors.Sdk.Office365; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.Azure.Connectors.Sdk.Tests diff --git a/tests/Microsoft.Azure.Connectors.Sdk.Tests/Office365usersClientTests.cs b/tests/Microsoft.Azure.Connectors.Sdk.Tests/Office365usersClientTests.cs index 612d912..5c9fbdc 100644 --- a/tests/Microsoft.Azure.Connectors.Sdk.Tests/Office365usersClientTests.cs +++ b/tests/Microsoft.Azure.Connectors.Sdk.Tests/Office365usersClientTests.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using global::Azure.Core; -using Microsoft.Azure.Connectors.DirectClient.Office365users; +using Microsoft.Azure.Connectors.Sdk.Office365users; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Moq.Protected; @@ -315,7 +315,7 @@ public async Task MyProfileAsync_WithErrorResponse_ThrowsConnectorException() // Act & Assert var exception = await Assert - .ThrowsExactlyAsync(async () => + .ThrowsExactlyAsync(async () => await client .MyProfileAsync(cancellationToken: CancellationToken.None) .ConfigureAwait(continueOnCapturedContext: false)) diff --git a/tests/Microsoft.Azure.Connectors.Sdk.Tests/OnedriveforbusinessClientTests.cs b/tests/Microsoft.Azure.Connectors.Sdk.Tests/OnedriveforbusinessClientTests.cs index f3aa587..1173790 100644 --- a/tests/Microsoft.Azure.Connectors.Sdk.Tests/OnedriveforbusinessClientTests.cs +++ b/tests/Microsoft.Azure.Connectors.Sdk.Tests/OnedriveforbusinessClientTests.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using global::Azure.Core; -using Microsoft.Azure.Connectors.DirectClient.Onedriveforbusiness; +using Microsoft.Azure.Connectors.Sdk.Onedriveforbusiness; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Moq.Protected; @@ -146,7 +146,7 @@ public async Task GetFileMetadataAsync_WithErrorResponse_ThrowsConnectorExceptio // Act & Assert var exception = await Assert - .ThrowsExactlyAsync(async () => + .ThrowsExactlyAsync(async () => await client .GetFileMetadataAsync( file: "nonexistent-file-id", @@ -159,10 +159,10 @@ await client } [TestMethod] - public void OnedriveforbusinessConnectorException_ShouldContainExpectedProperties() + public void ConnectorException_ShouldContainExpectedProperties() { // Arrange & Act - var exception = new OnedriveforbusinessConnectorException( + var exception = new ConnectorException("onedriveforbusiness", operation: "GET /datasets/default/files/abc", statusCode: 403, responseBody: "Access denied"); diff --git a/tests/Microsoft.Azure.Connectors.Sdk.Tests/SharepointonlineClientTests.cs b/tests/Microsoft.Azure.Connectors.Sdk.Tests/SharepointonlineClientTests.cs index 930e0d2..c8860b9 100644 --- a/tests/Microsoft.Azure.Connectors.Sdk.Tests/SharepointonlineClientTests.cs +++ b/tests/Microsoft.Azure.Connectors.Sdk.Tests/SharepointonlineClientTests.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using global::Azure.Core; -using Microsoft.Azure.Connectors.DirectClient.Sharepointonline; +using Microsoft.Azure.Connectors.Sdk.Sharepointonline; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Moq.Protected; @@ -146,7 +146,7 @@ public async Task GetAllTablesAsync_WithErrorResponse_ThrowsConnectorException() // Act & Assert var exception = await Assert - .ThrowsExactlyAsync(async () => + .ThrowsExactlyAsync(async () => await client .GetAllTablesAsync(siteAddress: "https://contoso.sharepoint.com/sites/nonexistent", cancellationToken: CancellationToken.None) .ConfigureAwait(continueOnCapturedContext: false)) @@ -157,10 +157,10 @@ await client } [TestMethod] - public void SharepointonlineConnectorException_ShouldContainExpectedProperties() + public void ConnectorException_ShouldContainExpectedProperties() { // Arrange & Act - var exception = new SharepointonlineConnectorException( + var exception = new ConnectorException("sharepointonline", operation: "GET /test", statusCode: 403, responseBody: "Access denied"); diff --git a/tests/Microsoft.Azure.Connectors.Sdk.Tests/SmtpClientTests.cs b/tests/Microsoft.Azure.Connectors.Sdk.Tests/SmtpClientTests.cs index 1ebfdb8..6673458 100644 --- a/tests/Microsoft.Azure.Connectors.Sdk.Tests/SmtpClientTests.cs +++ b/tests/Microsoft.Azure.Connectors.Sdk.Tests/SmtpClientTests.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using global::Azure.Core; -using Microsoft.Azure.Connectors.DirectClient.Smtp; +using Microsoft.Azure.Connectors.Sdk.Smtp; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Moq.Protected; @@ -130,7 +130,7 @@ await client } [TestMethod] - public async Task SendEmailAsync_WithErrorResponse_ThrowsSmtpConnectorException() + public async Task SendEmailAsync_WithErrorResponse_ThrowsConnectorException() { // Arrange var mockHandler = new Mock(); @@ -168,7 +168,7 @@ public async Task SendEmailAsync_WithErrorResponse_ThrowsSmtpConnectorException( // Act & Assert var exception = await Assert - .ThrowsExactlyAsync(async () => + .ThrowsExactlyAsync(async () => await client .SendEmailAsync(input: email, cancellationToken: CancellationToken.None) .ConfigureAwait(continueOnCapturedContext: false)) diff --git a/tests/Microsoft.Azure.Connectors.Sdk.Tests/TeamsClientTests.cs b/tests/Microsoft.Azure.Connectors.Sdk.Tests/TeamsClientTests.cs index 7e26e40..381738a 100644 --- a/tests/Microsoft.Azure.Connectors.Sdk.Tests/TeamsClientTests.cs +++ b/tests/Microsoft.Azure.Connectors.Sdk.Tests/TeamsClientTests.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using global::Azure.Core; -using Microsoft.Azure.Connectors.DirectClient.Teams; +using Microsoft.Azure.Connectors.Sdk.Teams; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Moq.Protected; @@ -165,7 +165,7 @@ public async Task CreateChannelAsync_WithErrorResponse_ThrowsConnectorException( // Act & Assert var exception = await Assert - .ThrowsExactlyAsync(async () => + .ThrowsExactlyAsync(async () => await client .CreateChannelAsync( team: "test-team-id", diff --git a/tests/Microsoft.Azure.Connectors.Sdk.Tests/TriggerCallbackPayloadTests.cs b/tests/Microsoft.Azure.Connectors.Sdk.Tests/TriggerCallbackPayloadTests.cs index 943f033..11fec4b 100644 --- a/tests/Microsoft.Azure.Connectors.Sdk.Tests/TriggerCallbackPayloadTests.cs +++ b/tests/Microsoft.Azure.Connectors.Sdk.Tests/TriggerCallbackPayloadTests.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Text.Json; -using Microsoft.Azure.Connectors.DirectClient.Office365; +using Microsoft.Azure.Connectors.Sdk.Office365; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.Azure.Connectors.Sdk.Tests From 41292092b43219206a6f5899558bb4da7153ec5c Mon Sep 17 00:00:00 2001 From: David Burg Date: Fri, 1 May 2026 20:33:27 -0700 Subject: [PATCH 2/6] fix: address PR review feedback - Remove unused System.Net.Http.Headers using from ConnectorClientBase - Add guard in ResolveUrl for empty _connectionRuntimeUrl - Fix README Quick Start namespace to Sdk.Office365 - Add Operation to ConnectorException description in README - Fix formatting in 5 test files (constructor arg wrapping) --- README.md | 4 ++-- src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6d10c69..a22df84 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ Copy the generated `*Extensions.cs` files to your project. ### 3. Use the Typed Client ```csharp -using Microsoft.Azure.Connectors.DirectClient.Office365; +using Microsoft.Azure.Connectors.Sdk.Office365; using Microsoft.Azure.Connectors.Sdk; // Get connection runtime URL from Azure Portal @@ -112,7 +112,7 @@ var categories = await client.GetOutlookCategoryNamesAsync(); |-----------|-------------| | `ConnectorClientBase` | Abstract base class for all generated clients — provides authentication, retry, OTel tracing, JSON serialization, and SSRF-protected URL resolution | | `ConnectorClientOptions` | Configuration for retry count, timeout, exponential backoff, and initial retry delay | -| `ConnectorException` | Unified exception for connector API failures with `ConnectorName`, `StatusCode`, and `ResponseBody` | +| `ConnectorException` | Unified exception for connector API failures with `ConnectorName`, `Operation`, `StatusCode`, and `ResponseBody` | ### Authentication diff --git a/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs b/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs index b653ce6..ee0b360 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs @@ -3,7 +3,6 @@ //------------------------------------------------------------ using System.Net.Http; -using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; @@ -242,6 +241,13 @@ protected string ResolveUrl(string path) { if (Uri.IsWellFormedUriString(path, UriKind.Absolute)) { + if (string.IsNullOrEmpty(this._connectionRuntimeUrl)) + { + throw new InvalidOperationException( + message: "Cannot validate absolute NextLink URL because no connection runtime URL was configured. " + + "Set ConnectorClientOptions.BaseUri or use a constructor that accepts connectionRuntimeUrl."); + } + var baseUri = new Uri(this._connectionRuntimeUrl); var nextUri = new Uri(path); if (!string.Equals(baseUri.Scheme, nextUri.Scheme, StringComparison.OrdinalIgnoreCase) || From a4e1852aebcb3e2d26d1afc894d65c07d4b44776 Mon Sep 17 00:00:00 2001 From: David Burg Date: Fri, 1 May 2026 21:04:39 -0700 Subject: [PATCH 3/6] fix: add blank line after Client Base heading for markdown lint --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a22df84..64e545f 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ var categories = await client.GetOutlookCategoryNamesAsync(); ## SDK Components ### Client Base + | Component | Description | |-----------|-------------| | `ConnectorClientBase` | Abstract base class for all generated clients — provides authentication, retry, OTel tracing, JSON serialization, and SSRF-protected URL resolution | From 14da73e67f3d3bcb91e7de23637788be1f14e0f4 Mon Sep 17 00:00:00 2001 From: David Burg Date: Mon, 4 May 2026 10:31:37 -0700 Subject: [PATCH 4/6] fix: address PR review round 3 - Make managedIdentityClientId parameter nullable (string?) - Guard ResolveUrl against empty _connectionRuntimeUrl for relative paths - ApplyBaseUri: only set BaseUri when caller did not provide one - Add 'using System;' to README Quick Start snippet - Update DirectClient namespace references in GENERATION.md, Generated/README.md, and trigger-registration SKILL.md - Regenerated all 11 connectors via updated CodefulSdkGenerator --- .github/skills/trigger-registration/SKILL.md | 4 ++-- GENERATION.md | 2 +- README.md | 3 ++- .../ConnectorClientBase.cs | 19 ++++++++++++++++--- .../Generated/AzureblobExtensions.cs | 2 +- .../Generated/KustoExtensions.cs | 2 +- .../Generated/MqExtensions.cs | 2 +- .../MsgraphgroupsanduserExtensions.cs | 2 +- .../Generated/Office365Extensions.cs | 2 +- .../Generated/Office365usersExtensions.cs | 2 +- .../OnedriveforbusinessExtensions.cs | 2 +- .../Generated/README.md | 2 +- .../Generated/SharepointonlineExtensions.cs | 2 +- .../Generated/SmtpExtensions.cs | 2 +- .../Generated/TeamsExtensions.cs | 2 +- 15 files changed, 32 insertions(+), 18 deletions(-) diff --git a/.github/skills/trigger-registration/SKILL.md b/.github/skills/trigger-registration/SKILL.md index 4eed4ba..0071888 100644 --- a/.github/skills/trigger-registration/SKILL.md +++ b/.github/skills/trigger-registration/SKILL.md @@ -116,10 +116,10 @@ There is no `ConnectorTrigger` template yet. Use `azd` with an HTTP trigger temp ### Example: ConnectorTrigger Function -Use the `[ConnectorTrigger]` attribute with SDK typed payloads for POCO binding. Payload types are in the `Microsoft.Azure.Connectors.DirectClient.` namespace: +Use the `[ConnectorTrigger]` attribute with SDK typed payloads for POCO binding. Payload types are in the `Microsoft.Azure.Connectors.Sdk.` namespace: ```csharp -using Microsoft.Azure.Connectors.DirectClient.Office365; +using Microsoft.Azure.Connectors.Sdk.Office365; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; diff --git a/GENERATION.md b/GENERATION.md index 193c2bf..9b5271c 100644 --- a/GENERATION.md +++ b/GENERATION.md @@ -118,7 +118,7 @@ LogicAppsCompiler.exe unused --managedConnectors using System.Text.Json.Serialization; // ... other usings -namespace Microsoft.Azure.Connectors.DirectClient.Office365; +namespace Microsoft.Azure.Connectors.Sdk.Office365; #region Types diff --git a/README.md b/README.md index 64e545f..f48a2d1 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,9 @@ Copy the generated `*Extensions.cs` files to your project. ### 3. Use the Typed Client ```csharp -using Microsoft.Azure.Connectors.Sdk.Office365; +using System; using Microsoft.Azure.Connectors.Sdk; +using Microsoft.Azure.Connectors.Sdk.Office365; // Get connection runtime URL from Azure Portal var connectionRuntimeUrl = "https://..."; diff --git a/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs b/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs index ee0b360..e110a93 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs @@ -78,7 +78,7 @@ protected ConnectorClientBase( /// Optional externally managed HttpClient. protected ConnectorClientBase( string connectionRuntimeUrl, - string managedIdentityClientId, + string? managedIdentityClientId, ConnectorClientOptions? options = null, HttpClient? httpClient = null) : this( @@ -262,6 +262,13 @@ protected string ResolveUrl(string path) return path; } + if (string.IsNullOrEmpty(this._connectionRuntimeUrl)) + { + throw new InvalidOperationException( + message: "Cannot resolve relative path because no connection runtime URL was configured. " + + "Set ConnectorClientOptions.BaseUri or use a constructor that accepts connectionRuntimeUrl."); + } + return $"{this._connectionRuntimeUrl}{path}"; } @@ -292,11 +299,17 @@ protected virtual void Dispose(bool disposing) private static ConnectorClientOptions ApplyBaseUri(ConnectorClientOptions? options, string connectionRuntimeUrl) { options ??= new ConnectorClientOptions(); - options.BaseUri = new Uri(connectionRuntimeUrl?.TrimEnd('/') ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl))); + + var uri = new Uri(connectionRuntimeUrl?.TrimEnd('/') ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl))); + + // NOTE(daviburg): Only set BaseUri when the caller did not provide one. + // This avoids silently overwriting a user-specified BaseUri on a shared options instance. + options.BaseUri ??= uri; + return options; } - private static TokenCredential CreateManagedIdentityCredential(string managedIdentityClientId) + private static TokenCredential CreateManagedIdentityCredential(string? managedIdentityClientId) { if (string.IsNullOrEmpty(managedIdentityClientId)) { diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/AzureblobExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/AzureblobExtensions.cs index 5635209..7a4f9d8 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/AzureblobExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/AzureblobExtensions.cs @@ -342,7 +342,7 @@ public AzureblobClient(string connectionRuntimeUrl, TokenCredential credential = /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public AzureblobClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public AzureblobClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/KustoExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/KustoExtensions.cs index 331c9fd..23aab26 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/KustoExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/KustoExtensions.cs @@ -273,7 +273,7 @@ public KustoClient(string connectionRuntimeUrl, TokenCredential credential = nul /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public KustoClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public KustoClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/MqExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/MqExtensions.cs index cc60af5..41b28e9 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/MqExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/MqExtensions.cs @@ -248,7 +248,7 @@ public MqClient(string connectionRuntimeUrl, TokenCredential credential = null, /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public MqClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public MqClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/MsgraphgroupsanduserExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/MsgraphgroupsanduserExtensions.cs index 94c8650..303ae2c 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/MsgraphgroupsanduserExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/MsgraphgroupsanduserExtensions.cs @@ -289,7 +289,7 @@ public MsgraphgroupsanduserClient(string connectionRuntimeUrl, TokenCredential c /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public MsgraphgroupsanduserClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public MsgraphgroupsanduserClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365Extensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365Extensions.cs index e4091e0..dfbec65 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365Extensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365Extensions.cs @@ -2549,7 +2549,7 @@ public Office365Client(string connectionRuntimeUrl, TokenCredential credential = /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public Office365Client(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public Office365Client(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365usersExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365usersExtensions.cs index 2f67895..9452180 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365usersExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365usersExtensions.cs @@ -699,7 +699,7 @@ public Office365usersClient(string connectionRuntimeUrl, TokenCredential credent /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public Office365usersClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public Office365usersClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/OnedriveforbusinessExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/OnedriveforbusinessExtensions.cs index dd5abf5..c73f380 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/OnedriveforbusinessExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/OnedriveforbusinessExtensions.cs @@ -367,7 +367,7 @@ public OnedriveforbusinessClient(string connectionRuntimeUrl, TokenCredential cr /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public OnedriveforbusinessClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public OnedriveforbusinessClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/README.md b/src/Microsoft.Azure.Connectors.Sdk/Generated/README.md index a55480a..d8200ef 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/README.md +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/README.md @@ -19,7 +19,7 @@ This folder contains pre-generated typed connector clients included in the SDK N ## Usage ```csharp -using Microsoft.Azure.Connectors.DirectClient.Office365; +using Microsoft.Azure.Connectors.Sdk.Office365; var connectionRuntimeUrl = "https://..."; // From Azure Portal using var client = new Office365Client(connectionRuntimeUrl); diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/SharepointonlineExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/SharepointonlineExtensions.cs index 5c09a93..bb1d142 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/SharepointonlineExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/SharepointonlineExtensions.cs @@ -1258,7 +1258,7 @@ public SharepointonlineClient(string connectionRuntimeUrl, TokenCredential crede /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public SharepointonlineClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public SharepointonlineClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/SmtpExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/SmtpExtensions.cs index c673345..762d16d 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/SmtpExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/SmtpExtensions.cs @@ -121,7 +121,7 @@ public SmtpClient(string connectionRuntimeUrl, TokenCredential credential = null /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public SmtpClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public SmtpClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/TeamsExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/TeamsExtensions.cs index 30df4de..3952797 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/TeamsExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/TeamsExtensions.cs @@ -1464,7 +1464,7 @@ public TeamsClient(string connectionRuntimeUrl, TokenCredential credential = nul /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public TeamsClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public TeamsClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } From 6c3053b6a5303e11d68b79671063e254d23677a7 Mon Sep 17 00:00:00 2001 From: David Burg Date: Mon, 4 May 2026 10:42:25 -0700 Subject: [PATCH 5/6] fix: validate connectionRuntimeUrl as absolute URI in ApplyBaseUri Use Uri.TryCreate with UriKind.Absolute and throw ArgumentException with actionable message instead of letting UriFormatException propagate. --- .../ConnectorClientBase.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs b/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs index e110a93..56ec566 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/ConnectorClientBase.cs @@ -298,9 +298,18 @@ protected virtual void Dispose(bool disposing) private static ConnectorClientOptions ApplyBaseUri(ConnectorClientOptions? options, string connectionRuntimeUrl) { - options ??= new ConnectorClientOptions(); + ArgumentNullException.ThrowIfNull(connectionRuntimeUrl); + + var trimmed = connectionRuntimeUrl.TrimEnd('/'); + + if (!Uri.TryCreate(trimmed, UriKind.Absolute, out var uri)) + { + throw new ArgumentException( + message: $"The connection runtime URL '{trimmed}' is not a valid absolute URI.", + paramName: nameof(connectionRuntimeUrl)); + } - var uri = new Uri(connectionRuntimeUrl?.TrimEnd('/') ?? throw new ArgumentNullException(nameof(connectionRuntimeUrl))); + options ??= new ConnectorClientOptions(); // NOTE(daviburg): Only set BaseUri when the caller did not provide one. // This avoids silently overwriting a user-specified BaseUri on a shared options instance. From f4fd5e4cecb388854a2cdbb46908e17018b0aaea Mon Sep 17 00:00:00 2001 From: David Burg Date: Mon, 4 May 2026 10:55:38 -0700 Subject: [PATCH 6/6] fix: regenerate with string (not string?) for nullable-disabled files Generated files use #nullable disable so string? triggers CS8632. Regenerated all 11 connectors with string parameter that forwards to the nullable-enabled base class. --- .../Generated/AzureblobExtensions.cs | 2 +- src/Microsoft.Azure.Connectors.Sdk/Generated/KustoExtensions.cs | 2 +- src/Microsoft.Azure.Connectors.Sdk/Generated/MqExtensions.cs | 2 +- .../Generated/MsgraphgroupsanduserExtensions.cs | 2 +- .../Generated/Office365Extensions.cs | 2 +- .../Generated/Office365usersExtensions.cs | 2 +- .../Generated/OnedriveforbusinessExtensions.cs | 2 +- .../Generated/SharepointonlineExtensions.cs | 2 +- src/Microsoft.Azure.Connectors.Sdk/Generated/SmtpExtensions.cs | 2 +- src/Microsoft.Azure.Connectors.Sdk/Generated/TeamsExtensions.cs | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/AzureblobExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/AzureblobExtensions.cs index 7a4f9d8..5635209 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/AzureblobExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/AzureblobExtensions.cs @@ -342,7 +342,7 @@ public AzureblobClient(string connectionRuntimeUrl, TokenCredential credential = /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public AzureblobClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public AzureblobClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/KustoExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/KustoExtensions.cs index 23aab26..331c9fd 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/KustoExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/KustoExtensions.cs @@ -273,7 +273,7 @@ public KustoClient(string connectionRuntimeUrl, TokenCredential credential = nul /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public KustoClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public KustoClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/MqExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/MqExtensions.cs index 41b28e9..cc60af5 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/MqExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/MqExtensions.cs @@ -248,7 +248,7 @@ public MqClient(string connectionRuntimeUrl, TokenCredential credential = null, /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public MqClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public MqClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/MsgraphgroupsanduserExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/MsgraphgroupsanduserExtensions.cs index 303ae2c..94c8650 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/MsgraphgroupsanduserExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/MsgraphgroupsanduserExtensions.cs @@ -289,7 +289,7 @@ public MsgraphgroupsanduserClient(string connectionRuntimeUrl, TokenCredential c /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public MsgraphgroupsanduserClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public MsgraphgroupsanduserClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365Extensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365Extensions.cs index dfbec65..e4091e0 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365Extensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365Extensions.cs @@ -2549,7 +2549,7 @@ public Office365Client(string connectionRuntimeUrl, TokenCredential credential = /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public Office365Client(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public Office365Client(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365usersExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365usersExtensions.cs index 9452180..2f67895 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365usersExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/Office365usersExtensions.cs @@ -699,7 +699,7 @@ public Office365usersClient(string connectionRuntimeUrl, TokenCredential credent /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public Office365usersClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public Office365usersClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/OnedriveforbusinessExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/OnedriveforbusinessExtensions.cs index c73f380..dd5abf5 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/OnedriveforbusinessExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/OnedriveforbusinessExtensions.cs @@ -367,7 +367,7 @@ public OnedriveforbusinessClient(string connectionRuntimeUrl, TokenCredential cr /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public OnedriveforbusinessClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public OnedriveforbusinessClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/SharepointonlineExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/SharepointonlineExtensions.cs index bb1d142..5c09a93 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/SharepointonlineExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/SharepointonlineExtensions.cs @@ -1258,7 +1258,7 @@ public SharepointonlineClient(string connectionRuntimeUrl, TokenCredential crede /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public SharepointonlineClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public SharepointonlineClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/SmtpExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/SmtpExtensions.cs index 762d16d..c673345 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/SmtpExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/SmtpExtensions.cs @@ -121,7 +121,7 @@ public SmtpClient(string connectionRuntimeUrl, TokenCredential credential = null /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public SmtpClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public SmtpClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { } diff --git a/src/Microsoft.Azure.Connectors.Sdk/Generated/TeamsExtensions.cs b/src/Microsoft.Azure.Connectors.Sdk/Generated/TeamsExtensions.cs index 3952797..30df4de 100644 --- a/src/Microsoft.Azure.Connectors.Sdk/Generated/TeamsExtensions.cs +++ b/src/Microsoft.Azure.Connectors.Sdk/Generated/TeamsExtensions.cs @@ -1464,7 +1464,7 @@ public TeamsClient(string connectionRuntimeUrl, TokenCredential credential = nul /// The client ID for user-assigned managed identity. Use null for system-assigned identity with . /// Optional client options for retry, timeout, etc. /// Optional . A new one will be created if not provided. - public TeamsClient(string connectionRuntimeUrl, string? managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) + public TeamsClient(string connectionRuntimeUrl, string managedIdentityClientId, ConnectorClientOptions options = null, HttpClient httpClient = null) : base(connectionRuntimeUrl, managedIdentityClientId, options, httpClient) { }