diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/IWebPubSubServiceClientFactory.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/IWebPubSubServiceClientFactory.cs
new file mode 100644
index 000000000000..3d156955948d
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/IWebPubSubServiceClientFactory.cs
@@ -0,0 +1,22 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Azure.Messaging.WebPubSub;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ internal interface IWebPubSubServiceClientFactory
+ {
+ ///
+ /// Creates a WebPubSubServiceClient with fallback connection and hub resolution.
+ /// Priority for connection:
+ /// 1. attributeConnection (can be connection string or config section name)
+ /// 2. options (identity-based connection prioritized over connection string for security)
+ /// Priority for hub: attributeHub > options.Hub
+ ///
+ /// Connection from the attribute (can be connection string or config section name).
+ /// Hub from the attribute (highest priority).
+ /// A configured WebPubSubServiceClient instance.
+ WebPubSubServiceClient Create(string attributeConnection, string attributeHub);
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs
index 6d2e6a8b34b0..4c1e6c2e39b9 100644
--- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubConfigProvider.cs
@@ -27,18 +27,24 @@ internal class WebPubSubConfigProvider : IExtensionConfigProvider, IAsyncConvert
private readonly ILogger _logger;
private readonly WebPubSubFunctionsOptions _options;
private readonly IWebPubSubTriggerDispatcher _dispatcher;
+ private readonly IWebPubSubServiceClientFactory _clientFactory;
+ private readonly IOptionsMonitor _accessOptions;
public WebPubSubConfigProvider(
IOptions options,
INameResolver nameResolver,
ILoggerFactory loggerFactory,
- IConfiguration configuration)
+ IConfiguration configuration,
+ IOptionsMonitor accessOptions,
+ IWebPubSubServiceClientFactory clientFactory)
{
_options = options.Value;
_logger = loggerFactory.CreateLogger(LogCategories.CreateTriggerCategory("WebPubSub"));
_nameResolver = nameResolver;
_configuration = configuration;
_dispatcher = new WebPubSubTriggerDispatcher(_logger, _options);
+ _accessOptions = accessOptions;
+ _clientFactory = clientFactory;
}
public void Initialize(ExtensionConfigContext context)
@@ -48,16 +54,6 @@ public void Initialize(ExtensionConfigContext context)
throw new ArgumentNullException(nameof(context));
}
- if (string.IsNullOrEmpty(_options.ConnectionString))
- {
- _options.ConnectionString = _nameResolver.Resolve(Constants.WebPubSubConnectionStringName);
- }
-
- if (string.IsNullOrEmpty(_options.Hub))
- {
- _options.Hub = _nameResolver.Resolve(Constants.HubNameStringName);
- }
-
Exception webhookException = null;
try
{
@@ -107,25 +103,53 @@ public Task ConvertAsync(HttpRequestMessage input, Cancella
return _dispatcher.ExecuteAsync(input, cancellationToken);
}
- private void ValidateWebPubSubConnectionAttributeBinding(WebPubSubConnectionAttribute attribute, Type type)
+ internal WebPubSubService GetService(WebPubSubAttribute attribute)
{
- ValidateConnectionString(
+ var client = _clientFactory.Create(
attribute.Connection,
- $"{nameof(WebPubSubConnectionAttribute)}.{nameof(WebPubSubConnectionAttribute.Connection)}");
+ attribute.Hub);
+ return new WebPubSubService(client);
}
private void ValidateWebPubSubAttributeBinding(WebPubSubAttribute attribute, Type type)
{
- ValidateConnectionString(
- attribute.Connection,
- $"{nameof(WebPubSubAttribute)}.{nameof(WebPubSubAttribute.Connection)}");
+ ValidateWebPubSubConnectionCore(attribute.Connection, attribute.Hub, "WebPubSub");
}
- internal WebPubSubService GetService(WebPubSubAttribute attribute)
+ private void ValidateWebPubSubConnectionAttributeBinding(WebPubSubConnectionAttribute attribute, Type type)
+ {
+ ValidateWebPubSubConnectionCore(attribute.Connection, attribute.Hub, "WebPubSubConnection");
+ }
+
+ private void ValidateWebPubSubConnectionCore(string attributeConnection, string attributeHub, string extensionType)
{
- var connectionString = Utilities.FirstOrDefault(attribute.Connection, _options.ConnectionString);
- var hubName = Utilities.FirstOrDefault(attribute.Hub, _options.Hub);
- return new WebPubSubService(connectionString, hubName);
+ var webPubSubAccessExists = true;
+ if (attributeConnection == null)
+ {
+ if (_accessOptions.CurrentValue.WebPubSubAccess == null)
+ {
+ webPubSubAccessExists = false;
+ }
+ }
+ else
+ {
+ if (!WebPubSubServiceAccessUtil.CanCreateFromIConfiguration(_configuration.GetSection(attributeConnection)))
+ {
+ webPubSubAccessExists = false;
+ }
+ }
+ if (!webPubSubAccessExists)
+ {
+ throw new InvalidOperationException(
+ $"Connection must be specified through one of the following:" + Environment.NewLine +
+ $" * Set '{extensionType}.Connection' property to the name of a config section that contains a Web PubSub connection." + Environment.NewLine +
+ $" * Set a Web PubSub connection under '{Constants.WebPubSubConnectionStringName}'.");
+ }
+
+ if ((attributeHub ?? _accessOptions.CurrentValue.Hub) is null)
+ {
+ throw new InvalidOperationException($"Resolved 'Hub' value is null for extension '{extensionType}''");
+ }
}
private IAsyncCollector CreateCollector(WebPubSubAttribute attribute)
@@ -135,21 +159,13 @@ private IAsyncCollector CreateCollector(WebPubSubAttribute attr
private WebPubSubConnection GetClientConnection(WebPubSubConnectionAttribute attribute)
{
- var hub = Utilities.FirstOrDefault(attribute.Hub, _options.Hub);
- var service = new WebPubSubService(attribute.Connection, hub);
+ var client = _clientFactory.Create(
+ attribute.Connection,
+ attribute.Hub);
+ var service = new WebPubSubService(client);
return service.GetClientConnection(attribute.UserId, clientProtocol: attribute.ClientProtocol);
}
- private void ValidateConnectionString(string attributeConnectionString, string attributeConnectionStringName)
- {
- var connectionString = Utilities.FirstOrDefault(attributeConnectionString, _options.ConnectionString);
-
- if (string.IsNullOrEmpty(connectionString))
- {
- throw new InvalidOperationException($"The Service connection string must be set either via an '{Constants.WebPubSubConnectionStringName}' app setting, via an '{Constants.WebPubSubConnectionStringName}' environment variable, or directly in code via {nameof(WebPubSubFunctionsOptions)}.{nameof(WebPubSubFunctionsOptions.ConnectionString)} or {attributeConnectionStringName}.");
- }
- }
-
internal static void RegisterJsonConverter()
{
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubJobsBuilderExtensions.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubJobsBuilderExtensions.cs
index 875faa8fde06..c58058d7bd76 100644
--- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubJobsBuilderExtensions.cs
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubJobsBuilderExtensions.cs
@@ -4,7 +4,11 @@
using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.WebPubSub;
+using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.Hosting
{
@@ -20,6 +24,7 @@ public static class WebPubSubJobsBuilderExtensions
/// .
public static IWebJobsBuilder AddWebPubSub(this IWebJobsBuilder builder)
{
+ ;
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
@@ -27,6 +32,13 @@ public static IWebJobsBuilder AddWebPubSub(this IWebJobsBuilder builder)
builder.AddExtension()
.ConfigureOptions(ApplyConfiguration);
+
+ // Register the options setup to read from default configuration section
+ builder.Services.AddSingleton, WebPubSubServiceAccessOptionsSetup>();
+
+ builder.Services.AddAzureClientsCore();
+ builder.Services.TryAddSingleton();
+
return builder;
}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceAccess.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceAccess.cs
new file mode 100644
index 000000000000..ccd0edf4f2bc
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceAccess.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub;
+
+#nullable enable
+
+///
+/// Access information to Web PubSub service.
+///
+internal class WebPubSubServiceAccess(Uri serviceEndpoint, WebPubSubServiceCredential credential)
+{
+ public Uri ServiceEndpoint { get; } = serviceEndpoint;
+ public WebPubSubServiceCredential Credential { get; } = credential;
+}
\ No newline at end of file
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceAccessOptions.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceAccessOptions.cs
new file mode 100644
index 000000000000..b93eb433ab1c
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceAccessOptions.cs
@@ -0,0 +1,10 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub;
+
+internal class WebPubSubServiceAccessOptions
+{
+ public WebPubSubServiceAccess? WebPubSubAccess { get; set; }
+ public string? Hub { get; set; }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceAccessOptionsSetup.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceAccessOptionsSetup.cs
new file mode 100644
index 000000000000..b79dfe860d51
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceAccessOptionsSetup.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Microsoft.Extensions.Azure;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
+{
+ ///
+ /// Configures by reading from the default configuration section.
+ ///
+ internal class WebPubSubServiceAccessOptionsSetup : IConfigureOptions
+ {
+ private readonly IConfiguration _configuration;
+ private readonly AzureComponentFactory _azureComponentFactory;
+ private readonly INameResolver _nameResolver;
+ private readonly IOptionsMonitor _publicOptions;
+
+ public WebPubSubServiceAccessOptionsSetup(
+ IConfiguration configuration,
+ AzureComponentFactory azureComponentFactory,
+ INameResolver nameResolver,
+ IOptionsMonitor publicOptions)
+ {
+ _configuration = configuration;
+ _azureComponentFactory = azureComponentFactory;
+ _nameResolver = nameResolver;
+ _publicOptions = publicOptions;
+ }
+
+ public void Configure(WebPubSubServiceAccessOptions options)
+ {
+ var publicOptions = _publicOptions.CurrentValue;
+
+ // WebPubSubFunctionsOptions.ConnectionString can be set via code only. Takes the highest priority.
+ if (!string.IsNullOrEmpty(publicOptions.ConnectionString))
+ {
+ options.WebPubSubAccess = WebPubSubServiceAccessUtil.CreateFromConnectionString(publicOptions.ConnectionString);
+ }
+ else
+ {
+ var defaultSection = _configuration.GetSection(Constants.WebPubSubConnectionStringName);
+ if (WebPubSubServiceAccessUtil.CreateFromIConfiguration(defaultSection, _azureComponentFactory, out var access))
+ {
+ options.WebPubSubAccess = access!;
+ }
+ }
+
+ // Only configure Hub from the default config section if not already set
+ options.Hub = publicOptions.Hub ?? _nameResolver.Resolve(Constants.HubNameStringName);
+ }
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceAccessUtil.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceAccessUtil.cs
new file mode 100644
index 000000000000..024d53407cc2
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceAccessUtil.cs
@@ -0,0 +1,117 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Azure;
+using Microsoft.Extensions.Configuration;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub;
+
+internal static class WebPubSubServiceAccessUtil
+{
+ private const string EndpointPropertyName = "Endpoint";
+ private const string AccessKeyPropertyName = "AccessKey";
+ private const string PortPropertyName = "Port";
+ private static readonly char[] KeyValueSeparator = { '=' };
+ private static readonly char[] PropertySeparator = { ';' };
+
+ internal static WebPubSubServiceAccess CreateFromConnectionString(string connectionString)
+ {
+ if (string.IsNullOrEmpty(connectionString))
+ {
+ throw new ArgumentNullException(nameof(connectionString));
+ }
+
+ var properties = connectionString.Split(PropertySeparator, StringSplitOptions.RemoveEmptyEntries);
+
+ var dict = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ foreach (var property in properties)
+ {
+ var kvp = property.Split(KeyValueSeparator, 2);
+ if (kvp.Length != 2)
+ continue;
+
+ var key = kvp[0].Trim();
+ if (dict.ContainsKey(key))
+ {
+ throw new ArgumentException($"Duplicate properties found in connection string: {key}.");
+ }
+
+ dict.Add(key, kvp[1].Trim());
+ }
+
+ if (!dict.TryGetValue(EndpointPropertyName, out var endpoint))
+ {
+ throw new ArgumentException($"Required property not found in connection string: {EndpointPropertyName}.");
+ }
+ endpoint = endpoint.TrimEnd('/');
+
+ // AccessKey is optional when connection string is disabled.
+ dict.TryGetValue(AccessKeyPropertyName, out var accessKey);
+
+ int? port = null;
+ if (dict.TryGetValue(PortPropertyName, out var rawPort))
+ {
+ if (int.TryParse(rawPort, out var portValue) && portValue > 0 && portValue <= 0xFFFF)
+ {
+ port = portValue;
+ }
+ else
+ {
+ throw new ArgumentException($"Invalid Port value: {rawPort}");
+ }
+ }
+
+ var uriBuilder = new UriBuilder(endpoint);
+ if (port.HasValue)
+ {
+ uriBuilder.Port = port.Value;
+ }
+
+ return new WebPubSubServiceAccess(uriBuilder.Uri, new KeyCredential(accessKey));
+ }
+
+ internal static bool CreateFromIConfiguration(IConfigurationSection section, AzureComponentFactory azureComponentFactory, out WebPubSubServiceAccess? result)
+ {
+ if (!string.IsNullOrEmpty(section.Value))
+ {
+ result = CreateFromConnectionString(section.Value);
+ return true;
+ }
+ else
+ {
+ // Check if this is an identity-based connection (has serviceUri)
+ var serviceUri = section[Constants.ServiceUriKey];
+ if (!string.IsNullOrEmpty(serviceUri))
+ {
+ var endpoint = new Uri(serviceUri);
+ var tokenCrential = azureComponentFactory.CreateTokenCredential(section);
+ result = new WebPubSubServiceAccess(endpoint, new IdentityCredential(tokenCrential));
+ return true;
+ }
+ }
+ result = null;
+ return false;
+ }
+
+ internal static bool CanCreateFromIConfiguration(IConfigurationSection section)
+ {
+ if (!string.IsNullOrEmpty(section.Value))
+ {
+ // Assume connection string exists.
+ return true;
+ }
+ else
+ {
+ // Check if this is an identity-based connection (has serviceUri)
+ var serviceUri = section[Constants.ServiceUriKey];
+ if (!string.IsNullOrEmpty(serviceUri))
+ {
+ // Identity-based connection
+ return true;
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceClientFactory.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceClientFactory.cs
new file mode 100644
index 000000000000..51542c0164c9
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceClientFactory.cs
@@ -0,0 +1,79 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using Azure;
+using Azure.Messaging.WebPubSub;
+using Microsoft.Extensions.Azure;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub;
+
+internal class WebPubSubServiceClientFactory(
+ IConfiguration configuration,
+ AzureComponentFactory azureComponentFactory,
+ IOptions options) : IWebPubSubServiceClientFactory
+{
+ private readonly WebPubSubServiceAccessOptions _options = options.Value;
+
+ ///
+ /// Creates a WebPubSubServiceClient with fallback connection and hub resolution.
+ /// Priority for connection:
+ /// 1. attributeConnection (can be connection string or config section name)
+ /// 2. options (identity-based connection prioritized over connection string)
+ /// Priority for hub: attributeHub > options.Hub.
+ ///
+ /// Connection from the attribute (can be connection string or config section name).
+ /// Hub from the attribute (highest priority).
+ /// A configured WebPubSubServiceClient instance.
+ public WebPubSubServiceClient Create(string attributeConnection, string attributeHub)
+ {
+ // Resolve hub with priority: attribute > options
+ var hub = attributeHub ?? _options.Hub;
+
+ // Already validated
+ Debug.Assert(hub is not null);
+
+ WebPubSubServiceAccess? access;
+ // Determine the connection source and create client accordingly
+ if (!string.IsNullOrEmpty(attributeConnection))
+ {
+ if (WebPubSubServiceAccessUtil.CreateFromIConfiguration(configuration.GetSection(attributeConnection), azureComponentFactory, out var fromConfig))
+ {
+ access = fromConfig;
+ }
+ else
+ {
+ // This should not happen because we have validated the attribute.
+ throw new InvalidOperationException(
+ $"Valid Web PubSub connection is missing.");
+ }
+ }
+ else if (_options.WebPubSubAccess != null)
+ {
+ access = _options.WebPubSubAccess;
+ }
+ else
+ {
+ // This should not happen because we have validated the attribute.
+ throw new InvalidOperationException(
+ $"Valid Web PubSub connection is missing.");
+ }
+ return CreateClient(access, hub);
+ }
+
+ private static WebPubSubServiceClient CreateClient(WebPubSubServiceAccess access, string hub)
+ {
+ if (access.Credential is KeyCredential keyCredential)
+ {
+ return new WebPubSubServiceClient(access.ServiceEndpoint, hub, new AzureKeyCredential(keyCredential.AccessKey));
+ }
+ if (access.Credential is IdentityCredential identityCredential)
+ {
+ return new WebPubSubServiceClient(access.ServiceEndpoint, hub, identityCredential.TokenCredential);
+ }
+ throw new InvalidOperationException($"Unsupported credential type {access.Credential.GetType().Name} for WebPubSubServiceClient.");
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceCredential.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceCredential.cs
new file mode 100644
index 000000000000..be9ca482c440
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Config/WebPubSubServiceCredential.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Azure.Core;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub;
+
+///
+/// Can be key-based credential, identity-based connection, or null (A connection string without access key provided to Web PubSub trigger)
+///
+internal abstract class WebPubSubServiceCredential
+{
+ public abstract bool CanValidateSignature { get; }
+}
+
+internal class KeyCredential(string accessKey) : WebPubSubServiceCredential
+{
+ public override bool CanValidateSignature => !string.IsNullOrEmpty(AccessKey);
+ public string AccessKey { get; } = accessKey;
+}
+
+internal class IdentityCredential(TokenCredential tokenCredential) : WebPubSubServiceCredential
+{
+ public override bool CanValidateSignature => false;
+
+ public TokenCredential TokenCredential { get; } = tokenCredential;
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Constants.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Constants.cs
index ea164ab99822..bf315509f055 100644
--- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Constants.cs
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Constants.cs
@@ -13,6 +13,9 @@ internal static class Constants
public const string HubNameStringName = "WebPubSubHub";
public const string WebPubSubValidationStringName = "WebPubSubValidation";
+ // Identity-based connection configuration keys
+ public const string ServiceUriKey = "serviceUri";
+
public const string MqttWebSocketSubprotocolValue = "mqtt";
public static class ContentTypes
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj
index 60fe1fb05c7b..245a5af33b4c 100644
--- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/Microsoft.Azure.WebJobs.Extensions.WebPubSub.csproj
@@ -1,4 +1,4 @@
-
+
$(RequiredTargetFrameworks)
@@ -6,8 +6,7 @@
Azure, WebPubSub
Azure Functions extension for the WebPubSub service
1.10.0-beta.1
-
- 1.9.0
+
$(NoWarn);CS8632;CA1056;CA2227
true
true
@@ -25,6 +24,7 @@
+
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubAttribute.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubAttribute.cs
index 7162a8791231..2051b125211b 100644
--- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubAttribute.cs
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubAttribute.cs
@@ -15,9 +15,8 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
public class WebPubSubAttribute : Attribute
{
///
- /// The connection of target Web PubSub service.
+ /// The connection name that resolves to the service endpoint URI or connection string.
///
- [ConnectionString]
public string Connection { get; set; } = Constants.WebPubSubConnectionStringName;
///
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs
index 5dc6b970c12e..078de9b2cb78 100644
--- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/src/WebPubSubConnectionAttribute.cs
@@ -15,9 +15,8 @@ namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub
public class WebPubSubConnectionAttribute : Attribute
{
///
- /// Target Web PubSub service connection string.
+ /// The configuration section name that resolves to the service endpoint URI or connection string.
///
- [ConnectionString]
public string Connection { get; set; } = Constants.WebPubSubConnectionStringName;
///
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestAzureComponentFactory.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestAzureComponentFactory.cs
new file mode 100644
index 000000000000..28a827eda180
--- /dev/null
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/Common/TestAzureComponentFactory.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Microsoft.Extensions.Azure;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.Azure.WebJobs.Extensions.WebPubSub.Tests;
+
+internal static class TestAzureComponentFactory
+{
+ public static AzureComponentFactory Instance;
+
+ static TestAzureComponentFactory()
+ {
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddAzureClientsCore();
+ Instance = serviceCollection.BuildServiceProvider()
+ .GetRequiredService();
+ }
+}
diff --git a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JobHostEndToEndTests.cs b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JobHostEndToEndTests.cs
index d972729699a1..3f788b6b5b23 100644
--- a/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JobHostEndToEndTests.cs
+++ b/sdk/webpubsub/Microsoft.Azure.WebJobs.Extensions.WebPubSub/tests/JobHostEndToEndTests.cs
@@ -17,62 +17,104 @@ public class JobHostEndToEndTests
{
private static readonly WebPubSubConnectionContext TestContext = CreateConnectionContext();
private static readonly BinaryData TestMessage = BinaryData.FromString("JobHostEndToEndTests");
- private static readonly Dictionary FuncConfiguration = new()
+ private static readonly Dictionary FuncConfiguration_WithGlobalConnectionString = new()
{
{ Constants.WebPubSubConnectionStringName, "Endpoint=https://abc;AccessKey=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGH;Version=1.0;" }
};
+ private static readonly Dictionary FuncConfiguration_WithGlobalIdentity = new()
+ {
+ { Constants.WebPubSubConnectionStringName+":serviceUri", "https://abc" }
+ };
+ private static readonly Dictionary FuncConfiguration_WithLocalConnectionString = new()
+ {
+ { "LocalConnection", "Endpoint=https://abc;AccessKey=ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGH;Version=1.0;" }
+ };
+ private static readonly Dictionary FuncConfiguration_WithLocalIdentity = new()
+ {
+ { "LocalConnection:serviceUri", "https://abc" }
+ };
+
+ public static readonly IEnumerable