diff --git a/dotnet/samples/Concepts/ChatCompletion/Connectors_CustomHttpClient.cs b/dotnet/samples/Concepts/ChatCompletion/Connectors_CustomHttpClient.cs
index 54de56688cdd..30df3f43870a 100644
--- a/dotnet/samples/Concepts/ChatCompletion/Connectors_CustomHttpClient.cs
+++ b/dotnet/samples/Concepts/ChatCompletion/Connectors_CustomHttpClient.cs
@@ -1,6 +1,14 @@
-// Copyright (c) Microsoft. All rights reserved.
+// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.SemanticKernel;
+using Xunit;
+using Xunit.Abstractions;
namespace ChatCompletion;
@@ -36,4 +44,126 @@ public void UseCustomHttpClient()
httpClient: httpClient)
.Build();
}
+
+ ///
+ /// Demonstrates how to use a custom DelegatingHandler to send the 'api-key' header
+ /// and 'api-version' parameter required by Azure AI Foundry / Model Catalog deployments (resolves #10455).
+ ///
+ [Fact]
+ public void UseCustomHttpClientWithApiKeyHeaderForAzureAIFoundry()
+ {
+ // Azure AI Foundry model inference endpoints (e.g. Phi-3, Mistral, Llama) require an 'api-key' header
+ // and an 'api-version' query parameter rather than the standard OpenAI 'Authorization: Bearer' token.
+ using var handler = new AzureAIFoundryApiKeyHandler("test-api-key");
+ using var httpClient = new HttpClient(handler);
+
+ var kernel = Kernel.CreateBuilder()
+ .AddOpenAIChatCompletion(
+ modelId: "Phi-3-small-8k-instruct",
+ endpoint: new Uri("https://my-resource.services.ai.azure.com/models"),
+ apiKey: "test-api-key",
+ httpClient: httpClient)
+ .Build();
+
+ Assert.NotNull(kernel);
+ }
+
+ ///
+ /// Verifies that AzureAIFoundryApiKeyHandler injects the 'api-key' header and 'api-version'
+ /// query parameter, and preserves existing values without contacting external services.
+ ///
+ [Fact]
+ public async Task AzureAIFoundryApiKeyHandler_InjectsHeaderAndApiVersionAsync()
+ {
+ HttpRequestMessage? capturedRequest = null;
+ var innerHandler = new RecordingHandler(req => capturedRequest = req);
+ using var handler = new AzureAIFoundryApiKeyHandler("my-secret-key", "2024-05-01-preview", innerHandler);
+ using var invoker = new HttpMessageInvoker(handler);
+
+ using var request = new HttpRequestMessage(HttpMethod.Post, "https://my-resource.services.ai.azure.com/models/chat/completions");
+ using var response = await invoker.SendAsync(request, CancellationToken.None);
+
+ Assert.NotNull(capturedRequest);
+ Assert.True(capturedRequest.Headers.Contains("api-key"));
+ Assert.Equal("my-secret-key", capturedRequest.Headers.GetValues("api-key").First());
+ Assert.NotNull(capturedRequest.RequestUri);
+ Assert.Contains("api-version=2024-05-01-preview", capturedRequest.RequestUri.Query);
+ }
+
+ ///
+ /// Verifies that an existing 'api-key' header is preserved if already set on the request.
+ ///
+ [Fact]
+ public async Task AzureAIFoundryApiKeyHandler_PreservesExistingHeaderAsync()
+ {
+ HttpRequestMessage? capturedRequest = null;
+ var innerHandler = new RecordingHandler(req => capturedRequest = req);
+ using var handler = new AzureAIFoundryApiKeyHandler("default-key", "2024-05-01-preview", innerHandler);
+ using var invoker = new HttpMessageInvoker(handler);
+
+ using var request = new HttpRequestMessage(HttpMethod.Post, "https://my-resource.services.ai.azure.com/models/chat/completions?existing=1");
+ request.Headers.Add("api-key", "explicit-key");
+ using var response = await invoker.SendAsync(request, CancellationToken.None);
+
+ Assert.NotNull(capturedRequest);
+ Assert.Equal("explicit-key", capturedRequest.Headers.GetValues("api-key").First());
+ Assert.NotNull(capturedRequest.RequestUri);
+ Assert.Contains("existing=1", capturedRequest.RequestUri.Query);
+ Assert.Contains("api-version=2024-05-01-preview", capturedRequest.RequestUri.Query);
+ }
+
+ ///
+ /// DelegatingHandler that injects the 'api-key' header and 'api-version' query parameter
+ /// for Azure AI Foundry and OpenAI-compatible endpoints.
+ ///
+ private sealed class AzureAIFoundryApiKeyHandler : DelegatingHandler
+ {
+ private readonly string _apiKey;
+ private readonly string _apiVersion;
+
+ public AzureAIFoundryApiKeyHandler(string apiKey, string apiVersion = "2024-05-01-preview", HttpMessageHandler? innerHandler = null)
+ : base(innerHandler ?? new HttpClientHandler())
+ {
+ this._apiKey = apiKey;
+ this._apiVersion = apiVersion;
+ }
+
+ protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ // 1. Ensure required 'api-key' header is present
+ if (!request.Headers.Contains("api-key"))
+ {
+ request.Headers.Add("api-key", this._apiKey);
+ }
+
+ // 2. Ensure required 'api-version' query parameter is present on inference endpoint
+ if (request.RequestUri is not null)
+ {
+ var uriBuilder = new UriBuilder(request.RequestUri);
+ if (!uriBuilder.Query.Contains("api-version="))
+ {
+ var query = string.IsNullOrEmpty(uriBuilder.Query) ? string.Empty : uriBuilder.Query.TrimStart('?') + "&";
+ query += $"api-version={Uri.EscapeDataString(this._apiVersion)}";
+ uriBuilder.Query = query;
+ request.RequestUri = uriBuilder.Uri;
+ }
+ }
+
+ return base.SendAsync(request, cancellationToken);
+ }
+ }
+
+ ///
+ /// In-memory handler for testing HTTP request transformations without live network calls.
+ ///
+ private sealed class RecordingHandler(Action onRequest) : HttpMessageHandler
+ {
+ private readonly Action _onRequest = onRequest;
+
+ protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ this._onRequest(request);
+ return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
+ }
+ }
}