Added endpoint routing support (#7608)

- Basic endpoint routing support to for SignalR hubs, ConnectionHandler and IConnectionBuilder endpoints
- Updated all functional tests and samples to use it
- Added all attributes as metadata from Hubs and ConnectionHandlers
- Added a test to verify client is rejected if auth is ineffective
This commit is contained in:
David Fowler 2019-02-15 12:32:01 -08:00 committed by GitHub
parent 8daca5ec3a
commit 5ef51822de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 456 additions and 77 deletions

View File

@ -899,6 +899,33 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
}
}
[Theory]
[MemberData(nameof(TransportTypesWithAuth))]
public async Task ClientWillFailAuthEndPointIfNotAuthorized(HttpTransportType transportType, string hubPath)
{
bool ExpectedErrors(WriteContext writeContext)
{
return writeContext.Exception is HttpRequestException;
}
using (StartServer<Startup>(out var server, ExpectedErrors))
{
var hubConnection = new HubConnectionBuilder()
.WithLoggerFactory(LoggerFactory)
.WithUrl(server.Url + hubPath, transportType)
.Build();
try
{
var ex = await Assert.ThrowsAnyAsync<HttpRequestException>(() => hubConnection.StartAsync().OrTimeout());
Assert.Equal("Response status code does not indicate success: 401 (Unauthorized).", ex.Message);
}
finally
{
await hubConnection.DisposeAsync().OrTimeout();
}
}
}
[Theory]
[MemberData(nameof(TransportTypes))]
public async Task ClientCanUseJwtBearerTokenForAuthenticationWhenRedirected(HttpTransportType transportType)
@ -1172,6 +1199,17 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
}
}
public static IEnumerable<object[]> TransportTypesWithAuth()
{
foreach (var transport in TransportTypes().SelectMany(t => t).Cast<HttpTransportType>())
{
foreach (var path in new[] { "/authorizedhub", "/authorizedhub2" })
{
yield return new object[] { transport, path };
}
}
}
// This list excludes "special" hub paths like "default-nowebsockets" which exist for specific tests.
public static string[] HubPaths = new[] { "/default", "/dynamic", "/hubT" };

View File

@ -221,4 +221,10 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
{
public string Echo(string message) => TestHubMethodsImpl.Echo(message);
}
// Authorization is added via endpoint routing in Startup
public class HubWithAuthorization2 : Hub
{
public string Echo(string message) => TestHubMethodsImpl.Echo(message);
}
}

View File

@ -5,9 +5,11 @@ using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
@ -21,11 +23,9 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR(options =>
{
options.EnableDetailedErrors = true;
})
.AddMessagePackProtocol();
services.AddSignalR(options => options.EnableDetailedErrors = true)
.AddMessagePackProtocol();
services.AddSingleton<IUserIdProvider, HeaderUserIdProvider>();
services.AddAuthorization(options =>
{
@ -35,50 +35,53 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests
policy.RequireClaim(ClaimTypes.NameIdentifier);
});
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters =
new TokenValidationParameters
.AddJwtBearer(options =>
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
ValidateLifetime = true,
IssuerSigningKey = SecurityKey
};
});
options.TokenValidationParameters =
new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
ValidateLifetime = true,
IssuerSigningKey = SecurityKey
};
});
}
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseSignalR(routes =>
app.UseRouting(routes =>
{
routes.MapHub<TestHub>("/default");
routes.MapHub<DynamicTestHub>("/dynamic");
routes.MapHub<TestHubT>("/hubT");
routes.MapHub<HubWithAuthorization>("/authorizedhub");
routes.MapHub<TestHub>("/default-nowebsockets", options => options.Transports = HttpTransportType.LongPolling | HttpTransportType.ServerSentEvents);
});
routes.MapHub<HubWithAuthorization2>("/authorizedhub2")
.RequireAuthorization(new AuthorizeAttribute(JwtBearerDefaults.AuthenticationScheme));
app.Run(async (context) =>
{
if (context.Request.Path.StartsWithSegments("/generateJwtToken"))
routes.MapHub<TestHub>("/default-nowebsockets", options => options.Transports = HttpTransportType.LongPolling | HttpTransportType.ServerSentEvents);
routes.MapGet("/generateJwtToken", context =>
{
await context.Response.WriteAsync(GenerateJwtToken());
return;
}
else if (context.Request.Path.StartsWithSegments("/redirect"))
return context.Response.WriteAsync(GenerateJwtToken());
});
routes.Map("/redirect/{*anything}", context =>
{
await context.Response.WriteAsync(JsonConvert.SerializeObject(new
return context.Response.WriteAsync(JsonConvert.SerializeObject(new
{
url = $"{context.Request.Scheme}://{context.Request.Host}/authorizedHub",
accessToken = GenerateJwtToken()
}));
}
});
});
app.UseAuthorization();
}
private string GenerateJwtToken()

View File

@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.Tokens;
@ -122,39 +123,21 @@ namespace FunctionalTests
return next.Invoke();
});
app.UseConnections(routes =>
{
routes.MapConnectionHandler<EchoConnectionHandler>("/echo");
});
app.Use(async (context, next) =>
{
if (context.Request.Path.Value.Contains("/negotiate"))
{
context.Response.Cookies.Append("testCookie", "testValue");
context.Response.Cookies.Append("testCookie2", "testValue2");
context.Response.Cookies.Append("expiredCookie", "doesntmatter", new CookieOptions() { Expires = DateTimeOffset.Now.AddHours(-1) });
}
await next.Invoke();
});
app.UseSignalR(routes =>
app.UseRouting(routes =>
{
routes.MapHub<TestHub>("/testhub");
routes.MapHub<TestHub>("/testhub-nowebsockets", options => options.Transports = HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling);
routes.MapHub<UncreatableHub>("/uncreatable");
routes.MapHub<HubWithAuthorization>("/authorizedhub");
});
app.Use(next => async (context) =>
{
if (context.Request.Path.StartsWithSegments("/generateJwtToken"))
routes.MapConnectionHandler<EchoConnectionHandler>("/echo");
routes.MapGet("/generateJwtToken", context =>
{
await context.Response.WriteAsync(GenerateJwtToken());
return;
}
return context.Response.WriteAsync(GenerateJwtToken());
});
if (context.Request.Path.StartsWithSegments("/deployment"))
routes.MapGet("/deployment", context =>
{
var attributes = Assembly.GetAssembly(typeof(Startup)).GetCustomAttributes<AssemblyMetadataAttribute>();
@ -182,7 +165,20 @@ namespace FunctionalTests
json.WriteTo(writer);
}
return Task.CompletedTask;
});
});
app.Use(async (context, next) =>
{
if (context.Request.Path.Value.Contains("/negotiate"))
{
context.Response.Cookies.Append("testCookie", "testValue");
context.Response.Cookies.Append("testCookie2", "testValue2");
context.Response.Cookies.Append("expiredCookie", "doesntmatter", new CookieOptions() { Expires = DateTimeOffset.Now.AddHours(-1) });
}
await next.Invoke();
});
}

View File

@ -0,0 +1,151 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Http.Connections.Internal;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Routing
{
public static class ConnectionEndpointRouteBuilderExtensions
{
/// <summary>
/// Maps incoming requests with the specified path to the provided connection pipeline.
/// </summary>
/// <param name="builder">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="configure">A callback to configure the connection.</param>
/// <returns>An <see cref="IEndpointConventionBuilder"/> for endpoints associated with the connections.</returns>
public static IEndpointConventionBuilder MapConnections(this IEndpointRouteBuilder builder, string pattern, Action<IConnectionBuilder> configure) =>
builder.MapConnections(pattern, new HttpConnectionDispatcherOptions(), configure);
/// <summary>
/// Maps incoming requests with the specified path to the provided connection pipeline.
/// </summary>
/// <typeparam name="TConnectionHandler">The <see cref="ConnectionHandler"/> type.</typeparam>
/// <param name="builder">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <returns>An <see cref="IEndpointConventionBuilder"/> for endpoints associated with the connections.</returns>
public static IEndpointConventionBuilder MapConnectionHandler<TConnectionHandler>(this IEndpointRouteBuilder builder, string pattern) where TConnectionHandler : ConnectionHandler
{
return builder.MapConnectionHandler<TConnectionHandler>(pattern, configureOptions: null);
}
/// <summary>
/// Maps incoming requests with the specified path to the provided connection pipeline.
/// </summary>
/// <typeparam name="TConnectionHandler">The <see cref="ConnectionHandler"/> type.</typeparam>
/// <param name="builder">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="configureOptions">A callback to configure dispatcher options.</param>
/// <returns>An <see cref="IEndpointConventionBuilder"/> for endpoints associated with the connections.</returns>
public static IEndpointConventionBuilder MapConnectionHandler<TConnectionHandler>(this IEndpointRouteBuilder builder, string pattern, Action<HttpConnectionDispatcherOptions> configureOptions) where TConnectionHandler : ConnectionHandler
{
var options = new HttpConnectionDispatcherOptions();
// REVIEW: WE should consider removing this and instead just relying on the
// AuthorizationMiddleware
var attributes = typeof(TConnectionHandler).GetCustomAttributes(inherit: true);
foreach (var attribute in attributes.OfType<AuthorizeAttribute>())
{
options.AuthorizationData.Add(attribute);
}
configureOptions?.Invoke(options);
var conventionBuilder = builder.MapConnections(pattern, options, b =>
{
b.UseConnectionHandler<TConnectionHandler>();
});
conventionBuilder.Add(e =>
{
// Add all attributes on the ConnectionHandler has metadata (this will allow for things like)
// auth attributes and cors attributes to work seamlessly
foreach (var item in attributes)
{
e.Metadata.Add(item);
}
});
return conventionBuilder;
}
/// <summary>
/// Maps incoming requests with the specified path to the provided connection pipeline.
/// </summary>
/// <param name="builder">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="options">Options used to configure the connection.</param>
/// <param name="configure">A callback to configure the connection.</param>
/// <returns>An <see cref="IEndpointConventionBuilder"/> for endpoints associated with the connections.</returns>
public static IEndpointConventionBuilder MapConnections(this IEndpointRouteBuilder builder, string pattern, HttpConnectionDispatcherOptions options, Action<IConnectionBuilder> configure)
{
var dispatcher = builder.ServiceProvider.GetRequiredService<HttpConnectionDispatcher>();
var connectionBuilder = new ConnectionBuilder(builder.ServiceProvider);
configure(connectionBuilder);
var connectionDelegate = connectionBuilder.Build();
// REVIEW: Consider expanding the internals of the dispatcher as endpoint routes instead of
// using if statemants we can let the matcher handle
var conventionBuilders = new List<IEndpointConventionBuilder>();
// Build the negotiate application
var app = builder.CreateApplicationBuilder();
app.UseWebSockets();
app.Run(c => dispatcher.ExecuteNegotiateAsync(c, options));
var negotiateHandler = app.Build();
var negotiateBuilder = builder.Map(pattern + "/negotiate", negotiateHandler);
conventionBuilders.Add(negotiateBuilder);
// build the execute handler part of the protocol
app = builder.CreateApplicationBuilder();
app.UseWebSockets();
app.Run(c => dispatcher.ExecuteAsync(c, options, connectionDelegate));
var executehandler = app.Build();
var executeBuilder = builder.Map(pattern, executehandler);
conventionBuilders.Add(executeBuilder);
var compositeConventionBuilder = new CompositeEndpointConventionBuilder(conventionBuilders);
// Add metadata to all of Endpoints
compositeConventionBuilder.Add(e =>
{
// Add the authorization data as metadata
foreach (var data in options.AuthorizationData)
{
e.Metadata.Add(data);
}
});
return compositeConventionBuilder;
}
private class CompositeEndpointConventionBuilder : IEndpointConventionBuilder
{
private readonly List<IEndpointConventionBuilder> _endpointConventionBuilders;
public CompositeEndpointConventionBuilder(List<IEndpointConventionBuilder> endpointConventionBuilders)
{
_endpointConventionBuilders = endpointConventionBuilders;
}
public void Add(Action<EndpointBuilder> convention)
{
foreach (var endpointConventionBuilder in _endpointConventionBuilders)
{
endpointConventionBuilder.Add(convention);
}
}
}
}
}

View File

@ -66,11 +66,15 @@ namespace JwtSample
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseFileServer();
app.UseSignalR(options => options.MapHub<Broadcaster>("/broadcast"));
var routeBuilder = new RouteBuilder(app);
routeBuilder.MapGet("generatetoken", c => c.Response.WriteAsync(GenerateToken(c)));
app.UseRouter(routeBuilder.Build());
app.UseRouting(routes =>
{
routes.MapHub<Broadcaster>("/broadcast");
routes.MapGet("/generatetoken", context =>
{
return context.Response.WriteAsync(GenerateToken(context));
});
});
}
private string GenerateToken(HttpContext httpContext)

View File

@ -4,9 +4,11 @@
using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@ -55,23 +57,17 @@ namespace SignalRSamples
app.UseCors("Everything");
app.UseSignalR(routes =>
app.UseRouting(routes =>
{
routes.MapHub<DynamicChat>("/dynamic");
routes.MapHub<Chat>("/default");
routes.MapHub<Streaming>("/streaming");
routes.MapHub<UploadHub>("/uploading");
routes.MapHub<HubTChat>("/hubT");
});
app.UseConnections(routes =>
{
routes.MapConnectionHandler<MessagesConnectionHandler>("/chat");
});
app.Use(next => (context) =>
{
if (context.Request.Path.StartsWithSegments("/deployment"))
routes.MapGet("/deployment", context =>
{
var attributes = Assembly.GetAssembly(typeof(Startup)).GetCustomAttributes<AssemblyMetadataAttribute>();
@ -99,9 +95,12 @@ namespace SignalRSamples
json.WriteTo(writer);
}
}
return Task.CompletedTask;
return Task.CompletedTask;
});
});
app.UseAuthorization();
}
}
}

View File

@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using SocialWeather.Json;
@ -30,9 +31,13 @@ namespace SocialWeather
app.UseDeveloperExceptionPage();
}
app.UseConnections(o => o.MapConnectionHandler<SocialWeatherConnectionHandler>("/weather"));
app.UseFileServer();
app.UseRouting(routes =>
{
routes.MapConnectionHandler<SocialWeatherConnectionHandler>("/weather");
});
var formatterResolver = app.ApplicationServices.GetRequiredService<FormatterResolver>();
formatterResolver.AddFormatter<WeatherReport, JsonStreamFormatter<WeatherReport>>("json");
formatterResolver.AddFormatter<WeatherReport, ProtobufWeatherStreamFormatter>("protobuf");

View File

@ -0,0 +1,75 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Routing
{
public static class HubEndpointRouteBuilderExtensions
{
/// <summary>
/// Maps incoming requests with the specified path to the specified <see cref="Hub"/> type.
/// </summary>
/// <typeparam name="THub">The <see cref="Hub"/> type to map requests to.</typeparam>
/// <param name="builder">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <returns>An <see cref="IEndpointConventionBuilder"/> for endpoints associated with the connections.</returns>
public static IEndpointConventionBuilder MapHub<THub>(this IEndpointRouteBuilder builder, string pattern) where THub : Hub
{
return builder.MapHub<THub>(pattern, configureOptions: null);
}
/// <summary>
/// Maps incoming requests with the specified path to the specified <see cref="Hub"/> type.
/// </summary>
/// <typeparam name="THub">The <see cref="Hub"/> type to map requests to.</typeparam>
/// <param name="builder">The <see cref="IEndpointRouteBuilder"/> to add the route to.</param>
/// <param name="pattern">The route pattern.</param>
/// <param name="configureOptions">A callback to configure dispatcher options.</param>
/// <returns>An <see cref="IEndpointConventionBuilder"/> for endpoints associated with the connections.</returns>
public static IEndpointConventionBuilder MapHub<THub>(this IEndpointRouteBuilder builder, string pattern, Action<HttpConnectionDispatcherOptions> configureOptions) where THub : Hub
{
var marker = builder.ServiceProvider.GetService<SignalRMarkerService>();
if (marker == null)
{
throw new InvalidOperationException("Unable to find the required services. Please add all the required services by calling " +
"'IServiceCollection.AddSignalR' inside the call to 'ConfigureServices(...)' in the application startup code.");
}
var options = new HttpConnectionDispatcherOptions();
// REVIEW: WE should consider removing this and instead just relying on the
// AuthorizationMiddleware
var attributes = typeof(THub).GetCustomAttributes(inherit: true);
foreach (var attribute in attributes.OfType<AuthorizeAttribute>())
{
options.AuthorizationData.Add(attribute);
}
configureOptions?.Invoke(options);
var conventionBuilder = builder.MapConnections(pattern, options, b =>
{
b.UseHub<THub>();
});
conventionBuilder.Add(e =>
{
// Add all attributes on the Hub has metadata (this will allow for things like)
// auth attributes and cors attributes to work seamlessly
foreach (var item in attributes)
{
e.Metadata.Add(item);
}
});
return conventionBuilder;
}
}
}

View File

@ -1,8 +1,11 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
@ -36,7 +39,8 @@ namespace Microsoft.AspNetCore.SignalR.Tests
{
executedConfigure = true;
var ex = Assert.Throws<InvalidOperationException>(() => {
var ex = Assert.Throws<InvalidOperationException>(() =>
{
app.UseSignalR(routes =>
{
routes.MapHub<AuthHub>("/overloads");
@ -56,6 +60,43 @@ namespace Microsoft.AspNetCore.SignalR.Tests
Assert.True(executedConfigure);
}
[Fact]
public void NotAddingSignalRServiceThrowsWhenUsingEndpointRouting()
{
var executedConfigure = false;
var builder = new WebHostBuilder();
builder
.UseKestrel()
.ConfigureServices(services =>
{
services.AddRouting();
})
.Configure(app =>
{
executedConfigure = true;
var ex = Assert.Throws<InvalidOperationException>(() =>
{
app.UseRouting(routes =>
{
routes.MapHub<AuthHub>("/overloads");
});
});
Assert.Equal("Unable to find the required services. Please add all the required services by calling " +
"'IServiceCollection.AddSignalR' inside the call to 'ConfigureServices(...)' in the application startup code.", ex.Message);
})
.UseUrls("http://127.0.0.1:0");
using (var host = builder.Build())
{
host.Start();
}
Assert.True(executedConfigure);
}
[Fact]
public void MapHubFindsAuthAttributeOnHub()
{
@ -101,6 +142,49 @@ namespace Microsoft.AspNetCore.SignalR.Tests
Assert.Equal(2, authCount);
}
[Fact]
public void MapHubEndPointRoutingFindsAttributesOnHub()
{
var authCount = 0;
using (var host = BuildWebHostWithEndPointRouting(routes => routes.MapHub<AuthHub>("/path", options =>
{
authCount += options.AuthorizationData.Count;
})))
{
host.Start();
var dataSource = host.Services.GetRequiredService<EndpointDataSource>();
// We register 2 endpoints (/negotiate and /)
Assert.Equal(2, dataSource.Endpoints.Count);
Assert.NotNull(dataSource.Endpoints[0].Metadata.GetMetadata<IAuthorizeData>());
Assert.NotNull(dataSource.Endpoints[1].Metadata.GetMetadata<IAuthorizeData>());
}
Assert.Equal(1, authCount);
}
[Fact]
public void MapHubEndPointRoutingAppliesAttributesBeforeConventions()
{
void ConfigureRoutes(IEndpointRouteBuilder routes)
{
// This "Foo" policy should override the default auth attribute
routes.MapHub<AuthHub>("/path")
.RequireAuthorization(new AuthorizeAttribute("Foo"));
}
using (var host = BuildWebHostWithEndPointRouting(ConfigureRoutes))
{
host.Start();
var dataSource = host.Services.GetRequiredService<EndpointDataSource>();
// We register 2 endpoints (/negotiate and /)
Assert.Equal(2, dataSource.Endpoints.Count);
Assert.Equal("Foo", dataSource.Endpoints[0].Metadata.GetMetadata<IAuthorizeData>()?.Policy);
Assert.Equal("Foo", dataSource.Endpoints[1].Metadata.GetMetadata<IAuthorizeData>()?.Policy);
}
}
private class InvalidHub : Hub
{
public void OverloadedMethod(int num)
@ -126,6 +210,22 @@ namespace Microsoft.AspNetCore.SignalR.Tests
{
}
private IWebHost BuildWebHostWithEndPointRouting(Action<IEndpointRouteBuilder> configure)
{
return new WebHostBuilder()
.UseKestrel()
.ConfigureServices(services =>
{
services.AddSignalR();
})
.Configure(app =>
{
app.UseRouting(routes => configure(routes));
})
.UseUrls("http://127.0.0.1:0")
.Build();
}
private IWebHost BuildWebHost(Action<HubRouteBuilder> configure)
{
return new WebHostBuilder()

View File

@ -5,6 +5,7 @@ using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.SignalR.Tests
@ -28,18 +29,15 @@ namespace Microsoft.AspNetCore.SignalR.Tests
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseConnections(routes =>
app.UseRouting(routes =>
{
routes.MapHub<UncreatableHub>("/uncreatable");
routes.MapConnectionHandler<EchoConnectionHandler>("/echo");
routes.MapConnectionHandler<WriteThenCloseConnectionHandler>("/echoAndClose");
routes.MapConnectionHandler<HttpHeaderConnectionHandler>("/httpheader");
routes.MapConnectionHandler<AuthConnectionHandler>("/auth");
});
app.UseSignalR(routes =>
{
routes.MapHub<UncreatableHub>("/uncreatable");
});
}
}
}

View File

@ -4,6 +4,7 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
@ -28,7 +29,10 @@ namespace Microsoft.AspNetCore.SignalR.StackExchangeRedis.Tests
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSignalR(options => options.MapHub<EchoHub>("/echo"));
app.UseRouting(routes =>
{
routes.MapHub<EchoHub>("/echo");
});
}
private class UserNameIdProvider : IUserIdProvider