From 76fba260d0a9966def8cad6c7909847f2e2529aa Mon Sep 17 00:00:00 2001 From: David Burg Date: Fri, 8 May 2026 14:31:02 -0700 Subject: [PATCH 1/8] Upgrade to Azure.Connectors.Sdk 0.9.0-preview.1 Breaking changes addressed: - Package: Microsoft.Azure.Connectors.Sdk -> Azure.Connectors.Sdk - Namespaces: Microsoft.Azure.Connectors.DirectClient.* -> Azure.Connectors.Sdk.* - Models sub-namespace: types now in Azure.Connectors.Sdk.{Connector}.Models - PascalCase client names: SharePointOnlineClient, OneDriveForBusinessClient, MsGraphGroupsAndUsersClient, AzureBlobClient - Constructors: removed managedIdentityClientId parameter, use TokenCredential - Exceptions: per-connector types replaced with unified ConnectorException (inherits RequestFailedException, .Status instead of .StatusCode) - IsFatal(): removed (now internal), use plain catch (Exception ex) - AzureLogAnalytics: replaced with AzureMonitorLogs connector New capabilities demonstrated: - DI extension methods (AddOffice365Client, etc.) replace 15+ lines of boilerplate per connector with one-liner config-driven registration - DefaultAzureCredential via DI for local dev authentication - ARM connector added - local.settings.json.template updated with AzureMonitorLogs + ARM entries --- DirectConnector/AzureBlobFunctions.cs | 69 ++++---- DirectConnector/AzureLogAnalyticsFunctions.cs | 43 ++--- DirectConnector/ConnectorFunctions.cs | 135 +++++++------- .../ConnectorTriggerMetadataAttribute.cs | 4 +- DirectConnector/DirectConnector.csproj | 2 +- DirectConnector/MqFunctions.cs | 45 ++--- DirectConnector/MsGraphFunctions.cs | 47 ++--- DirectConnector/Office365UsersFunctions.cs | 45 ++--- DirectConnector/OneDriveFunctions.cs | 89 +++++----- DirectConnector/Program.cs | 165 +++--------------- DirectConnector/SmtpFunctions.cs | 21 +-- DirectConnector/local.settings.json.template | 10 +- 12 files changed, 283 insertions(+), 392 deletions(-) diff --git a/DirectConnector/AzureBlobFunctions.cs b/DirectConnector/AzureBlobFunctions.cs index 4dc8c38..f16c9e2 100644 --- a/DirectConnector/AzureBlobFunctions.cs +++ b/DirectConnector/AzureBlobFunctions.cs @@ -3,8 +3,9 @@ //------------------------------------------------------------ using System.Net; -using Microsoft.Azure.Connectors.DirectClient.Azureblob; -using Microsoft.Azure.Connectors.Sdk; +using Azure.Connectors.Sdk.AzureBlob; +using Azure.Connectors.Sdk.AzureBlob.Models; +using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; @@ -13,7 +14,7 @@ namespace DirectConnector; /// /// Azure Functions demonstrating Azure Blob Storage operations using the generated -/// from the DirectClient SDK. +/// from the DirectClient SDK. /// /// /// Azure Blob Storage uses key-based auth (accountName + accessKey), not OAuth. @@ -22,18 +23,18 @@ namespace DirectConnector; public class AzureBlobFunctions { private readonly ILogger _logger; - private readonly AzureblobClient _azureBlobClient; + private readonly AzureBlobClient _AzureBlobClient; public AzureBlobFunctions( ILogger logger, - AzureblobClient azureBlobClient) + AzureBlobClient AzureBlobClient) { this._logger = logger; - this._azureBlobClient = azureBlobClient; + this._AzureBlobClient = AzureBlobClient; } /// - /// Gets blob metadata using the generated . + /// Gets blob metadata using the generated . /// Exercises the response type. /// [Function("GetBlobMetadata")] @@ -41,7 +42,7 @@ public async Task GetBlobMetadataAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "blob/metadata")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("GetBlobMetadata: Using generated AzureblobClient."); + this._logger.LogInformation("GetBlobMetadata: Using generated AzureBlobClient."); var storageAccount = request.Query["account"]; var blobPath = request.Query["path"]; @@ -56,7 +57,7 @@ await badRequest try { - var metadata = await this._azureBlobClient + var metadata = await this._AzureBlobClient .GetFileMetadataByPathAsync( storageAccountNameOrBlobEndpoint: storageAccount, blobPath: blobPath, @@ -71,17 +72,17 @@ await response .ConfigureAwait(continueOnCapturedContext: false); return response; } - catch (AzureblobConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "Azure Blob connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "Azure Blob connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in GetBlobMetadata."); @@ -94,7 +95,7 @@ await errorResponse } /// - /// Downloads blob content using the generated . + /// Downloads blob content using the generated . /// Exercises the byte[] return path. /// [Function("DownloadBlob")] @@ -102,7 +103,7 @@ public async Task DownloadBlobAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "blob/download")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("DownloadBlob: Using generated AzureblobClient byte[] response path."); + this._logger.LogInformation("DownloadBlob: Using generated AzureBlobClient byte[] response path."); var storageAccount = request.Query["account"]; var blobPath = request.Query["path"]; @@ -117,7 +118,7 @@ await badRequest try { - var fileBytes = await this._azureBlobClient + var fileBytes = await this._AzureBlobClient .GetFileContentByPathAsync( storageAccountNameOrBlobEndpoint: storageAccount, blobPath: blobPath, @@ -140,17 +141,17 @@ await response.Body return response; } - catch (AzureblobConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "Azure Blob connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "Azure Blob connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in DownloadBlob."); @@ -163,7 +164,7 @@ await errorResponse } /// - /// Uploads a blob using the generated . + /// Uploads a blob using the generated . /// Exercises the byte[] input path with response. /// [Function("UploadBlob")] @@ -171,7 +172,7 @@ public async Task UploadBlobAsync( [HttpTrigger(AuthorizationLevel.Function, "post", Route = "blob/upload")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("UploadBlob: Using generated AzureblobClient."); + this._logger.LogInformation("UploadBlob: Using generated AzureBlobClient."); var storageAccount = request.Query["account"]; var folder = request.Query["folder"]; @@ -193,7 +194,7 @@ await request.Body .ConfigureAwait(continueOnCapturedContext: false); var bodyBytes = memoryStream.ToArray(); - var metadata = await this._azureBlobClient + var metadata = await this._AzureBlobClient .CreateFileAsync( storageAccountNameOrBlobEndpoint: storageAccount, input: bodyBytes, @@ -210,17 +211,17 @@ await response .ConfigureAwait(continueOnCapturedContext: false); return response; } - catch (AzureblobConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "Azure Blob connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "Azure Blob connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in UploadBlob."); @@ -233,7 +234,7 @@ await errorResponse } /// - /// Deletes a blob using the generated . + /// Deletes a blob using the generated . /// Exercises the void (no response body) path. /// [Function("DeleteBlob")] @@ -241,7 +242,7 @@ public async Task DeleteBlobAsync( [HttpTrigger(AuthorizationLevel.Function, "delete", Route = "blob/delete")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("DeleteBlob: Using generated AzureblobClient."); + this._logger.LogInformation("DeleteBlob: Using generated AzureBlobClient."); var storageAccount = request.Query["account"]; var blobId = request.Query["id"]; @@ -256,7 +257,7 @@ await badRequest try { - await this._azureBlobClient + await this._AzureBlobClient .DeleteFileAsync( storageAccountNameOrBlobEndpoint: storageAccount, blob: blobId, @@ -271,17 +272,17 @@ await response .ConfigureAwait(continueOnCapturedContext: false); return response; } - catch (AzureblobConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "Azure Blob connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "Azure Blob connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in DeleteBlob."); diff --git a/DirectConnector/AzureLogAnalyticsFunctions.cs b/DirectConnector/AzureLogAnalyticsFunctions.cs index 04f3492..4652710 100644 --- a/DirectConnector/AzureLogAnalyticsFunctions.cs +++ b/DirectConnector/AzureLogAnalyticsFunctions.cs @@ -3,8 +3,9 @@ //------------------------------------------------------------ using System.Net; -using Microsoft.Azure.Connectors.DirectClient.Azureloganalytics; -using Microsoft.Azure.Connectors.Sdk; +using Azure.Connectors.Sdk.Azuremonitorlogs; +using Azure.Connectors.Sdk.Azuremonitorlogs.Models; +using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; @@ -13,16 +14,16 @@ namespace DirectConnector; /// /// Azure Functions demonstrating Azure Log Analytics operations using the generated -/// from the DirectClient SDK. +/// from the DirectClient SDK. /// public class AzureLogAnalyticsFunctions { private readonly ILogger _logger; - private readonly AzureloganalyticsClient _logAnalyticsClient; + private readonly AzuremonitorlogsClient _logAnalyticsClient; public AzureLogAnalyticsFunctions( ILogger logger, - AzureloganalyticsClient logAnalyticsClient) + AzuremonitorlogsClient logAnalyticsClient) { this._logger = logger; this._logAnalyticsClient = logAnalyticsClient; @@ -37,7 +38,7 @@ public async Task ListSubscriptionsAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "loganalytics/subscriptions")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("ListLogAnalyticsSubscriptions: Using generated AzureloganalyticsClient from SDK."); + this._logger.LogInformation("ListLogAnalyticsSubscriptions: Using generated AzuremonitorlogsClient from SDK."); try { @@ -57,18 +58,18 @@ await response return response; } - catch (AzureloganalyticsConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "ListLogAnalyticsSubscriptions failed with status '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "ListLogAnalyticsSubscriptions failed with status '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }, cancellationToken) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in ListLogAnalyticsSubscriptions."); @@ -94,7 +95,7 @@ public async Task ListWorkspacesAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "loganalytics/workspaces")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("ListLogAnalyticsWorkspaces: Using generated AzureloganalyticsClient from SDK."); + this._logger.LogInformation("ListLogAnalyticsWorkspaces: Using generated AzuremonitorlogsClient from SDK."); var subscription = request.Query["subscription"]; var resourceGroup = request.Query["resourceGroup"]; @@ -111,35 +112,35 @@ await badRequest try { - // Note: SDK returns ResourceGroup type for workspace entries per the connector API schema - var workspaces = new List(); - await foreach (var workspace in this._logAnalyticsClient - .ListWorkspaceNamesAsync(subscription: subscription, resourceGroup: resourceGroup) + // Note: SDK returns ResourceItem for resource entries per the connector API schema + var resources = new List(); + await foreach (var resource in this._logAnalyticsClient + .ListResourcesAsync(subscription: subscription, resourceGroup: resourceGroup, resourceType: "Microsoft.OperationalInsights/workspaces") .WithCancellation(cancellationToken) .ConfigureAwait(continueOnCapturedContext: false)) { - workspaces.Add(workspace); + resources.Add(resource); } var response = request.CreateResponse(HttpStatusCode.OK); await response - .WriteAsJsonAsync(new { success = true, count = workspaces.Count, workspaces }, cancellationToken) + .WriteAsJsonAsync(new { success = true, count = resources.Count, resources }, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); return response; } - catch (AzureloganalyticsConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "ListLogAnalyticsWorkspaces failed with status '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "ListLogAnalyticsWorkspaces failed with status '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }, cancellationToken) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in ListLogAnalyticsWorkspaces."); diff --git a/DirectConnector/ConnectorFunctions.cs b/DirectConnector/ConnectorFunctions.cs index 81b52a1..0e8eb67 100644 --- a/DirectConnector/ConnectorFunctions.cs +++ b/DirectConnector/ConnectorFunctions.cs @@ -5,19 +5,22 @@ using System.Net; using System.Text; using System.Text.Json; -using Microsoft.Azure.Connectors.DirectClient.Office365; -using Microsoft.Azure.Connectors.DirectClient.Sharepointonline; -using Microsoft.Azure.Connectors.DirectClient.Teams; -using Microsoft.Azure.Connectors.Sdk; +using Azure.Connectors.Sdk.Office365; +using Azure.Connectors.Sdk.Office365.Models; +using Azure.Connectors.Sdk.SharePointOnline; +using Azure.Connectors.Sdk.SharePointOnline.Models; +using Azure.Connectors.Sdk.Teams; +using Azure.Connectors.Sdk.Teams.Models; +using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; -using SharePointBlobMetadata = Microsoft.Azure.Connectors.DirectClient.Sharepointonline.BlobMetadata; +using SharePointBlobMetadata = Azure.Connectors.Sdk.SharePointOnline.Models.BlobMetadata; namespace DirectConnector; /// -/// Azure Functions that use the generated , , +/// Azure Functions that use the generated , , /// and from the DirectClient SDK. /// /// @@ -56,7 +59,7 @@ public class ConnectorFunctions private readonly ILogger _logger; private readonly Office365Client _office365Client; - private readonly SharepointonlineClient _sharePointClient; + private readonly SharePointOnlineClient _sharePointClient; private readonly TeamsClient _teamsClient; /// @@ -69,7 +72,7 @@ public class ConnectorFunctions public ConnectorFunctions( ILogger logger, Office365Client office365Client, - SharepointonlineClient sharePointClient, + SharePointOnlineClient sharePointClient, TeamsClient teamsClient) { this._logger = logger; @@ -148,9 +151,9 @@ await response return response; } - catch (Office365ConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "Connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "Connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -158,14 +161,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in SendEmail."); @@ -212,9 +215,9 @@ await response return response; } - catch (Office365ConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "Connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "Connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -222,13 +225,13 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode + statusCode = ex.Status }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in GetCategories."); @@ -246,7 +249,7 @@ await errorResponse } /// - /// Gets all SharePoint lists and libraries for a site using the generated . + /// Gets all SharePoint lists and libraries for a site using the generated . /// /// The HTTP request containing the site address. /// The cancellation token. @@ -255,7 +258,7 @@ public async Task GetSharePointListsAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "sharepoint/lists")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("GetSharePointLists: Using generated SharepointonlineClient from SDK."); + this._logger.LogInformation("GetSharePointLists: Using generated SharePointOnlineClient from SDK."); var siteAddress = request.Query["site"]; if (string.IsNullOrEmpty(siteAddress)) @@ -290,9 +293,9 @@ await response return response; } - catch (SharepointonlineConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "SharePoint connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "SharePoint connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -300,14 +303,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in GetSharePointLists."); @@ -325,7 +328,7 @@ await errorResponse } /// - /// Lists files in a SharePoint folder using the generated . + /// Lists files in a SharePoint folder using the generated . /// /// /// Exercises the model for folder browsing. @@ -337,7 +340,7 @@ public async Task ListFolderAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "sharepoint/files")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("ListFolder: Using generated SharepointonlineClient from SDK."); + this._logger.LogInformation("ListFolder: Using generated SharePointOnlineClient from SDK."); var siteAddress = request.Query["site"]; if (string.IsNullOrEmpty(siteAddress)) @@ -386,9 +389,9 @@ await response return response; } - catch (SharepointonlineConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "SharePoint connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "SharePoint connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -396,14 +399,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in ListFolder."); @@ -420,7 +423,7 @@ await errorResponse /// Downloads file content from SharePoint as binary bytes. /// /// - /// Exercises the byte[] response path in . + /// Exercises the byte[] response path in . /// The generated CallConnectorAsync detects byte[] as the response type and uses /// ReadAsByteArrayAsync instead of JSON deserialization. /// @@ -431,7 +434,7 @@ public async Task DownloadFileAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "sharepoint/download")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("DownloadFile: Using generated SharepointonlineClient byte[] response path."); + this._logger.LogInformation("DownloadFile: Using generated SharePointOnlineClient byte[] response path."); var siteAddress = request.Query["site"]; var filePath = request.Query["path"]; @@ -471,9 +474,9 @@ await response.Body return response; } - catch (SharepointonlineConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "SharePoint connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "SharePoint connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -481,14 +484,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in DownloadFile."); @@ -505,7 +508,7 @@ await errorResponse /// Uploads a file to a SharePoint document library. /// /// - /// Exercises the byte[] input path in . + /// Exercises the byte[] input path in . /// Accepts a JSON body with base64-encoded content or plain text, and uploads it to /// the specified SharePoint folder. /// @@ -516,7 +519,7 @@ public async Task UploadFileAsync( [HttpTrigger(AuthorizationLevel.Function, "post", Route = "sharepoint/upload")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("UploadFile: Using generated SharepointonlineClient byte[] input path."); + this._logger.LogInformation("UploadFile: Using generated SharePointOnlineClient byte[] input path."); try { @@ -593,9 +596,9 @@ await response return response; } - catch (SharepointonlineConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "SharePoint connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "SharePoint connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -603,14 +606,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in UploadFile."); @@ -670,9 +673,9 @@ await response.Body return response; } - catch (Office365ConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "Connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "Connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -680,13 +683,13 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode + statusCode = ex.Status }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in ExportEmail."); @@ -846,7 +849,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in TriggerCallback."); @@ -928,9 +931,9 @@ await response return response; } - catch (TeamsConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "Teams connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "Teams connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -938,14 +941,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in GetAllTeams."); @@ -1005,9 +1008,9 @@ await response return response; } - catch (TeamsConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "Teams connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "Teams connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -1015,14 +1018,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in GetTeamChannels."); @@ -1098,9 +1101,9 @@ await response return response; } - catch (TeamsConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "Teams connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "Teams connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -1108,14 +1111,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in GetChannelMessages."); @@ -1178,7 +1181,7 @@ await badRequest // The actual message body properties are determined at runtime by the connector's schema // discovery endpoint. With [JsonExtensionData] on AdditionalProperties, arbitrary properties // are now serialized correctly. Populate the dictionary with the expected message fields. - var messageRequest = new Microsoft.Azure.Connectors.DirectClient.Teams.DynamicPostMessageRequest(); + var messageRequest = new Azure.Connectors.Sdk.Teams.Models.DynamicPostMessageRequest(); messageRequest.AdditionalProperties["recipient"] = JsonSerializer.SerializeToElement( new { @@ -1210,9 +1213,9 @@ await response return response; } - catch (TeamsConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "Teams connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "Teams connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -1220,14 +1223,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in PostTeamsMessage."); @@ -1323,9 +1326,9 @@ await response return response; } - catch (Office365ConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "Connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "Connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -1333,14 +1336,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in CreateCalendarEvent."); diff --git a/DirectConnector/ConnectorTriggerMetadataAttribute.cs b/DirectConnector/ConnectorTriggerMetadataAttribute.cs index fd4ac8e..988ba0c 100644 --- a/DirectConnector/ConnectorTriggerMetadataAttribute.cs +++ b/DirectConnector/ConnectorTriggerMetadataAttribute.cs @@ -28,13 +28,13 @@ namespace DirectConnector; public sealed class ConnectorTriggerMetadataAttribute : Attribute { /// - /// The connector API name. Use constants from . + /// The connector API name. Use constants from . /// public string ConnectorName { get; set; } = ""; /// /// The trigger operation name. Use constants from the connector's *TriggerOperations class - /// (e.g., ). + /// (e.g., ). /// public string OperationName { get; set; } = ""; diff --git a/DirectConnector/DirectConnector.csproj b/DirectConnector/DirectConnector.csproj index 25131d0..5f49bed 100644 --- a/DirectConnector/DirectConnector.csproj +++ b/DirectConnector/DirectConnector.csproj @@ -14,7 +14,7 @@ - + diff --git a/DirectConnector/MqFunctions.cs b/DirectConnector/MqFunctions.cs index f5a1918..5c9e913 100644 --- a/DirectConnector/MqFunctions.cs +++ b/DirectConnector/MqFunctions.cs @@ -4,8 +4,9 @@ using System.Net; using System.Text.Json; -using Microsoft.Azure.Connectors.DirectClient.Mq; -using Microsoft.Azure.Connectors.Sdk; +using Azure.Connectors.Sdk.Mq; +using Azure.Connectors.Sdk.Mq.Models; +using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; @@ -85,13 +86,13 @@ await response return response; } - catch (MqConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "MQ send failed with status '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "MQ send failed with status '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }, cancellationToken) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; @@ -107,7 +108,7 @@ await badRequest return badRequest; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in MqSendMessage."); @@ -156,13 +157,13 @@ await response return response; } - catch (MqConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "MQ browse failed with status '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "MQ browse failed with status '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }, cancellationToken) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; @@ -178,7 +179,7 @@ await badRequest return badRequest; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in MqBrowseMessage."); @@ -231,13 +232,13 @@ await response return response; } - catch (MqConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "MQ browse batch failed with status '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "MQ browse batch failed with status '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }, cancellationToken) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; @@ -253,7 +254,7 @@ await badRequest return badRequest; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in MqBrowseMessages."); @@ -302,13 +303,13 @@ await response return response; } - catch (MqConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "MQ receive failed with status '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "MQ receive failed with status '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }, cancellationToken) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; @@ -324,7 +325,7 @@ await badRequest return badRequest; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in MqReceiveMessage."); @@ -378,13 +379,13 @@ await response return response; } - catch (MqConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "MQ delete failed with status '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "MQ delete failed with status '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }, cancellationToken) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; @@ -400,7 +401,7 @@ await badRequest return badRequest; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in MqDeleteMessage."); diff --git a/DirectConnector/MsGraphFunctions.cs b/DirectConnector/MsGraphFunctions.cs index 7be6cbb..a883383 100644 --- a/DirectConnector/MsGraphFunctions.cs +++ b/DirectConnector/MsGraphFunctions.cs @@ -3,8 +3,9 @@ //------------------------------------------------------------ using System.Net; -using Microsoft.Azure.Connectors.DirectClient.Msgraphgroupsanduser; -using Microsoft.Azure.Connectors.Sdk; +using Azure.Connectors.Sdk.MsGraphGroupsAndUsers; +using Azure.Connectors.Sdk.MsGraphGroupsAndUsers.Models; +using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; @@ -13,7 +14,7 @@ namespace DirectConnector; /// /// Azure Functions demonstrating MS Graph Groups & Users operations using the generated -/// from the DirectClient SDK. +/// from the DirectClient SDK. /// /// /// Exercises user listing, group search, and group property retrieval. @@ -21,7 +22,7 @@ namespace DirectConnector; public class MsGraphFunctions { private readonly ILogger _logger; - private readonly MsgraphgroupsanduserClient _msGraphClient; + private readonly MsGraphGroupsAndUsersClient _msGraphClient; /// /// Initializes a new instance of the class. @@ -30,14 +31,14 @@ public class MsGraphFunctions /// The DI-injected MS Graph Groups & Users client (disposed by the host). public MsGraphFunctions( ILogger logger, - MsgraphgroupsanduserClient msGraphClient) + MsGraphGroupsAndUsersClient msGraphClient) { this._logger = logger; this._msGraphClient = msGraphClient; } /// - /// Lists a page of users in the tenant using the generated . + /// Lists a page of users in the tenant using the generated . /// /// The HTTP request. /// The cancellation token. @@ -46,7 +47,7 @@ public async Task ListGraphUsersAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "graph/users")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("ListGraphUsers: Using generated MsgraphgroupsanduserClient from SDK."); + this._logger.LogInformation("ListGraphUsers: Using generated MsGraphGroupsAndUsersClient from SDK."); try { @@ -66,9 +67,9 @@ await response return response; } - catch (MsgraphgroupsanduserConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "MS Graph connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "MS Graph connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -76,14 +77,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in ListGraphUsers."); @@ -97,7 +98,7 @@ await errorResponse } /// - /// Searches for groups by display name using the generated . + /// Searches for groups by display name using the generated . /// /// The HTTP request with optional 'search' query parameter. /// The cancellation token. @@ -106,7 +107,7 @@ public async Task ListGraphGroupsAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "graph/groups")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("ListGraphGroups: Using generated MsgraphgroupsanduserClient from SDK."); + this._logger.LogInformation("ListGraphGroups: Using generated MsGraphGroupsAndUsersClient from SDK."); var rawSearch = request.Query["search"]; var search = string.IsNullOrWhiteSpace(rawSearch) ? null : rawSearch; @@ -130,9 +131,9 @@ await response return response; } - catch (MsgraphgroupsanduserConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "MS Graph connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "MS Graph connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -140,14 +141,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in ListGraphGroups."); @@ -161,7 +162,7 @@ await errorResponse } /// - /// Gets properties of a specific group using the generated . + /// Gets properties of a specific group using the generated . /// /// The HTTP request with 'groupId' query parameter. /// The cancellation token. @@ -170,7 +171,7 @@ public async Task GetGraphGroupPropertiesAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "graph/groups/properties")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("GetGraphGroupProperties: Using generated MsgraphgroupsanduserClient from SDK."); + this._logger.LogInformation("GetGraphGroupProperties: Using generated MsGraphGroupsAndUsersClient from SDK."); var rawGroupId = request.Query["groupId"]; var groupId = rawGroupId?.Trim(); @@ -206,9 +207,9 @@ await response return response; } - catch (MsgraphgroupsanduserConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "MS Graph connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "MS Graph connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -216,14 +217,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in GetGraphGroupProperties."); diff --git a/DirectConnector/Office365UsersFunctions.cs b/DirectConnector/Office365UsersFunctions.cs index d6d4d41..1fe9bc0 100644 --- a/DirectConnector/Office365UsersFunctions.cs +++ b/DirectConnector/Office365UsersFunctions.cs @@ -3,8 +3,9 @@ //------------------------------------------------------------ using System.Net; -using Microsoft.Azure.Connectors.DirectClient.Office365users; -using Microsoft.Azure.Connectors.Sdk; +using Azure.Connectors.Sdk.Office365users; +using Azure.Connectors.Sdk.Office365users.Models; +using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; @@ -48,18 +49,18 @@ await response return response; } - catch (Office365usersConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "GetMyProfile failed with status '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "GetMyProfile failed with status '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }, cancellationToken) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in GetMyProfile."); @@ -97,18 +98,18 @@ await response return response; } - catch (Office365usersConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "GetUserProfile failed with status '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "GetUserProfile failed with status '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }, cancellationToken) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in GetUserProfile."); @@ -146,18 +147,18 @@ await response return response; } - catch (Office365usersConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "GetManager failed with status '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "GetManager failed with status '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }, cancellationToken) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in GetManager."); @@ -195,18 +196,18 @@ await response return response; } - catch (Office365usersConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "GetDirectReports failed with status '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "GetDirectReports failed with status '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }, cancellationToken) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in GetDirectReports."); @@ -260,18 +261,18 @@ await response return response; } - catch (Office365usersConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "SearchUsers failed with status '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "SearchUsers failed with status '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse - .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.StatusCode, details = ex.ResponseBody }, cancellationToken) + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in SearchUsers."); diff --git a/DirectConnector/OneDriveFunctions.cs b/DirectConnector/OneDriveFunctions.cs index f46dbdc..1fb9d9e 100644 --- a/DirectConnector/OneDriveFunctions.cs +++ b/DirectConnector/OneDriveFunctions.cs @@ -5,18 +5,19 @@ using System.Net; using System.Text; using System.Text.Json; -using Microsoft.Azure.Connectors.DirectClient.Onedriveforbusiness; -using Microsoft.Azure.Connectors.Sdk; +using Azure.Connectors.Sdk.OneDriveForBusiness; +using Azure.Connectors.Sdk.OneDriveForBusiness.Models; +using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; -using OneDriveBlobMetadata = Microsoft.Azure.Connectors.DirectClient.Onedriveforbusiness.BlobMetadata; +using OneDriveBlobMetadata = Azure.Connectors.Sdk.OneDriveForBusiness.Models.BlobMetadata; namespace DirectConnector; /// /// Azure Functions demonstrating OneDrive for Business operations using the generated -/// from the DirectClient SDK. +/// from the DirectClient SDK. /// /// /// Exercises folder listing, file download/upload, search, sharing links, @@ -49,7 +50,7 @@ public class OneDriveFunctions }; private readonly ILogger _logger; - private readonly OnedriveforbusinessClient _oneDriveClient; + private readonly OneDriveForBusinessClient _oneDriveClient; /// /// Initializes a new instance of the class. @@ -58,7 +59,7 @@ public class OneDriveFunctions /// The DI-injected OneDrive for Business client (disposed by the host). public OneDriveFunctions( ILogger logger, - OnedriveforbusinessClient oneDriveClient) + OneDriveForBusinessClient oneDriveClient) { this._logger = logger; this._oneDriveClient = oneDriveClient; @@ -74,7 +75,7 @@ public async Task ListOneDriveRootAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "onedrive/root")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("ListOneDriveRoot: Using generated OnedriveforbusinessClient from SDK."); + this._logger.LogInformation("ListOneDriveRoot: Using generated OneDriveForBusinessClient from SDK."); try { @@ -103,9 +104,9 @@ await response return response; } - catch (OnedriveforbusinessConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "OneDrive connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "OneDrive connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -113,14 +114,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in ListOneDriveRoot."); @@ -137,7 +138,7 @@ await errorResponse /// Lists files in a specific OneDrive folder. /// /// - /// Exercises which returns an + /// Exercises which returns an /// that automatically follows pagination across all pages. /// /// The HTTP request containing the folder identifier. @@ -147,7 +148,7 @@ public async Task ListOneDriveFolderAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "onedrive/files")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("ListOneDriveFolder: Using generated OnedriveforbusinessClient from SDK."); + this._logger.LogInformation("ListOneDriveFolder: Using generated OneDriveForBusinessClient from SDK."); var folderId = request.Query["folder"]; if (string.IsNullOrEmpty(folderId)) @@ -194,9 +195,9 @@ await response return response; } - catch (OnedriveforbusinessConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "OneDrive connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "OneDrive connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -204,14 +205,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in ListOneDriveFolder."); @@ -228,7 +229,7 @@ await errorResponse /// Downloads a file from OneDrive for Business as binary bytes. /// /// - /// Exercises the byte[] response path in . + /// Exercises the byte[] response path in . /// /// The HTTP request containing the file path. /// The cancellation token. @@ -237,7 +238,7 @@ public async Task DownloadOneDriveFileAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "onedrive/download")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("DownloadOneDriveFile: Using generated OnedriveforbusinessClient byte[] response path."); + this._logger.LogInformation("DownloadOneDriveFile: Using generated OneDriveForBusinessClient byte[] response path."); var filePath = request.Query["path"]; if (string.IsNullOrEmpty(filePath)) @@ -272,9 +273,9 @@ await response.Body return response; } - catch (OnedriveforbusinessConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "OneDrive connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "OneDrive connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -282,14 +283,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in DownloadOneDriveFile."); @@ -306,7 +307,7 @@ await errorResponse /// Uploads a file to OneDrive for Business. /// /// - /// Exercises the byte[] input path in . + /// Exercises the byte[] input path in . /// /// The HTTP request containing upload details. /// The cancellation token. @@ -315,7 +316,7 @@ public async Task UploadOneDriveFileAsync( [HttpTrigger(AuthorizationLevel.Function, "post", Route = "onedrive/upload")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("UploadOneDriveFile: Using generated OnedriveforbusinessClient byte[] input path."); + this._logger.LogInformation("UploadOneDriveFile: Using generated OneDriveForBusinessClient byte[] input path."); try { @@ -391,9 +392,9 @@ await response return response; } - catch (OnedriveforbusinessConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "OneDrive connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "OneDrive connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -401,14 +402,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in UploadOneDriveFile."); @@ -431,7 +432,7 @@ public async Task SearchOneDriveFilesAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "onedrive/search")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("SearchOneDriveFiles: Using generated OnedriveforbusinessClient from SDK."); + this._logger.LogInformation("SearchOneDriveFiles: Using generated OneDriveForBusinessClient from SDK."); var query = request.Query["query"]; if (string.IsNullOrEmpty(query)) @@ -477,9 +478,9 @@ await response return response; } - catch (OnedriveforbusinessConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "OneDrive connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "OneDrive connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -487,14 +488,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in SearchOneDriveFiles."); @@ -517,7 +518,7 @@ public async Task CreateOneDriveShareLinkAsync( [HttpTrigger(AuthorizationLevel.Function, "post", Route = "onedrive/share")] HttpRequestData request, CancellationToken cancellationToken) { - this._logger.LogInformation("CreateOneDriveShareLink: Using generated OnedriveforbusinessClient from SDK."); + this._logger.LogInformation("CreateOneDriveShareLink: Using generated OneDriveForBusinessClient from SDK."); try { @@ -577,9 +578,9 @@ await response return response; } - catch (OnedriveforbusinessConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "OneDrive connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "OneDrive connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -587,14 +588,14 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }) .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in CreateOneDriveShareLink."); @@ -627,7 +628,7 @@ await errorResponse /// Object body (OnNewFilesV2 / OnUpdatedFilesV2) /// /// The body field is a {"value":[...]} object with items. - /// The payload deserializes to . + /// The payload deserializes to . /// /// /// @@ -636,8 +637,8 @@ await errorResponse /// The cancellation token. [Function("OneDriveTriggerCallback")] [ConnectorTriggerMetadata( - ConnectorName = ConnectorNames.Onedriveforbusiness, - OperationName = OnedriveforbusinessTriggerOperations.OnNewFiles, + ConnectorName = ConnectorNames.OneDriveForBusiness, + OperationName = OneDriveForBusinessTriggerOperations.OnNewFiles, Connection = "Connectors:OneDrive")] public async Task OneDriveTriggerCallbackAsync( [HttpTrigger(AuthorizationLevel.Function, "post", Route = "onedriveTriggerCallback")] HttpRequestData request, @@ -758,7 +759,7 @@ await binaryResponse // NOTE: OnNewFilesV2 / OnUpdatedFilesV2 (properties-only trigger). // The "body" field is a {"value":[...]} object with BlobMetadata items. - var payload = JsonSerializer.Deserialize( + var payload = JsonSerializer.Deserialize( body, OneDriveFunctions.JsonOptions); @@ -817,7 +818,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in OneDriveTriggerCallback."); diff --git a/DirectConnector/Program.cs b/DirectConnector/Program.cs index e88984c..3997f7c 100644 --- a/DirectConnector/Program.cs +++ b/DirectConnector/Program.cs @@ -2,22 +2,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ -using DirectConnector.Configuration; -using Microsoft.Azure.Connectors.DirectClient.Azureblob; -using Microsoft.Azure.Connectors.DirectClient.Azureloganalytics; -using Microsoft.Azure.Connectors.DirectClient.Mq; -using Microsoft.Azure.Connectors.DirectClient.Msgraphgroupsanduser; -using Microsoft.Azure.Connectors.DirectClient.Office365; -using Microsoft.Azure.Connectors.DirectClient.Office365users; -using Microsoft.Azure.Connectors.DirectClient.Onedriveforbusiness; -using Microsoft.Azure.Connectors.DirectClient.Sharepointonline; -using Microsoft.Azure.Connectors.DirectClient.Smtp; -using Microsoft.Azure.Connectors.DirectClient.Teams; +using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Options; var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults() @@ -25,137 +13,26 @@ { var configuration = hostContext.Configuration; - // NOTE: Bind connector options from configuration. - // This uses the Options pattern for hierarchical, validated configuration. - // See: https://learn.microsoft.com/dotnet/core/extensions/options - services.AddOptions() - .Bind(configuration.GetSection(ConnectorOptions.SectionName)) - .ValidateDataAnnotations() - .ValidateOnStart(); - - services.AddHttpClient(); - - // NOTE: Register generated connector clients as singletons. - // The factory overload lets DI own the instance lifetime and call Dispose, - // which exercises the ownership-based disposal pattern: the client will - // dispose its internally-created HttpClient and DefaultAzureCredential. - // NOTE: Validation of ConnectionRuntimeUrl is handled by - // [Required] attribute + ValidateOnStart() at host initialization. - services.AddSingleton(serviceProvider => - { - var options = serviceProvider.GetRequiredService>().Value; - - // NOTE: When ManagedIdentityClientId is null, use the default constructor so - // the client relies on DefaultAzureCredential. When ManagedIdentityClientId is non-null - // (empty string = system-assigned MSI, non-empty = user-assigned MSI), use the MSI constructor. - return options.Office365.ManagedIdentityClientId != null - ? new Office365Client( - options.Office365.ConnectionRuntimeUrl, - options.Office365.ManagedIdentityClientId) - : new Office365Client(options.Office365.ConnectionRuntimeUrl); - }); - - services.AddSingleton(serviceProvider => - { - var options = serviceProvider.GetRequiredService>().Value; - - // NOTE: When ManagedIdentityClientId is null, use the default constructor so - // the client relies on DefaultAzureCredential. When ManagedIdentityClientId is non-null - // (empty string = system-assigned MSI, non-empty = user-assigned MSI), use the MSI constructor. - return options.SharePoint.ManagedIdentityClientId != null - ? new SharepointonlineClient( - options.SharePoint.ConnectionRuntimeUrl, - options.SharePoint.ManagedIdentityClientId) - : new SharepointonlineClient(options.SharePoint.ConnectionRuntimeUrl); - }); - - services.AddSingleton(serviceProvider => - { - var options = serviceProvider.GetRequiredService>().Value; - - return options.Teams.ManagedIdentityClientId != null - ? new TeamsClient( - options.Teams.ConnectionRuntimeUrl, - options.Teams.ManagedIdentityClientId) - : new TeamsClient(options.Teams.ConnectionRuntimeUrl); - }); - - services.AddSingleton(serviceProvider => - { - var options = serviceProvider.GetRequiredService>().Value; - - return options.OneDrive.ManagedIdentityClientId != null - ? new OnedriveforbusinessClient( - options.OneDrive.ConnectionRuntimeUrl, - options.OneDrive.ManagedIdentityClientId) - : new OnedriveforbusinessClient(options.OneDrive.ConnectionRuntimeUrl); - }); - - services.AddSingleton(serviceProvider => - { - var options = serviceProvider.GetRequiredService>().Value; - - return options.MsGraph.ManagedIdentityClientId != null - ? new MsgraphgroupsanduserClient( - options.MsGraph.ConnectionRuntimeUrl, - options.MsGraph.ManagedIdentityClientId) - : new MsgraphgroupsanduserClient(options.MsGraph.ConnectionRuntimeUrl); - }); - - services.AddSingleton(serviceProvider => - { - var options = serviceProvider.GetRequiredService>().Value; - - return options.AzureBlob.ManagedIdentityClientId != null - ? new AzureblobClient( - options.AzureBlob.ConnectionRuntimeUrl, - options.AzureBlob.ManagedIdentityClientId) - : new AzureblobClient(options.AzureBlob.ConnectionRuntimeUrl); - }); - - services.AddSingleton(serviceProvider => - { - var options = serviceProvider.GetRequiredService>().Value; - - return options.Smtp.ManagedIdentityClientId != null - ? new SmtpClient( - options.Smtp.ConnectionRuntimeUrl, - options.Smtp.ManagedIdentityClientId) - : new SmtpClient(options.Smtp.ConnectionRuntimeUrl); - }); - - services.AddSingleton(serviceProvider => - { - var options = serviceProvider.GetRequiredService>().Value; - - return options.Mq.ManagedIdentityClientId != null - ? new MqClient( - options.Mq.ConnectionRuntimeUrl, - options.Mq.ManagedIdentityClientId) - : new MqClient(options.Mq.ConnectionRuntimeUrl); - }); - - services.AddSingleton(serviceProvider => - { - var options = serviceProvider.GetRequiredService>().Value; - - return options.Office365Users.ManagedIdentityClientId != null - ? new Office365usersClient( - options.Office365Users.ConnectionRuntimeUrl, - options.Office365Users.ManagedIdentityClientId) - : new Office365usersClient(options.Office365Users.ConnectionRuntimeUrl); - }); - - services.AddSingleton(serviceProvider => - { - var options = serviceProvider.GetRequiredService>().Value; - - return options.AzureLogAnalytics.ManagedIdentityClientId != null - ? new AzureloganalyticsClient( - options.AzureLogAnalytics.ConnectionRuntimeUrl, - options.AzureLogAnalytics.ManagedIdentityClientId) - : new AzureloganalyticsClient(options.AzureLogAnalytics.ConnectionRuntimeUrl); - }); + // NOTE: Register generated connector clients as singletons using + // the DI extension methods from the SDK. Each method reads + // ConnectionRuntimeUrl from the IConfiguration section. + // TokenCredential is resolved from DI if registered, otherwise + // defaults to system-assigned managed identity. + // For local dev, register DefaultAzureCredential in DI: + services.AddSingleton( + new Azure.Identity.DefaultAzureCredential()); + + services.AddOffice365Client(configuration.GetSection("Connectors:Office365")); + services.AddSharePointOnlineClient(configuration.GetSection("Connectors:SharePoint")); + services.AddTeamsClient(configuration.GetSection("Connectors:Teams")); + services.AddOneDriveForBusinessClient(configuration.GetSection("Connectors:OneDrive")); + services.AddMsGraphGroupsAndUsersClient(configuration.GetSection("Connectors:MsGraph")); + services.AddAzureBlobClient(configuration.GetSection("Connectors:AzureBlob")); + services.AddSmtpClient(configuration.GetSection("Connectors:Smtp")); + services.AddMqClient(configuration.GetSection("Connectors:Mq")); + services.AddOffice365usersClient(configuration.GetSection("Connectors:Office365Users")); + services.AddAzuremonitorlogsClient(configuration.GetSection("Connectors:AzureMonitorLogs")); + services.AddArmClient(configuration.GetSection("Connectors:Arm")); }) .Build(); diff --git a/DirectConnector/SmtpFunctions.cs b/DirectConnector/SmtpFunctions.cs index d84a1ce..d75aac7 100644 --- a/DirectConnector/SmtpFunctions.cs +++ b/DirectConnector/SmtpFunctions.cs @@ -4,8 +4,9 @@ using System.Net; using System.Text.Json; -using Microsoft.Azure.Connectors.DirectClient.Smtp; -using Microsoft.Azure.Connectors.Sdk; +using Azure.Connectors.Sdk.Smtp; +using Azure.Connectors.Sdk.Smtp.Models; +using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; @@ -24,14 +25,14 @@ public class SmtpFunctions }; private readonly ILogger _logger; - private readonly SmtpClient _smtpClient; + private readonly SmtpClient _SmtpClient; public SmtpFunctions( ILogger logger, - SmtpClient smtpClient) + SmtpClient SmtpClient) { this._logger = logger; - this._smtpClient = smtpClient; + this._SmtpClient = SmtpClient; } /// @@ -72,7 +73,7 @@ await badRequest Body = input.Body ?? string.Empty }; - await this._smtpClient + await this._SmtpClient .SendEmailAsync(input: email, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); @@ -91,9 +92,9 @@ await response return response; } - catch (SmtpConnectorException ex) + catch (ConnectorException ex) { - this._logger.LogError(ex, "SMTP connector error: '{StatusCode}'.", ex.StatusCode); + this._logger.LogError(ex, "SMTP connector error: '{StatusCode}'.", ex.Status); var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); await errorResponse @@ -101,7 +102,7 @@ await errorResponse { success = false, error = ex.Message, - statusCode = ex.StatusCode, + statusCode = ex.Status, details = ex.ResponseBody }, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); @@ -119,7 +120,7 @@ await badRequest return badRequest; } - catch (Exception ex) when (!ex.IsFatal()) + catch (Exception ex) { this._logger.LogError(ex, "Error in SmtpSendEmail."); diff --git a/DirectConnector/local.settings.json.template b/DirectConnector/local.settings.json.template index e068f83..f8cc022 100644 --- a/DirectConnector/local.settings.json.template +++ b/DirectConnector/local.settings.json.template @@ -40,8 +40,12 @@ "__comment_Office365Users_MSI": "To use managed identity, remove the __ prefix from the key below. Use empty string for system-assigned MSI, or a client ID GUID for user-assigned MSI.", "__Connectors:Office365Users:ManagedIdentityClientId": "", - "Connectors:AzureLogAnalytics:ConnectionRuntimeUrl": "https://YOUR-INSTANCE.azure-apihub.net/apim/azureloganalytics/YOUR-CONNECTION-ID", - "__comment_AzureLogAnalytics_MSI": "To use managed identity, remove the __ prefix from the key below. Use empty string for system-assigned MSI, or a client ID GUID for user-assigned MSI.", - "__Connectors:AzureLogAnalytics:ManagedIdentityClientId": "" + "Connectors:AzureMonitorLogs:ConnectionRuntimeUrl": "https://YOUR-INSTANCE.azure-apihub.net/apim/azuremonitorlogs/YOUR-CONNECTION-ID", + "__comment_AzureMonitorLogs_MSI": "To use managed identity, remove the __ prefix from the key below. Use empty string for system-assigned MSI, or a client ID GUID for user-assigned MSI.", + "__Connectors:AzureMonitorLogs:ManagedIdentityClientId": "", + + "Connectors:Arm:ConnectionRuntimeUrl": "https://YOUR-INSTANCE.azure-apihub.net/apim/arm/YOUR-CONNECTION-ID", + "__comment_Arm_MSI": "To use managed identity, remove the __ prefix from the key below. Use empty string for system-assigned MSI, or a client ID GUID for user-assigned MSI.", + "__Connectors:Arm:ManagedIdentityClientId": "" } } From ef07b788ad3dc260ccde5052b94a39b95cc3981a Mon Sep 17 00:00:00 2001 From: David Burg Date: Fri, 8 May 2026 14:36:10 -0700 Subject: [PATCH 2/8] Add ArmFunctions.cs + update README for v0.9.0 connector lineup - ARM sample functions (list subscriptions, resource groups, deployments) ported from PR #33 with namespace updates for v0.9.0 - README table updated: removed Azure Log Analytics, added Azure Monitor Logs and ARM connectors - Supersedes PRs #32-#38 (incremental unpublished SDK changes) --- DirectConnector/ArmFunctions.cs | 268 ++++++++++++++++++++++++++++++++ README.md | 5 +- 2 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 DirectConnector/ArmFunctions.cs diff --git a/DirectConnector/ArmFunctions.cs b/DirectConnector/ArmFunctions.cs new file mode 100644 index 0000000..36490ae --- /dev/null +++ b/DirectConnector/ArmFunctions.cs @@ -0,0 +1,268 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using System.Net; +using System.Text.Json; +using Azure.Connectors.Sdk.Arm; +using Azure.Connectors.Sdk.Arm.Models; +using Azure.Connectors.Sdk; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Extensions.Logging; + +namespace DirectConnector; + +/// +/// Azure Functions demonstrating Azure Resource Manager operations using the generated +/// from the DirectClient SDK. +/// +/// +/// ARM connector uses OAuth (user-delegated Azure AD token). +/// The connection requires OAuth consent via the consent link flow. +/// +public class ArmFunctions +{ + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNameCaseInsensitive = true + }; + + private readonly ILogger _logger; + private readonly ArmClient _armClient; + + public ArmFunctions( + ILogger logger, + ArmClient armClient) + { + this._logger = logger; + this._armClient = armClient; + } + + /// + /// Lists all subscriptions accessible to the authenticated user. + /// + [Function("ListSubscriptions")] + public async Task ListSubscriptionsAsync( + [HttpTrigger(AuthorizationLevel.Function, "get", Route = "arm/subscriptions")] HttpRequestData request, + CancellationToken cancellationToken) + { + this._logger.LogInformation("ListSubscriptions: Using generated ArmClient."); + + try + { + var subscriptions = new List(); + await foreach (var subscription in this._armClient + .SubscriptionsListAsync() + .WithCancellation(cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false)) + { + subscriptions.Add(subscription); + } + + this._logger.LogInformation("Found '{Count}' subscriptions.", subscriptions.Count); + + var response = request.CreateResponse(HttpStatusCode.OK); + await response + .WriteAsJsonAsync(subscriptions) + .ConfigureAwait(continueOnCapturedContext: false); + return response; + } + catch (ConnectorException ex) + { + this._logger.LogError(ex, "ARM connector error: '{StatusCode}'.", ex.Status); + + var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); + await errorResponse + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }) + .ConfigureAwait(continueOnCapturedContext: false); + return errorResponse; + } + catch (Exception ex) + { + this._logger.LogError(ex, "Error in ListSubscriptions."); + + var errorResponse = request.CreateResponse(HttpStatusCode.InternalServerError); + await errorResponse + .WriteAsJsonAsync(new { success = false, error = ex.Message }) + .ConfigureAwait(continueOnCapturedContext: false); + return errorResponse; + } + } + + /// + /// Lists resource groups in a subscription. + /// + [Function("ListResourceGroups")] + public async Task ListResourceGroupsAsync( + [HttpTrigger(AuthorizationLevel.Function, "get", Route = "arm/subscriptions/{subscriptionId}/resourcegroups")] HttpRequestData request, + string subscriptionId, + CancellationToken cancellationToken) + { + this._logger.LogInformation("ListResourceGroups: subscription='{SubscriptionId}'.", subscriptionId); + + try + { + var resourceGroups = new List(); + await foreach (var resourceGroup in this._armClient + .ResourceGroupsListAsync(subscription: subscriptionId) + .WithCancellation(cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false)) + { + resourceGroups.Add(resourceGroup); + } + + this._logger.LogInformation("Found '{Count}' resource groups.", resourceGroups.Count); + + var response = request.CreateResponse(HttpStatusCode.OK); + await response + .WriteAsJsonAsync(resourceGroups) + .ConfigureAwait(continueOnCapturedContext: false); + return response; + } + catch (ConnectorException ex) + { + this._logger.LogError(ex, "ARM connector error: '{StatusCode}'.", ex.Status); + + var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); + await errorResponse + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }) + .ConfigureAwait(continueOnCapturedContext: false); + return errorResponse; + } + catch (Exception ex) + { + this._logger.LogError(ex, "Error in ListResourceGroups."); + + var errorResponse = request.CreateResponse(HttpStatusCode.InternalServerError); + await errorResponse + .WriteAsJsonAsync(new { success = false, error = ex.Message }) + .ConfigureAwait(continueOnCapturedContext: false); + return errorResponse; + } + } + + /// + /// Reads a specific resource by its resource group, provider, and short resource ID. + /// + [Function("ReadResource")] + public async Task ReadResourceAsync( + [HttpTrigger(AuthorizationLevel.Function, "get", Route = "arm/resource")] HttpRequestData request, + CancellationToken cancellationToken) + { + var subscriptionId = request.Query["subscriptionId"]; + var resourceGroup = request.Query["resourceGroup"]; + var provider = request.Query["provider"]; + var shortResourceId = request.Query["shortResourceId"]; + var apiVersion = request.Query["apiVersion"]; + + if (string.IsNullOrWhiteSpace(subscriptionId) || + string.IsNullOrWhiteSpace(resourceGroup) || + string.IsNullOrWhiteSpace(provider) || + string.IsNullOrWhiteSpace(shortResourceId) || + string.IsNullOrWhiteSpace(apiVersion)) + { + var badRequest = request.CreateResponse(HttpStatusCode.BadRequest); + await badRequest + .WriteAsJsonAsync(new { error = "Query parameters 'subscriptionId', 'resourceGroup', 'provider', 'shortResourceId', and 'apiVersion' are required." }) + .ConfigureAwait(continueOnCapturedContext: false); + return badRequest; + } + + this._logger.LogInformation("ReadResource: '{Provider}/{ShortResourceId}' in '{ResourceGroup}'.", provider, shortResourceId, resourceGroup); + + try + { + var resource = await this._armClient + .ResourcesGetByIdAsync( + subscription: subscriptionId, + resourceGroup: resourceGroup, + resourceProvider: provider, + shortResourceId: shortResourceId, + clientApiVersion: apiVersion, + cancellationToken: cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false); + + this._logger.LogInformation("Read resource '{Name}' of type '{Type}'.", resource.Name, resource.Type); + + var response = request.CreateResponse(HttpStatusCode.OK); + await response + .WriteAsJsonAsync(resource) + .ConfigureAwait(continueOnCapturedContext: false); + return response; + } + catch (ConnectorException ex) + { + this._logger.LogError(ex, "ARM connector error: '{StatusCode}'.", ex.Status); + + var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); + await errorResponse + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }) + .ConfigureAwait(continueOnCapturedContext: false); + return errorResponse; + } + catch (Exception ex) + { + this._logger.LogError(ex, "Error in ReadResource."); + + var errorResponse = request.CreateResponse(HttpStatusCode.InternalServerError); + await errorResponse + .WriteAsJsonAsync(new { success = false, error = ex.Message }) + .ConfigureAwait(continueOnCapturedContext: false); + return errorResponse; + } + } + + /// + /// Lists resources in a resource group. + /// + [Function("ListResourcesByResourceGroup")] + public async Task ListResourcesByResourceGroupAsync( + [HttpTrigger(AuthorizationLevel.Function, "get", Route = "arm/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/resources")] HttpRequestData request, + string subscriptionId, + string resourceGroupName, + CancellationToken cancellationToken) + { + this._logger.LogInformation("ListResourcesByResourceGroup: subscription='{SubscriptionId}', resourceGroup='{ResourceGroup}'.", subscriptionId, resourceGroupName); + + try + { + var resources = new List(); + await foreach (var resource in this._armClient + .ResourceGroupsListResourcesAsync(subscription: subscriptionId, resourceGroup: resourceGroupName) + .WithCancellation(cancellationToken) + .ConfigureAwait(continueOnCapturedContext: false)) + { + resources.Add(resource); + } + + this._logger.LogInformation("Found '{Count}' resources.", resources.Count); + + var response = request.CreateResponse(HttpStatusCode.OK); + await response + .WriteAsJsonAsync(resources) + .ConfigureAwait(continueOnCapturedContext: false); + return response; + } + catch (ConnectorException ex) + { + this._logger.LogError(ex, "ARM connector error: '{StatusCode}'.", ex.Status); + + var errorResponse = request.CreateResponse(HttpStatusCode.BadGateway); + await errorResponse + .WriteAsJsonAsync(new { success = false, error = ex.Message, statusCode = ex.Status, details = ex.ResponseBody }) + .ConfigureAwait(continueOnCapturedContext: false); + return errorResponse; + } + catch (Exception ex) + { + this._logger.LogError(ex, "Error in ListResourcesByResourceGroup."); + + var errorResponse = request.CreateResponse(HttpStatusCode.InternalServerError); + await errorResponse + .WriteAsJsonAsync(new { success = false, error = ex.Message }) + .ConfigureAwait(continueOnCapturedContext: false); + return errorResponse; + } + } +} diff --git a/README.md b/README.md index 1388b16..07f6410 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Sample Azure Functions demonstrating the [Azure Connectors .NET SDK](https://git ## What's Inside -The `DirectConnector/` project is an Azure Functions (isolated worker) app with 40 sample functions across 10 connectors. Newer connectors have dedicated Functions classes; the original three (Office 365, SharePoint, Teams) share `ConnectorFunctions.cs`: +The `DirectConnector/` project is an Azure Functions (isolated worker) app with sample functions across 11 connectors. Newer connectors have dedicated Functions classes; the original three (Office 365, SharePoint, Teams) share `ConnectorFunctions.cs`: | File | Connector | Sample Operations | |------|-----------|-------------------| @@ -28,7 +28,8 @@ The `DirectConnector/` project is an Azure Functions (isolated worker) app with | MqFunctions.cs | IBM MQ | Send, browse, receive, delete messages | | SmtpFunctions.cs | SMTP | Send email via SMTP | | AzureBlobFunctions.cs | Azure Blob Storage | Upload, download, get metadata, delete blobs | -| AzureLogAnalyticsFunctions.cs | Azure Log Analytics | List subscriptions, list workspaces | +| AzureLogAnalyticsFunctions.cs | Azure Monitor Logs | List subscriptions, list resources | +| ArmFunctions.cs | Azure Resource Manager (ARM) | List subscriptions, resource groups, deployments | ### Key Patterns Demonstrated From 9704c92b93190901ad6294425107c0b0ec8ef7ad Mon Sep 17 00:00:00 2001 From: David Burg Date: Fri, 8 May 2026 14:37:51 -0700 Subject: [PATCH 3/8] Add local ExceptionExtensions.IsFatal() helper + restore guards The SDK made IsFatal() internal in v0.9.0. Add a local copy so sample exception handling follows the recommended pattern of not catching fatal exceptions (OOM, StackOverflow, AccessViolation, SEH, ThreadAbort). --- DirectConnector/ArmFunctions.cs | 8 +++--- DirectConnector/AzureBlobFunctions.cs | 8 +++--- DirectConnector/AzureLogAnalyticsFunctions.cs | 4 +-- DirectConnector/ConnectorFunctions.cs | 26 ++++++++--------- DirectConnector/ExceptionExtensions.cs | 28 +++++++++++++++++++ DirectConnector/MqFunctions.cs | 10 +++---- DirectConnector/MsGraphFunctions.cs | 6 ++-- DirectConnector/Office365UsersFunctions.cs | 10 +++---- DirectConnector/OneDriveFunctions.cs | 14 +++++----- DirectConnector/SmtpFunctions.cs | 2 +- 10 files changed, 72 insertions(+), 44 deletions(-) create mode 100644 DirectConnector/ExceptionExtensions.cs diff --git a/DirectConnector/ArmFunctions.cs b/DirectConnector/ArmFunctions.cs index 36490ae..bf2701a 100644 --- a/DirectConnector/ArmFunctions.cs +++ b/DirectConnector/ArmFunctions.cs @@ -78,7 +78,7 @@ await errorResponse .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in ListSubscriptions."); @@ -130,7 +130,7 @@ await errorResponse .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in ListResourceGroups."); @@ -201,7 +201,7 @@ await errorResponse .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in ReadResource."); @@ -254,7 +254,7 @@ await errorResponse .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in ListResourcesByResourceGroup."); diff --git a/DirectConnector/AzureBlobFunctions.cs b/DirectConnector/AzureBlobFunctions.cs index f16c9e2..12f9ae0 100644 --- a/DirectConnector/AzureBlobFunctions.cs +++ b/DirectConnector/AzureBlobFunctions.cs @@ -82,7 +82,7 @@ await errorResponse .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in GetBlobMetadata."); @@ -151,7 +151,7 @@ await errorResponse .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in DownloadBlob."); @@ -221,7 +221,7 @@ await errorResponse .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in UploadBlob."); @@ -282,7 +282,7 @@ await errorResponse .ConfigureAwait(continueOnCapturedContext: false); return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in DeleteBlob."); diff --git a/DirectConnector/AzureLogAnalyticsFunctions.cs b/DirectConnector/AzureLogAnalyticsFunctions.cs index 4652710..0e736db 100644 --- a/DirectConnector/AzureLogAnalyticsFunctions.cs +++ b/DirectConnector/AzureLogAnalyticsFunctions.cs @@ -69,7 +69,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in ListLogAnalyticsSubscriptions."); @@ -140,7 +140,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in ListLogAnalyticsWorkspaces."); diff --git a/DirectConnector/ConnectorFunctions.cs b/DirectConnector/ConnectorFunctions.cs index 0e8eb67..973a387 100644 --- a/DirectConnector/ConnectorFunctions.cs +++ b/DirectConnector/ConnectorFunctions.cs @@ -168,7 +168,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in SendEmail."); @@ -231,7 +231,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in GetCategories."); @@ -310,7 +310,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in GetSharePointLists."); @@ -406,7 +406,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in ListFolder."); @@ -491,7 +491,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in DownloadFile."); @@ -613,7 +613,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in UploadFile."); @@ -689,7 +689,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in ExportEmail."); @@ -849,7 +849,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in TriggerCallback."); @@ -948,7 +948,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in GetAllTeams."); @@ -1025,7 +1025,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in GetTeamChannels."); @@ -1118,7 +1118,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in GetChannelMessages."); @@ -1230,7 +1230,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in PostTeamsMessage."); @@ -1343,7 +1343,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in CreateCalendarEvent."); diff --git a/DirectConnector/ExceptionExtensions.cs b/DirectConnector/ExceptionExtensions.cs new file mode 100644 index 0000000..736cf1a --- /dev/null +++ b/DirectConnector/ExceptionExtensions.cs @@ -0,0 +1,28 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +using System.Runtime.InteropServices; + +namespace DirectConnector; + +/// +/// Extension methods for exception handling. +/// Provides the guard that was available in earlier SDK versions +/// but made internal in Azure.Connectors.Sdk 0.9.0. +/// +internal static class ExceptionExtensions +{ + /// + /// Determines whether the exception is fatal and should not be caught. + /// + /// The exception to check. + public static bool IsFatal(this Exception exception) + { + return exception is OutOfMemoryException or + StackOverflowException or + AccessViolationException or + SEHException or + ThreadAbortException; + } +} diff --git a/DirectConnector/MqFunctions.cs b/DirectConnector/MqFunctions.cs index 5c9e913..9d60aa5 100644 --- a/DirectConnector/MqFunctions.cs +++ b/DirectConnector/MqFunctions.cs @@ -108,7 +108,7 @@ await badRequest return badRequest; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in MqSendMessage."); @@ -179,7 +179,7 @@ await badRequest return badRequest; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in MqBrowseMessage."); @@ -254,7 +254,7 @@ await badRequest return badRequest; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in MqBrowseMessages."); @@ -325,7 +325,7 @@ await badRequest return badRequest; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in MqReceiveMessage."); @@ -401,7 +401,7 @@ await badRequest return badRequest; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in MqDeleteMessage."); diff --git a/DirectConnector/MsGraphFunctions.cs b/DirectConnector/MsGraphFunctions.cs index a883383..975b8f0 100644 --- a/DirectConnector/MsGraphFunctions.cs +++ b/DirectConnector/MsGraphFunctions.cs @@ -84,7 +84,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in ListGraphUsers."); @@ -148,7 +148,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in ListGraphGroups."); @@ -224,7 +224,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in GetGraphGroupProperties."); diff --git a/DirectConnector/Office365UsersFunctions.cs b/DirectConnector/Office365UsersFunctions.cs index 1fe9bc0..1625234 100644 --- a/DirectConnector/Office365UsersFunctions.cs +++ b/DirectConnector/Office365UsersFunctions.cs @@ -60,7 +60,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in GetMyProfile."); @@ -109,7 +109,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in GetUserProfile."); @@ -158,7 +158,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in GetManager."); @@ -207,7 +207,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in GetDirectReports."); @@ -272,7 +272,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in SearchUsers."); diff --git a/DirectConnector/OneDriveFunctions.cs b/DirectConnector/OneDriveFunctions.cs index 1fb9d9e..d37b3db 100644 --- a/DirectConnector/OneDriveFunctions.cs +++ b/DirectConnector/OneDriveFunctions.cs @@ -121,7 +121,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in ListOneDriveRoot."); @@ -212,7 +212,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in ListOneDriveFolder."); @@ -290,7 +290,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in DownloadOneDriveFile."); @@ -409,7 +409,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in UploadOneDriveFile."); @@ -495,7 +495,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in SearchOneDriveFiles."); @@ -595,7 +595,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in CreateOneDriveShareLink."); @@ -818,7 +818,7 @@ await errorResponse return errorResponse; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in OneDriveTriggerCallback."); diff --git a/DirectConnector/SmtpFunctions.cs b/DirectConnector/SmtpFunctions.cs index d75aac7..f6a093e 100644 --- a/DirectConnector/SmtpFunctions.cs +++ b/DirectConnector/SmtpFunctions.cs @@ -120,7 +120,7 @@ await badRequest return badRequest; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { this._logger.LogError(ex, "Error in SmtpSendEmail."); From 926449230664e09fb4c509c947e1401ce4d58dc4 Mon Sep 17 00:00:00 2001 From: David Burg Date: Fri, 8 May 2026 15:01:14 -0700 Subject: [PATCH 4/8] Address PR feedback: fix naming, credential scope, stale references - Fix _SmtpClient/_AzureBlobClient to _smtpClient/_azureBlobClient (camelCase) - Register DefaultAzureCredential only in Development environment - Replace all 'DirectClient SDK' doc references with 'Azure Connectors SDK' - Add remarks to AzureLogAnalyticsFunctions noting Monitor Logs rename Co-authored-by: Dobby --- DirectConnector/ArmFunctions.cs | 2 +- DirectConnector/AzureBlobFunctions.cs | 16 ++++++++-------- DirectConnector/AzureLogAnalyticsFunctions.cs | 8 ++++++-- DirectConnector/ConnectorFunctions.cs | 2 +- DirectConnector/MqFunctions.cs | 2 +- DirectConnector/MsGraphFunctions.cs | 2 +- DirectConnector/Office365UsersFunctions.cs | 2 +- DirectConnector/OneDriveFunctions.cs | 2 +- DirectConnector/Program.cs | 17 +++++++++-------- DirectConnector/SmtpFunctions.cs | 10 +++++----- 10 files changed, 34 insertions(+), 29 deletions(-) diff --git a/DirectConnector/ArmFunctions.cs b/DirectConnector/ArmFunctions.cs index bf2701a..f6a56bd 100644 --- a/DirectConnector/ArmFunctions.cs +++ b/DirectConnector/ArmFunctions.cs @@ -15,7 +15,7 @@ namespace DirectConnector; /// /// Azure Functions demonstrating Azure Resource Manager operations using the generated -/// from the DirectClient SDK. +/// from the Azure Connectors SDK. /// /// /// ARM connector uses OAuth (user-delegated Azure AD token). diff --git a/DirectConnector/AzureBlobFunctions.cs b/DirectConnector/AzureBlobFunctions.cs index 12f9ae0..9ae7f8e 100644 --- a/DirectConnector/AzureBlobFunctions.cs +++ b/DirectConnector/AzureBlobFunctions.cs @@ -14,7 +14,7 @@ namespace DirectConnector; /// /// Azure Functions demonstrating Azure Blob Storage operations using the generated -/// from the DirectClient SDK. +/// from the Azure Connectors SDK. /// /// /// Azure Blob Storage uses key-based auth (accountName + accessKey), not OAuth. @@ -23,14 +23,14 @@ namespace DirectConnector; public class AzureBlobFunctions { private readonly ILogger _logger; - private readonly AzureBlobClient _AzureBlobClient; + private readonly AzureBlobClient _azureBlobClient; public AzureBlobFunctions( ILogger logger, - AzureBlobClient AzureBlobClient) + AzureBlobClient azureBlobClient) { this._logger = logger; - this._AzureBlobClient = AzureBlobClient; + this._azureBlobClient = azureBlobClient; } /// @@ -57,7 +57,7 @@ await badRequest try { - var metadata = await this._AzureBlobClient + var metadata = await this._azureBlobClient .GetFileMetadataByPathAsync( storageAccountNameOrBlobEndpoint: storageAccount, blobPath: blobPath, @@ -118,7 +118,7 @@ await badRequest try { - var fileBytes = await this._AzureBlobClient + var fileBytes = await this._azureBlobClient .GetFileContentByPathAsync( storageAccountNameOrBlobEndpoint: storageAccount, blobPath: blobPath, @@ -194,7 +194,7 @@ await request.Body .ConfigureAwait(continueOnCapturedContext: false); var bodyBytes = memoryStream.ToArray(); - var metadata = await this._AzureBlobClient + var metadata = await this._azureBlobClient .CreateFileAsync( storageAccountNameOrBlobEndpoint: storageAccount, input: bodyBytes, @@ -257,7 +257,7 @@ await badRequest try { - await this._AzureBlobClient + await this._azureBlobClient .DeleteFileAsync( storageAccountNameOrBlobEndpoint: storageAccount, blob: blobId, diff --git a/DirectConnector/AzureLogAnalyticsFunctions.cs b/DirectConnector/AzureLogAnalyticsFunctions.cs index 0e736db..c4c8e2d 100644 --- a/DirectConnector/AzureLogAnalyticsFunctions.cs +++ b/DirectConnector/AzureLogAnalyticsFunctions.cs @@ -13,9 +13,13 @@ namespace DirectConnector; /// -/// Azure Functions demonstrating Azure Log Analytics operations using the generated -/// from the DirectClient SDK. +/// Azure Functions demonstrating Azure Monitor Logs operations using the generated +/// from the Azure Connectors SDK. /// +/// +/// This connector replaces the deprecated Azure Log Analytics connector. +/// The file retains the "LogAnalytics" name in routes for backward compatibility. +/// public class AzureLogAnalyticsFunctions { private readonly ILogger _logger; diff --git a/DirectConnector/ConnectorFunctions.cs b/DirectConnector/ConnectorFunctions.cs index 973a387..0eaddcc 100644 --- a/DirectConnector/ConnectorFunctions.cs +++ b/DirectConnector/ConnectorFunctions.cs @@ -21,7 +21,7 @@ namespace DirectConnector; /// /// Azure Functions that use the generated , , -/// and from the DirectClient SDK. +/// and from the Azure Connectors SDK. /// /// /// Demonstrates DI-based lifetime management, JSON deserialization for structured responses, diff --git a/DirectConnector/MqFunctions.cs b/DirectConnector/MqFunctions.cs index 9d60aa5..5fe25ad 100644 --- a/DirectConnector/MqFunctions.cs +++ b/DirectConnector/MqFunctions.cs @@ -15,7 +15,7 @@ namespace DirectConnector; /// /// Azure Functions demonstrating IBM MQ operations using the generated -/// from the DirectClient SDK. +/// from the Azure Connectors SDK. /// /// /// IBM MQ uses parameter-based auth (server, queue manager, channel, credentials). diff --git a/DirectConnector/MsGraphFunctions.cs b/DirectConnector/MsGraphFunctions.cs index 975b8f0..6657a28 100644 --- a/DirectConnector/MsGraphFunctions.cs +++ b/DirectConnector/MsGraphFunctions.cs @@ -14,7 +14,7 @@ namespace DirectConnector; /// /// Azure Functions demonstrating MS Graph Groups & Users operations using the generated -/// from the DirectClient SDK. +/// from the Azure Connectors SDK. /// /// /// Exercises user listing, group search, and group property retrieval. diff --git a/DirectConnector/Office365UsersFunctions.cs b/DirectConnector/Office365UsersFunctions.cs index 1625234..a3d68ab 100644 --- a/DirectConnector/Office365UsersFunctions.cs +++ b/DirectConnector/Office365UsersFunctions.cs @@ -14,7 +14,7 @@ namespace DirectConnector; /// /// Azure Functions demonstrating Office 365 Users operations using the generated -/// from the DirectClient SDK. +/// from the Azure Connectors SDK. /// public class Office365UsersFunctions { diff --git a/DirectConnector/OneDriveFunctions.cs b/DirectConnector/OneDriveFunctions.cs index d37b3db..d98b4f2 100644 --- a/DirectConnector/OneDriveFunctions.cs +++ b/DirectConnector/OneDriveFunctions.cs @@ -17,7 +17,7 @@ namespace DirectConnector; /// /// Azure Functions demonstrating OneDrive for Business operations using the generated -/// from the DirectClient SDK. +/// from the Azure Connectors SDK. /// /// /// Exercises folder listing, file download/upload, search, sharing links, diff --git a/DirectConnector/Program.cs b/DirectConnector/Program.cs index 3997f7c..9fdec3f 100644 --- a/DirectConnector/Program.cs +++ b/DirectConnector/Program.cs @@ -13,14 +13,15 @@ { var configuration = hostContext.Configuration; - // NOTE: Register generated connector clients as singletons using - // the DI extension methods from the SDK. Each method reads - // ConnectionRuntimeUrl from the IConfiguration section. - // TokenCredential is resolved from DI if registered, otherwise - // defaults to system-assigned managed identity. - // For local dev, register DefaultAzureCredential in DI: - services.AddSingleton( - new Azure.Identity.DefaultAzureCredential()); + // NOTE: Register a TokenCredential for connector clients. + // In Development, use DefaultAzureCredential (supports CLI, env vars, etc.). + // In Production, the SDK defaults to system-assigned managed identity + // when no TokenCredential is registered in DI. + if (hostContext.HostingEnvironment.IsDevelopment()) + { + services.AddSingleton( + new Azure.Identity.DefaultAzureCredential()); + } services.AddOffice365Client(configuration.GetSection("Connectors:Office365")); services.AddSharePointOnlineClient(configuration.GetSection("Connectors:SharePoint")); diff --git a/DirectConnector/SmtpFunctions.cs b/DirectConnector/SmtpFunctions.cs index f6a093e..1783290 100644 --- a/DirectConnector/SmtpFunctions.cs +++ b/DirectConnector/SmtpFunctions.cs @@ -15,7 +15,7 @@ namespace DirectConnector; /// /// Azure Functions demonstrating SMTP operations using the generated -/// from the DirectClient SDK. +/// from the Azure Connectors SDK. /// public class SmtpFunctions { @@ -25,14 +25,14 @@ public class SmtpFunctions }; private readonly ILogger _logger; - private readonly SmtpClient _SmtpClient; + private readonly SmtpClient _smtpClient; public SmtpFunctions( ILogger logger, - SmtpClient SmtpClient) + SmtpClient smtpClient) { this._logger = logger; - this._SmtpClient = SmtpClient; + this._smtpClient = smtpClient; } /// @@ -73,7 +73,7 @@ await badRequest Body = input.Body ?? string.Empty }; - await this._SmtpClient + await this._smtpClient .SendEmailAsync(input: email, cancellationToken: cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); From 5f6af7cd95efc340e58ff54755aeaba8708d1fb8 Mon Sep 17 00:00:00 2001 From: David Burg Date: Fri, 8 May 2026 16:35:15 -0700 Subject: [PATCH 5/8] Fix using sort order, response naming, unused field - Sort using directives: System.* first, then alphabetically (9 files) - AzureLogAnalyticsFunctions: return workspaces= in response body for backward compatibility with route naming - ArmFunctions: remove unused JsonOptions field and System.Text.Json using Co-authored-by: Dobby --- DirectConnector/ArmFunctions.cs | 8 +------- DirectConnector/AzureBlobFunctions.cs | 2 +- DirectConnector/AzureLogAnalyticsFunctions.cs | 4 ++-- DirectConnector/ConnectorFunctions.cs | 2 +- DirectConnector/MqFunctions.cs | 2 +- DirectConnector/MsGraphFunctions.cs | 2 +- DirectConnector/Office365UsersFunctions.cs | 2 +- DirectConnector/OneDriveFunctions.cs | 2 +- DirectConnector/SmtpFunctions.cs | 2 +- 9 files changed, 10 insertions(+), 16 deletions(-) diff --git a/DirectConnector/ArmFunctions.cs b/DirectConnector/ArmFunctions.cs index f6a56bd..b4f2524 100644 --- a/DirectConnector/ArmFunctions.cs +++ b/DirectConnector/ArmFunctions.cs @@ -3,10 +3,9 @@ //------------------------------------------------------------ using System.Net; -using System.Text.Json; +using Azure.Connectors.Sdk; using Azure.Connectors.Sdk.Arm; using Azure.Connectors.Sdk.Arm.Models; -using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; @@ -23,11 +22,6 @@ namespace DirectConnector; /// public class ArmFunctions { - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNameCaseInsensitive = true - }; - private readonly ILogger _logger; private readonly ArmClient _armClient; diff --git a/DirectConnector/AzureBlobFunctions.cs b/DirectConnector/AzureBlobFunctions.cs index 9ae7f8e..79d8962 100644 --- a/DirectConnector/AzureBlobFunctions.cs +++ b/DirectConnector/AzureBlobFunctions.cs @@ -3,9 +3,9 @@ //------------------------------------------------------------ using System.Net; +using Azure.Connectors.Sdk; using Azure.Connectors.Sdk.AzureBlob; using Azure.Connectors.Sdk.AzureBlob.Models; -using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; diff --git a/DirectConnector/AzureLogAnalyticsFunctions.cs b/DirectConnector/AzureLogAnalyticsFunctions.cs index c4c8e2d..bf3afd5 100644 --- a/DirectConnector/AzureLogAnalyticsFunctions.cs +++ b/DirectConnector/AzureLogAnalyticsFunctions.cs @@ -3,9 +3,9 @@ //------------------------------------------------------------ using System.Net; +using Azure.Connectors.Sdk; using Azure.Connectors.Sdk.Azuremonitorlogs; using Azure.Connectors.Sdk.Azuremonitorlogs.Models; -using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; @@ -128,7 +128,7 @@ await badRequest var response = request.CreateResponse(HttpStatusCode.OK); await response - .WriteAsJsonAsync(new { success = true, count = resources.Count, resources }, cancellationToken) + .WriteAsJsonAsync(new { success = true, count = resources.Count, workspaces = resources }, cancellationToken) .ConfigureAwait(continueOnCapturedContext: false); return response; diff --git a/DirectConnector/ConnectorFunctions.cs b/DirectConnector/ConnectorFunctions.cs index 0eaddcc..f3dbf0c 100644 --- a/DirectConnector/ConnectorFunctions.cs +++ b/DirectConnector/ConnectorFunctions.cs @@ -5,13 +5,13 @@ using System.Net; using System.Text; using System.Text.Json; +using Azure.Connectors.Sdk; using Azure.Connectors.Sdk.Office365; using Azure.Connectors.Sdk.Office365.Models; using Azure.Connectors.Sdk.SharePointOnline; using Azure.Connectors.Sdk.SharePointOnline.Models; using Azure.Connectors.Sdk.Teams; using Azure.Connectors.Sdk.Teams.Models; -using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; diff --git a/DirectConnector/MqFunctions.cs b/DirectConnector/MqFunctions.cs index 5fe25ad..09f93db 100644 --- a/DirectConnector/MqFunctions.cs +++ b/DirectConnector/MqFunctions.cs @@ -4,9 +4,9 @@ using System.Net; using System.Text.Json; +using Azure.Connectors.Sdk; using Azure.Connectors.Sdk.Mq; using Azure.Connectors.Sdk.Mq.Models; -using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; diff --git a/DirectConnector/MsGraphFunctions.cs b/DirectConnector/MsGraphFunctions.cs index 6657a28..837e1a7 100644 --- a/DirectConnector/MsGraphFunctions.cs +++ b/DirectConnector/MsGraphFunctions.cs @@ -3,9 +3,9 @@ //------------------------------------------------------------ using System.Net; +using Azure.Connectors.Sdk; using Azure.Connectors.Sdk.MsGraphGroupsAndUsers; using Azure.Connectors.Sdk.MsGraphGroupsAndUsers.Models; -using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; diff --git a/DirectConnector/Office365UsersFunctions.cs b/DirectConnector/Office365UsersFunctions.cs index a3d68ab..5dc6a86 100644 --- a/DirectConnector/Office365UsersFunctions.cs +++ b/DirectConnector/Office365UsersFunctions.cs @@ -3,9 +3,9 @@ //------------------------------------------------------------ using System.Net; +using Azure.Connectors.Sdk; using Azure.Connectors.Sdk.Office365users; using Azure.Connectors.Sdk.Office365users.Models; -using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; diff --git a/DirectConnector/OneDriveFunctions.cs b/DirectConnector/OneDriveFunctions.cs index d98b4f2..3460486 100644 --- a/DirectConnector/OneDriveFunctions.cs +++ b/DirectConnector/OneDriveFunctions.cs @@ -5,9 +5,9 @@ using System.Net; using System.Text; using System.Text.Json; +using Azure.Connectors.Sdk; using Azure.Connectors.Sdk.OneDriveForBusiness; using Azure.Connectors.Sdk.OneDriveForBusiness.Models; -using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; diff --git a/DirectConnector/SmtpFunctions.cs b/DirectConnector/SmtpFunctions.cs index 1783290..3d6a35d 100644 --- a/DirectConnector/SmtpFunctions.cs +++ b/DirectConnector/SmtpFunctions.cs @@ -4,9 +4,9 @@ using System.Net; using System.Text.Json; +using Azure.Connectors.Sdk; using Azure.Connectors.Sdk.Smtp; using Azure.Connectors.Sdk.Smtp.Models; -using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.Extensions.Logging; From a3b47a6744be537fa389a1bf721d0dd00e384554 Mon Sep 17 00:00:00 2001 From: David Burg Date: Fri, 8 May 2026 16:41:01 -0700 Subject: [PATCH 6/8] Add missing Configuration using + extract magic string constant Co-authored-by: Dobby --- DirectConnector/AzureLogAnalyticsFunctions.cs | 4 +++- DirectConnector/Program.cs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/DirectConnector/AzureLogAnalyticsFunctions.cs b/DirectConnector/AzureLogAnalyticsFunctions.cs index bf3afd5..1e0ed20 100644 --- a/DirectConnector/AzureLogAnalyticsFunctions.cs +++ b/DirectConnector/AzureLogAnalyticsFunctions.cs @@ -22,6 +22,8 @@ namespace DirectConnector; /// public class AzureLogAnalyticsFunctions { + private const string OperationalInsightsWorkspaceResourceType = "Microsoft.OperationalInsights/workspaces"; + private readonly ILogger _logger; private readonly AzuremonitorlogsClient _logAnalyticsClient; @@ -119,7 +121,7 @@ await badRequest // Note: SDK returns ResourceItem for resource entries per the connector API schema var resources = new List(); await foreach (var resource in this._logAnalyticsClient - .ListResourcesAsync(subscription: subscription, resourceGroup: resourceGroup, resourceType: "Microsoft.OperationalInsights/workspaces") + .ListResourcesAsync(subscription: subscription, resourceGroup: resourceGroup, resourceType: AzureLogAnalyticsFunctions.OperationalInsightsWorkspaceResourceType) .WithCancellation(cancellationToken) .ConfigureAwait(continueOnCapturedContext: false)) { diff --git a/DirectConnector/Program.cs b/DirectConnector/Program.cs index 9fdec3f..15d9a97 100644 --- a/DirectConnector/Program.cs +++ b/DirectConnector/Program.cs @@ -4,6 +4,7 @@ using Azure.Connectors.Sdk; using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; From 81f5c6289f04accacf88f28d253066aabe248510 Mon Sep 17 00:00:00 2001 From: David Burg Date: Fri, 8 May 2026 16:46:25 -0700 Subject: [PATCH 7/8] Fix README ARM description + ARM response envelopes Co-authored-by: Dobby --- DirectConnector/ArmFunctions.cs | 8 ++++---- README.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DirectConnector/ArmFunctions.cs b/DirectConnector/ArmFunctions.cs index b4f2524..9993be1 100644 --- a/DirectConnector/ArmFunctions.cs +++ b/DirectConnector/ArmFunctions.cs @@ -58,7 +58,7 @@ public async Task ListSubscriptionsAsync( var response = request.CreateResponse(HttpStatusCode.OK); await response - .WriteAsJsonAsync(subscriptions) + .WriteAsJsonAsync(new { success = true, count = subscriptions.Count, subscriptions }) .ConfigureAwait(continueOnCapturedContext: false); return response; } @@ -110,7 +110,7 @@ public async Task ListResourceGroupsAsync( var response = request.CreateResponse(HttpStatusCode.OK); await response - .WriteAsJsonAsync(resourceGroups) + .WriteAsJsonAsync(new { success = true, count = resourceGroups.Count, resourceGroups }) .ConfigureAwait(continueOnCapturedContext: false); return response; } @@ -181,7 +181,7 @@ await badRequest var response = request.CreateResponse(HttpStatusCode.OK); await response - .WriteAsJsonAsync(resource) + .WriteAsJsonAsync(new { success = true, resource }) .ConfigureAwait(continueOnCapturedContext: false); return response; } @@ -234,7 +234,7 @@ public async Task ListResourcesByResourceGroupAsync( var response = request.CreateResponse(HttpStatusCode.OK); await response - .WriteAsJsonAsync(resources) + .WriteAsJsonAsync(new { success = true, count = resources.Count, resources }) .ConfigureAwait(continueOnCapturedContext: false); return response; } diff --git a/README.md b/README.md index 07f6410..1e02a39 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The `DirectConnector/` project is an Azure Functions (isolated worker) app with | SmtpFunctions.cs | SMTP | Send email via SMTP | | AzureBlobFunctions.cs | Azure Blob Storage | Upload, download, get metadata, delete blobs | | AzureLogAnalyticsFunctions.cs | Azure Monitor Logs | List subscriptions, list resources | -| ArmFunctions.cs | Azure Resource Manager (ARM) | List subscriptions, resource groups, deployments | +| ArmFunctions.cs | Azure Resource Manager (ARM) | List subscriptions, resource groups, read resources | ### Key Patterns Demonstrated From 4df3c668f32aa193a451240a7f1360a0dd9d09f5 Mon Sep 17 00:00:00 2001 From: David Burg Date: Fri, 8 May 2026 16:52:52 -0700 Subject: [PATCH 8/8] Prefix ARM function names + fix PR description accuracy Co-authored-by: Dobby --- DirectConnector/ArmFunctions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DirectConnector/ArmFunctions.cs b/DirectConnector/ArmFunctions.cs index 9993be1..4c5d9e2 100644 --- a/DirectConnector/ArmFunctions.cs +++ b/DirectConnector/ArmFunctions.cs @@ -36,7 +36,7 @@ public ArmFunctions( /// /// Lists all subscriptions accessible to the authenticated user. /// - [Function("ListSubscriptions")] + [Function("ArmListSubscriptions")] public async Task ListSubscriptionsAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "arm/subscriptions")] HttpRequestData request, CancellationToken cancellationToken) @@ -87,7 +87,7 @@ await errorResponse /// /// Lists resource groups in a subscription. /// - [Function("ListResourceGroups")] + [Function("ArmListResourceGroups")] public async Task ListResourceGroupsAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "arm/subscriptions/{subscriptionId}/resourcegroups")] HttpRequestData request, string subscriptionId, @@ -139,7 +139,7 @@ await errorResponse /// /// Reads a specific resource by its resource group, provider, and short resource ID. /// - [Function("ReadResource")] + [Function("ArmReadResource")] public async Task ReadResourceAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "arm/resource")] HttpRequestData request, CancellationToken cancellationToken) @@ -210,7 +210,7 @@ await errorResponse /// /// Lists resources in a resource group. /// - [Function("ListResourcesByResourceGroup")] + [Function("ArmListResources")] public async Task ListResourcesByResourceGroupAsync( [HttpTrigger(AuthorizationLevel.Function, "get", Route = "arm/subscriptions/{subscriptionId}/resourcegroups/{resourceGroupName}/resources")] HttpRequestData request, string subscriptionId,