Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure Web PubSub service: MapWebPubSubHub extension with custom hub name #47022

Merged
merged 6 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions sdk/webpubsub/Microsoft.Azure.WebPubSub.AspNetCore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public void ConfigureServices(IServiceCollection services)

### Map `WebPubSubHub` to endpoint routing

The name of the hub has to match the class name e.g. `SampleHub`.

```C# Snippet:WebPubSubMapHub
public void Configure(IApplicationBuilder app)
{
Expand All @@ -71,6 +73,18 @@ public void Configure(IApplicationBuilder app)
}
```

Hub name can be overriden by using extension method.

```C# Snippet:WebPubSubMapHubCustom
public void Configure(IApplicationBuilder app)
{
app.UseEndpoints(endpoint =>
{
endpoint.MapWebPubSubHub<SampleHub>("/eventhandler", "customHub");
});
}
```

## Key concepts

For information about general Web PubSub concepts [Concepts in Azure Web PubSub](https://docs.microsoft.com/azure/azure-web-pubsub/key-concepts)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace Microsoft.AspNetCore.Builder
public static partial class WebPubSubEndpointRouteBuilderExtensions
{
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapWebPubSubHub<THub>(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string path) where THub : Microsoft.Azure.WebPubSub.AspNetCore.WebPubSubHub { throw null; }
public static Microsoft.AspNetCore.Builder.IEndpointConventionBuilder MapWebPubSubHub<THub>(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string path, string hubName) where THub : Microsoft.Azure.WebPubSub.AspNetCore.WebPubSubHub { throw null; }
}
}
namespace Microsoft.Azure.WebPubSub.AspNetCore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public static class WebPubSubEndpointRouteBuilderExtensions
/// <summary>
/// Maps the <see cref="WebPubSubHub"/> to the path <paramref name="path"/>.
/// </summary>
/// <typeparam name="THub">User implemented <see cref="WebPubSubHub"/>.</typeparam>
/// <typeparam name="THub">User implemented <see cref="WebPubSubHub"/>.
/// Name of the class has to match the name of the hub in the Azure portal.</typeparam>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
/// <param name="path">The path to map the <see cref="WebPubSubHub"/>.</param>
/// <returns>The <see cref="IEndpointConventionBuilder"/>.</returns>
vicancy marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -41,5 +42,39 @@ public static IEndpointConventionBuilder MapWebPubSubHub<THub>(

return endpoints.Map(path, app.Build());
}

/// <summary>
/// Maps the <see cref="WebPubSubHub"/> to the path "/client" with the specified hub name.
/// </summary>
/// <typeparam name="THub">User implemented <see cref="WebPubSubHub"/>.</typeparam>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/>.</param>
/// <param name="path">The path to map the <see cref="WebPubSubHub"/>.</param>
/// <param name="hubName">The name of the hub to connect to.</param>
/// <returns>The <see cref="IEndpointConventionBuilder"/>.</returns>
public static IEndpointConventionBuilder MapWebPubSubHub<THub>(
this IEndpointRouteBuilder endpoints, string path,
string hubName) where THub : WebPubSubHub
{
if (endpoints == null)
{
throw new ArgumentNullException(nameof(endpoints));
}
if (string.IsNullOrEmpty(hubName))
{
throw new ArgumentNullException(nameof(hubName));
}

var marker = endpoints.ServiceProvider.GetService<WebPubSubMarkerService>() ?? throw new InvalidOperationException(
"Unable to find the required services. Please add all the required services by calling " +
"'IServiceCollection.AddWebPubSub' inside the call to 'ConfigureServices(...)' in the application startup code.");

var adaptor = endpoints.ServiceProvider.GetService<ServiceRequestHandlerAdapter>();
adaptor.RegisterHub<THub>(hubName);

var app = endpoints.CreateApplicationBuilder();
app.UseMiddleware<WebPubSubMiddleware>();

return endpoints.Map(path, app.Build());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ public void RegisterHub<THub>() where THub : WebPubSubHub
RegisterHub(hub.GetType().Name, hub);
}

public void RegisterHub<THub>(string hubName) where THub : WebPubSubHub
{
var hub = Create<THub>();
RegisterHub(hubName, hub);
}

// For test only
internal void RegisterHub(string hubName, WebPubSubHub hub)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

#if NETCOREAPP
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NUnit.Framework;
Expand Down Expand Up @@ -59,6 +60,23 @@ public void TestWebPubSubConfigureEmptyOptionsAndAddHubThrows()
Assert.Throws<ArgumentException>(() => clientFactory.Create<TestHub>());
}

[Test]
public void TestMapWebPubSubHubConfigureNormal()
vicancy marked this conversation as resolved.
Show resolved Hide resolved
{
var testHost = "webpubsub.azure.net";
WebApplicationBuilder builder = WebApplication.CreateBuilder();
builder.Services
.AddWebPubSub(o => o.ServiceEndpoint = new WebPubSubServiceEndpoint($"Endpoint=https://{testHost};AccessKey=7aab239577fd4f24bc919802fb629f5f;Version=1.0;"));

using var app = builder.Build();
app.MapWebPubSubHub<TestHub>("/testhub");

var validator = app.Services.GetRequiredService<RequestValidator>();

Assert.NotNull(validator);
Assert.True(validator.IsValidOrigin(new List<string> { testHost }));
}

private sealed class TestHub : WebPubSubHub
{ }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Azure.Identity"/>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Moq" />
<PackageReference Include="NUnit" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ public void ConfigureServices(IServiceCollection services)
}
#endregion

#region Snippet:WebPubSubMapHubCustom
public void Configure(IApplicationBuilder app)
{
app.UseEndpoints(endpoint =>
{
endpoint.MapWebPubSubHub<SampleHub>("/eventhandler");
endpoint.MapWebPubSubHub<SampleHub>("/eventhandler", "customHub");
});
}
#endregion

private sealed class SampleHub : WebPubSubHub
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Authentication;
using System.Text;
using System.Text.Json;
using System.Threading;
Expand Down Expand Up @@ -311,6 +310,20 @@ public async Task TestWrongTypeReturns()
Assert.AreEqual("Invalid user", response);
}

[Test]
public async Task TestHubBaseReturnsWhenCustomHubName()
{
var hubName = "customHub";
_adaptor.RegisterHub<TestDefaultHub>(hubName);
var connectBody = "{\"claims\":{\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier\":[\"ddd\"],\"nbf\":[\"1629183374\"],\"exp\":[\"1629186974\"],\"iat\":[\"1629183374\"],\"aud\":[\"http://localhost:8080/client/hubs/chat\"],\"sub\":[\"ddd\"]},\"query\":{\"access_token\":[\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkZGQiLCJuYmYiOjE2MjkxODMzNzQsImV4cCI6MTYyOTE4Njk3NCwiaWF0IjoxNjI5MTgzMzc0LCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvY2xpZW50L2h1YnMvY2hhdCJ9.tqD8ykjv5NmYw6gzLKglUAv-c-AVWu-KNZOptRKkgMM\"]},\"subprotocols\":[\"protocol1\", \"protocol2\"],\"clientCertificates\":[],\"headers\":{}}";
var context = PrepareHttpContext(httpMethod: HttpMethods.Post, eventName: "connect", body: connectBody, hub: hubName);

await _adaptor.HandleRequest(context);

Assert.AreEqual(StatusCodes.Status200OK, context.Response.StatusCode);
Assert.Null(context.Response.ContentLength);
}

private static HttpContext PrepareHttpContext(
WebPubSubEventType type = WebPubSubEventType.System,
string eventName = "connect",
Expand Down