Skip to content

Commit

Permalink
Release 1.6.0 #273
Browse files Browse the repository at this point in the history
  • Loading branch information
Y-Sindo authored Sep 10, 2021
2 parents 65369bf + 2224ad4 commit ebd989f
Show file tree
Hide file tree
Showing 19 changed files with 322 additions and 26 deletions.
3 changes: 2 additions & 1 deletion build/dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>
<PropertyGroup Label="Package Versions">
<MicroBuildCorePackageVersion>0.3.0</MicroBuildCorePackageVersion>
<MicrosoftAzureSignalRManagement>1.9.1</MicrosoftAzureSignalRManagement>
<MicrosoftAzureSignalRManagement>1.11.0</MicrosoftAzureSignalRManagement>
<MicrosoftAzureFunctionsExtensionsVersion>1.0.0</MicrosoftAzureFunctionsExtensionsVersion>
<MicrosoftNETTestSdkPackageVersion>15.8.0</MicrosoftNETTestSdkPackageVersion>
<MoqPackageVersion>4.9.0</MoqPackageVersion>
Expand All @@ -20,6 +20,7 @@
<MicrosoftAspNetCoreSignalRProtocolsNewtonsoftJson3_1>3.0.0</MicrosoftAspNetCoreSignalRProtocolsNewtonsoftJson3_1>
<MicrosoftAspNetCoreSignalRClient>5.0.3</MicrosoftAspNetCoreSignalRClient>
<MicrosoftExtensionsConfiguration>5.0.0</MicrosoftExtensionsConfiguration>
<MicrosoftExtensionsAzureVersion>1.1.0</MicrosoftExtensionsAzureVersion>

<!--E2E test functions-->
<MicrosoftNETSdkFunctionsV2>1.0.30</MicrosoftNETSdkFunctionsV2>
Expand Down
67 changes: 67 additions & 0 deletions src/SignalRServiceExtension/Config/IConfigurationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using Azure.Core;
using Microsoft.Azure.SignalR;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
{
/// <summary>
/// A helper class to parse <see cref="ServiceEndpoint"/> from configuration.
/// </summary>
internal static class IConfigurationExtensions
{
public static IEnumerable<ServiceEndpoint> GetEndpoints(this IConfiguration config, AzureComponentFactory azureComponentFactory)
{
foreach (IConfigurationSection child in config.GetChildren())
{
if (child.TryGetNamedEndpointFromIdentity(azureComponentFactory, out ServiceEndpoint endpoint))
{
yield return endpoint;
continue;
}

foreach (ServiceEndpoint item in child.GetNamedEndpointsFromConnectionString())
{
yield return item;
}
}
}

public static IEnumerable<ServiceEndpoint> GetNamedEndpointsFromConnectionString(this IConfigurationSection section)
{
string endpointName = section.Key;
if (section.Value != null)
{
yield return new ServiceEndpoint(section.Key, section.Value);
}

if (section["primary"] != null)
{
yield return new ServiceEndpoint(section["primary"], EndpointType.Primary, endpointName);
}

if (section["secondary"] != null)
{
yield return new ServiceEndpoint(section["secondary"], EndpointType.Secondary, endpointName);
}
}

public static bool TryGetNamedEndpointFromIdentity(this IConfigurationSection section, AzureComponentFactory azureComponentFactory, out ServiceEndpoint endpoint)
{
string text = section["ServiceUri"];
if (text != null)
{
string key = section.Key;
EndpointType value = section.GetValue("Type", EndpointType.Primary);
TokenCredential credential = azureComponentFactory.CreateTokenCredential(section);
endpoint = new ServiceEndpoint(new Uri(text), credential, value, key);
return true;
}

endpoint = null;
return false;
}
}
}
16 changes: 13 additions & 3 deletions src/SignalRServiceExtension/Config/OptionsSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using Microsoft.Azure.SignalR;
using Microsoft.Azure.SignalR.Management;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
Expand All @@ -15,10 +16,11 @@ namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
internal class OptionsSetup : IConfigureOptions<ServiceManagerOptions>, IOptionsChangeTokenSource<ServiceManagerOptions>
{
private readonly IConfiguration configuration;
private readonly AzureComponentFactory _azureComponentFactory;
private readonly string connectionStringKey;
private readonly ILogger logger;

public OptionsSetup(IConfiguration configuration, ILoggerFactory loggerFactory, string connectionStringKey)
public OptionsSetup(IConfiguration configuration, ILoggerFactory loggerFactory, AzureComponentFactory azureComponentFactory, string connectionStringKey)
{
if (loggerFactory is null)
{
Expand All @@ -31,6 +33,7 @@ public OptionsSetup(IConfiguration configuration, ILoggerFactory loggerFactory,
}

this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
_azureComponentFactory = azureComponentFactory;
this.connectionStringKey = connectionStringKey;
logger = loggerFactory.CreateLogger<OptionsSetup>();
}
Expand All @@ -39,8 +42,15 @@ public OptionsSetup(IConfiguration configuration, ILoggerFactory loggerFactory,

public void Configure(ServiceManagerOptions options)
{
options.ConnectionString = configuration[connectionStringKey];
options.ServiceEndpoints = configuration.GetEndpoints(Constants.AzureSignalREndpoints).ToArray();
options.ConnectionString = configuration.GetConnectionString(connectionStringKey) ?? configuration[connectionStringKey];
var endpoints = configuration.GetSection(Constants.AzureSignalREndpoints).GetEndpoints(_azureComponentFactory);
// Fall back to use a section to configure Azure identity
if (options.ConnectionString == null && configuration.GetSection(connectionStringKey).TryGetNamedEndpointFromIdentity(_azureComponentFactory, out var endpoint))
{
endpoint.Name = string.Empty;
endpoints = endpoints.Append(endpoint);
}
options.ServiceEndpoints = endpoints.ToArray();
var serviceTransportTypeStr = configuration[Constants.ServiceTransportTypeName];
if (Enum.TryParse<ServiceTransportType>(serviceTransportTypeStr, out var transport))
{
Expand Down
8 changes: 5 additions & 3 deletions src/SignalRServiceExtension/Config/ServiceManagerStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Azure.SignalR;
using Microsoft.Azure.SignalR.Management;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand All @@ -17,13 +17,15 @@ namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
internal class ServiceManagerStore : IServiceManagerStore
{
private readonly ILoggerFactory loggerFactory;
private readonly AzureComponentFactory _azureComponentFactory;
private readonly IConfiguration configuration;
private readonly IEndpointRouter router;
private readonly ConcurrentDictionary<string, IInternalServiceHubContextStore> store = new ConcurrentDictionary<string, IInternalServiceHubContextStore>();

public ServiceManagerStore(IConfiguration configuration, ILoggerFactory loggerFactory, IEndpointRouter router = null)
public ServiceManagerStore(IConfiguration configuration, ILoggerFactory loggerFactory, AzureComponentFactory azureComponentFactory, IEndpointRouter router = null)
{
this.loggerFactory = loggerFactory;
_azureComponentFactory = azureComponentFactory;
this.configuration = configuration;
this.router = router;
}
Expand All @@ -46,7 +48,7 @@ public IInternalServiceHubContextStore GetByConfigurationKey(string connectionSt
private IInternalServiceHubContextStore CreateHubContextStore(string connectionStringKey)
{
var services = new ServiceCollection()
.SetupOptions<ServiceManagerOptions, OptionsSetup>(new OptionsSetup(configuration, loggerFactory, connectionStringKey))
.SetupOptions<ServiceManagerOptions, OptionsSetup>(new OptionsSetup(configuration, loggerFactory, _azureComponentFactory, connectionStringKey))
.PostConfigure<ServiceManagerOptions>(o =>
{
if ((o.ServiceEndpoints == null || o.ServiceEndpoints.Length == 0) && string.IsNullOrWhiteSpace(o.ConnectionString))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="$(MicrosoftAzureFunctionsExtensionsVersion)" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="$(SystemIdentityModelTokensJwtVersion)" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="$(MicrosoftAspNetCoreSignalRPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Azure" Version="$(MicrosoftExtensionsAzureVersion)"/>
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1'">
Expand Down
21 changes: 19 additions & 2 deletions src/SignalRServiceExtension/TriggerBindings/ServerlessHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Reflection;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
Expand Down Expand Up @@ -40,16 +41,32 @@ public abstract class ServerlessHub : IDisposable
/// </summary>
protected ServerlessHub(IServiceHubContext hubContext = null, IServiceManager serviceManager = null)
{
var hubContextAttribute = GetType().GetCustomAttribute<SignalRConnectionAttribute>(true);
var connectionString = hubContextAttribute?.Connection ?? Constants.AzureSignalRConnectionStringName;
HubName = GetType().Name;
hubContext = hubContext ?? StaticServiceHubContextStore.Get().GetAsync(HubName).GetAwaiter().GetResult();
_serviceManager = serviceManager ?? StaticServiceHubContextStore.Get().ServiceManager;
hubContext = hubContext ?? StaticServiceHubContextStore.Get(connectionString).GetAsync(HubName).GetAwaiter().GetResult();
_serviceManager = serviceManager ?? StaticServiceHubContextStore.Get(connectionString).ServiceManager;
Clients = hubContext.Clients;
Groups = hubContext.Groups;
UserGroups = hubContext.UserGroups;
_hubContext = hubContext as ServiceHubContext;
ClientManager = _hubContext?.ClientManager;
}

/// <summary>
/// Customized settings to be passed into the serverless hub context.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
protected internal class SignalRConnectionAttribute : Attribute, IConnectionProvider
{
public SignalRConnectionAttribute(string connectionStringSetting)
{
Connection = connectionStringSetting;
}

public string Connection { get; set; } = Constants.AzureSignalRConnectionStringName;
}

/// <summary>
/// Gets an object that can be used to invoke methods on the clients connected to this hub.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Azure.WebJobs.Host.Triggers;
using Microsoft.Extensions.Logging;
using static Microsoft.Azure.WebJobs.Extensions.SignalRService.ServerlessHub;

namespace Microsoft.Azure.WebJobs.Extensions.SignalRService
{
Expand Down Expand Up @@ -52,9 +53,9 @@ public async Task<ITriggerBinding> TryCreateAsync(TriggerBindingProviderContext
var resolvedAttribute = GetParameterResolvedAttribute(attribute, parameterInfo);
ValidateSignalRTriggerAttributeBinding(resolvedAttribute);

var accessKeys = _managerStore.GetOrAddByConnectionStringKey(attribute.ConnectionStringSetting).AccessKeys;
var accessKeys = _managerStore.GetOrAddByConnectionStringKey(resolvedAttribute.ConnectionStringSetting).AccessKeys;

var hubContext = await _managerStore.GetOrAddByConnectionStringKey(attribute.ConnectionStringSetting).GetAsync(resolvedAttribute.HubName);
var hubContext = await _managerStore.GetOrAddByConnectionStringKey(resolvedAttribute.ConnectionStringSetting).GetAsync(resolvedAttribute.HubName);

return new SignalRTriggerBinding(parameterInfo, resolvedAttribute, _dispatcher, accessKeys, hubContext as ServiceHubContext);
}
Expand All @@ -66,6 +67,7 @@ internal SignalRTriggerAttribute GetParameterResolvedAttribute(SignalRTriggerAtt
var category = attribute.Category;
var @event = attribute.Event;
var parameterNames = attribute.ParameterNames ?? Array.Empty<string>();
var connectionStringSetting = attribute.ConnectionStringSetting;

// We have two models for C#, one is function based model which also work in multiple language
// Another one is class based model, which is highly close to SignalR itself but must keep some conventions.
Expand All @@ -87,6 +89,7 @@ internal SignalRTriggerAttribute GetParameterResolvedAttribute(SignalRTriggerAtt
hubName = declaredType.Name;
category = GetCategoryFromMethodName(method.Name);
@event = GetEventFromMethodName(method.Name, category);
connectionStringSetting = declaredType.GetCustomAttribute<SignalRConnectionAttribute>()?.Connection ?? attribute.ConnectionStringSetting;
}
else
{
Expand All @@ -110,7 +113,7 @@ internal SignalRTriggerAttribute GetParameterResolvedAttribute(SignalRTriggerAtt
? parameterNamesFromAttribute
: parameterNames;

return new SignalRTriggerAttribute(hubName, category, @event, parameterNames) { ConnectionStringSetting = attribute.ConnectionStringSetting };
return new SignalRTriggerAttribute(hubName, category, @event, parameterNames) { ConnectionStringSetting = connectionStringSetting };
}

private void ValidateSignalRTriggerAttributeBinding(SignalRTriggerAttribute attribute)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public async Task GetClientConnectionInfo()
var connectionStringKey = Constants.AzureSignalRConnectionStringName;
var configDict = new Dictionary<string, string>() { { Constants.ServiceTransportTypeName, "Transient" }, { connectionStringKey, connectionString } };
var configuration = new ConfigurationBuilder().AddInMemoryCollection(configDict).Build();
var serviceManagerStore = new ServiceManagerStore(configuration, NullLoggerFactory.Instance, new TestRouter());
var serviceManagerStore = new ServiceManagerStore(configuration, NullLoggerFactory.Instance, SingletonAzureComponentFactory.Instance, new TestRouter());
var azureSignalRClient = await SignalRUtils.GetAzureSignalRClientAsync(connectionStringKey, hubName, serviceManagerStore);
var connectionInfo = await azureSignalRClient.GetClientConnectionInfoAsync(userId, idToken, claimTypeList, null);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Linq;
using Azure.Identity;
using Microsoft.Azure.SignalR;
using Microsoft.Azure.SignalR.Tests.Common;
using Microsoft.Azure.WebJobs.Extensions.SignalRService;
using Microsoft.Extensions.Azure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace SignalRServiceExtension.Tests.Config
{
public class IConfigurationExtensionsFacts
{
[Fact]
public void TestGetNamedEndpointFromIdentityWithNoUri()
{
var services = new ServiceCollection();
services.AddAzureClientsCore();
var factory = services.BuildServiceProvider().GetRequiredService<AzureComponentFactory>();
var config = new ConfigurationBuilder().AddInMemoryCollection().Build();
Assert.False(config.GetSection("eastus").TryGetNamedEndpointFromIdentity(factory, out _));
}

[Fact]
public void TestGetNamedEndpointFromIdentityWithOnlyUri()
{
var services = new ServiceCollection();
services.AddAzureClientsCore();
var factory = services.BuildServiceProvider().GetRequiredService<AzureComponentFactory>();

var config = new ConfigurationBuilder().AddInMemoryCollection().Build();
var uri = "http://signalr.service.uri.com:441";
config["eastus:serviceUri"] = uri;

Assert.True(config.GetSection("eastus").TryGetNamedEndpointFromIdentity(factory, out var endpoint));
Assert.Equal("eastus", endpoint.Name);
Assert.Equal(uri, endpoint.Endpoint);
Assert.IsType<DefaultAzureCredential>((endpoint.AccessKey as AadAccessKey).TokenCredential);
Assert.Equal(EndpointType.Primary, endpoint.EndpointType);
}

[Fact]
public void TestGetNamedEndpointFromIdentityWithAllEndpointField()
{
var services = new ServiceCollection();
services.AddAzureClientsCore();
var factory = services.BuildServiceProvider().GetRequiredService<AzureComponentFactory>();

var config = new ConfigurationBuilder().AddInMemoryCollection().Build();
var uri = "http://signalr.service.uri.com:441";
config["eastus:serviceUri"] = uri;
config["eastus:credential"] = "managedidentity";
config["eastus:type"] = "secondary";

Assert.True(config.GetSection("eastus").TryGetNamedEndpointFromIdentity(factory, out var endpoint));
Assert.Equal("eastus", endpoint.Name);
Assert.Equal(uri, endpoint.Endpoint);
Assert.IsType<ManagedIdentityCredential>((endpoint.AccessKey as AadAccessKey).TokenCredential);
Assert.Equal(EndpointType.Secondary, endpoint.EndpointType);
}

[Fact]
public void TestGetNamedEndpointsFromConnectionString()
{
var connectionString = FakeEndpointUtils.GetFakeConnectionString(1).Single();
var config = new ConfigurationBuilder().AddInMemoryCollection().Build();
config["eastus"] = connectionString;
config["eastus:primary"] = connectionString;
config["eastus:secondary"] = connectionString;
var endpoints = config.GetSection("eastus").GetNamedEndpointsFromConnectionString().ToArray();
Assert.Equal(3, endpoints.Length);
Assert.All(endpoints, e => Assert.Equal("eastus", e.Name));
Assert.Equal(EndpointType.Primary, endpoints[0].EndpointType);
Assert.Equal(EndpointType.Primary, endpoints[1].EndpointType);
Assert.Equal(EndpointType.Secondary, endpoints[2].EndpointType);
}

[Fact]
public void TestGetEndpointsFromIdentityAndConnectionString()
{
var services = new ServiceCollection();
services.AddAzureClientsCore();
var factory = services.BuildServiceProvider().GetRequiredService<AzureComponentFactory>();
var config = new ConfigurationBuilder().AddInMemoryCollection().Build();

var serviceUri = "http://signalr.service.uri.com:441";
config["endpoints:eastus:serviceUri"] = serviceUri;
config["endpoints:westus:secondary"] = FakeEndpointUtils.GetFakeConnectionString(1).Single();

var endpoints = config.GetSection("endpoints").GetEndpoints(factory).ToArray();
Assert.Collection(endpoints, e =>
{
Assert.Equal("eastus", e.Name);
Assert.Equal(serviceUri, e.Endpoint);
}, e =>
{
Assert.Equal("westus", e.Name);
Assert.Equal(EndpointType.Secondary, e.EndpointType);
});
}
}
}
Loading

0 comments on commit ebd989f

Please sign in to comment.