Relayer implementation
This refactor introduces two major changes 1. Now creating the 'handler' delegate happens inside the endpoint middleware. This allows you to short circuit even harder, AND to create endpoint funcs that capture and use 'next' to rejoin the middleware pipeline. 2. Relayered the implementation to have routing plug into the dispatcher. It wasn't immediately apparent to me that this was the right thing to do, but I think we will need to do things this way to deliver the kind of back-compat experience we need to do. The idea that I have is that 'attribute routing' will be the 'default' entry in the dispatcher. Adding additional conventional routes or other IRouter-based extensibility will be possible through adapters - but the default experience will be to add items to the 'attribute route'. So. We will need to port the attribute routing infrastructure to the dispatcher library. We may also need to make RVD into a subclass of something in the dispatcher assembly.
This commit is contained in:
parent
b01072eb47
commit
134096d9cb
|
|
@ -1,17 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Dispatcher;
|
||||
|
||||
namespace DispatcherSample
|
||||
{
|
||||
public class DispatcherEndpoint : Endpoint
|
||||
{
|
||||
public DispatcherEndpoint(string displayName)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
}
|
||||
|
||||
public override string DisplayName { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -6,9 +6,6 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Dispatcher\Microsoft.AspNetCore.Dispatcher.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Dispatcher.Abstractions\Microsoft.AspNetCore.Dispatcher.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Routing.Abstractions\Microsoft.AspNetCore.Routing.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,73 +1,70 @@
|
|||
// 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.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.AspNetCore.Routing.Dispatcher;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace DispatcherSample
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
private readonly static IInlineConstraintResolver ConstraintResolver = new DefaultInlineConstraintResolver(
|
||||
new OptionsManager<RouteOptions>(
|
||||
new OptionsFactory<RouteOptions>(
|
||||
Enumerable.Empty<IConfigureOptions<RouteOptions>>(),
|
||||
Enumerable.Empty<IPostConfigureOptions<RouteOptions>>())));
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.Configure<DispatcherOptions>(options =>
|
||||
{
|
||||
options.DispatcherEntryList = new List<DispatcherEntry>()
|
||||
{
|
||||
new DispatcherEntry
|
||||
{
|
||||
RouteTemplate = TemplateParser.Parse("{Endpoint=example}"),
|
||||
Endpoints = new []
|
||||
options.Dispatchers.Add(CreateDispatcher(
|
||||
"{Endpoint=example}",
|
||||
new RouteValuesEndpoint(
|
||||
new RouteValueDictionary(new { Endpoint = "First" }),
|
||||
async (context) =>
|
||||
{
|
||||
new RouteValuesEndpoint("example")
|
||||
{
|
||||
RequiredValues = new RouteValueDictionary(new { Endpoint = "First" }),
|
||||
RequestDelegate = async (context) =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello from the example!");
|
||||
}
|
||||
},
|
||||
new RouteValuesEndpoint("example2")
|
||||
{
|
||||
RequiredValues = new RouteValueDictionary(new { Endpoint = "Second" }),
|
||||
RequestDelegate = async (context) =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello from the second example!");
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
await context.Response.WriteAsync("Hello from the example!");
|
||||
},
|
||||
Array.Empty<object>(),
|
||||
"example"),
|
||||
new RouteValuesEndpoint(
|
||||
new RouteValueDictionary(new { Endpoint = "Second" }),
|
||||
async (context) =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello from the second example!");
|
||||
},
|
||||
Array.Empty<object>(),
|
||||
"example2")));
|
||||
|
||||
new DispatcherEntry
|
||||
{
|
||||
RouteTemplate = TemplateParser.Parse("{Endpoint=example}/{Parameter=foo}"),
|
||||
Endpoints = new []
|
||||
options.Dispatchers.Add(CreateDispatcher(
|
||||
"{Endpoint=example}/{Parameter=foo}",
|
||||
new RouteValuesEndpoint(
|
||||
new RouteValueDictionary(new { Endpoint = "First", Parameter = "param1" }),
|
||||
async (context) =>
|
||||
{
|
||||
new RouteValuesEndpoint("example")
|
||||
{
|
||||
RequiredValues = new RouteValueDictionary(new { Endpoint = "First", Parameter = "param1"}),
|
||||
RequestDelegate = async (context) =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello from the example for foo!");
|
||||
}
|
||||
},
|
||||
new RouteValuesEndpoint("example2")
|
||||
{
|
||||
RequiredValues = new RouteValueDictionary(new { Endpoint = "Second", Parameter = "param2"}),
|
||||
RequestDelegate = async (context) =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello from the second example for foo!");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
await context.Response.WriteAsync("Hello from the example for foo!");
|
||||
},
|
||||
Array.Empty<object>(),
|
||||
"example"),
|
||||
new RouteValuesEndpoint(
|
||||
new RouteValueDictionary(new { Endpoint = "Second", Parameter = "param2" }),
|
||||
async (context) =>
|
||||
{
|
||||
await context.Response.WriteAsync("Hello from the second example for foo!");
|
||||
},
|
||||
Array.Empty<object>(),
|
||||
"example2")));
|
||||
|
||||
options.HandlerFactories.Add((endpoint) => (endpoint as RouteValuesEndpoint)?.HandlerFactory);
|
||||
});
|
||||
|
||||
services.AddSingleton<UrlGenerator>();
|
||||
|
|
@ -99,5 +96,11 @@ namespace DispatcherSample
|
|||
await next.Invoke();
|
||||
});
|
||||
}
|
||||
|
||||
private static RequestDelegate CreateDispatcher(string routeTemplate, RouteValuesEndpoint endpoint, params RouteValuesEndpoint[] endpoints)
|
||||
{
|
||||
var dispatcher = new RouterDispatcher(new Route(new RouterEndpointSelector(new[] { endpoint }.Concat(endpoints)), routeTemplate, ConstraintResolver));
|
||||
return dispatcher.InvokeAsync;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
// 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.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public abstract class Endpoint
|
||||
{
|
||||
public abstract string DisplayName { get; }
|
||||
|
||||
public abstract IReadOnlyList<object> Metadata { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
// 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 Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// A delegate which attempts to create a <see cref="Func{RequestDelegate, RequestDelegate}"/> for the selected <see cref="Endpoint"/>.
|
||||
/// </summary>
|
||||
/// <param name="endpoint">The <see cref="Endpoint"/> selected by the dispatcher.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Func{RequestDelegate, RequestDelegate}"/> that invokes the operation represented by the <see cref="Endpoint"/>, or <c>null</c>.
|
||||
/// </returns>
|
||||
public delegate Func<RequestDelegate, RequestDelegate> EndpointHandlerFactory(Endpoint endpoint);
|
||||
}
|
||||
|
|
@ -7,8 +7,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
{
|
||||
public interface IDispatcherFeature
|
||||
{
|
||||
Endpoint Endpoint { get; }
|
||||
Endpoint Endpoint { get; set; }
|
||||
|
||||
RequestDelegate RequestDelegate { get; }
|
||||
RequestDelegate RequestDelegate { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class DispatcherEndpoint : Endpoint
|
||||
public abstract class DispatcherBase
|
||||
{
|
||||
public DispatcherEndpoint(string displayName)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
}
|
||||
|
||||
public override string DisplayName { get; }
|
||||
public abstract Task InvokeAsync(HttpContext httpContext);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
// 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.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class DispatcherEntry
|
||||
{
|
||||
public IList<RouteValuesEndpoint> Endpoints { get; set; }
|
||||
|
||||
public RouteTemplate RouteTemplate { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,6 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
|
|
@ -17,82 +15,35 @@ namespace Microsoft.AspNetCore.Dispatcher
|
|||
|
||||
public DispatcherMiddleware(IOptions<DispatcherOptions> options, RequestDelegate next)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (next == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
|
||||
_options = options.Value;
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext httpContext)
|
||||
{
|
||||
foreach (var entry in _options.DispatcherEntryList)
|
||||
var feature = new DispatcherFeature();
|
||||
httpContext.Features.Set<IDispatcherFeature>(feature);
|
||||
|
||||
foreach (var entry in _options.Dispatchers)
|
||||
{
|
||||
var parsedTemplate = entry.RouteTemplate;
|
||||
var defaults = GetDefaults(parsedTemplate);
|
||||
var templateMatcher = new TemplateMatcher(parsedTemplate, defaults);
|
||||
var values = new RouteValueDictionary();
|
||||
|
||||
foreach (var endpoint in entry.Endpoints)
|
||||
await entry(httpContext);
|
||||
if (feature.Endpoint != null || feature.RequestDelegate != null)
|
||||
{
|
||||
if (templateMatcher.TryMatch(httpContext.Request.Path, values))
|
||||
{
|
||||
if (!CompareRouteValues(values, endpoint.RequiredValues))
|
||||
{
|
||||
values.Clear();
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
var dispatcherFeature = new DispatcherFeature
|
||||
{
|
||||
Endpoint = endpoint,
|
||||
RequestDelegate = endpoint.RequestDelegate
|
||||
};
|
||||
|
||||
httpContext.Features.Set<IDispatcherFeature>(dispatcherFeature);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await _next(httpContext);
|
||||
}
|
||||
|
||||
private RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate)
|
||||
{
|
||||
var result = new RouteValueDictionary();
|
||||
|
||||
foreach (var parameter in parsedTemplate.Parameters)
|
||||
{
|
||||
if (parameter.DefaultValue != null)
|
||||
{
|
||||
result.Add(parameter.Name, parameter.DefaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool CompareRouteValues(RouteValueDictionary values, RouteValueDictionary requiredValues)
|
||||
{
|
||||
foreach (var kvp in requiredValues)
|
||||
{
|
||||
if (string.IsNullOrEmpty(kvp.Value.ToString()))
|
||||
{
|
||||
if (values.TryGetValue(kvp.Key, out var routeValue) && !string.IsNullOrEmpty(routeValue.ToString()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!values.TryGetValue(kvp.Key, out var routeValue) || !string.Equals(kvp.Value.ToString(), routeValue.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
// 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 Microsoft.AspNetCore.Http;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class DispatcherOptions
|
||||
{
|
||||
public IList<DispatcherEntry> DispatcherEntryList { get; set; }
|
||||
public IList<RequestDelegate> Dispatchers { get; } = new List<RequestDelegate>();
|
||||
|
||||
public IList<EndpointHandlerFactory> HandlerFactories { get; } = new List<EndpointHandlerFactory>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,56 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class EndpointMiddleware
|
||||
{
|
||||
private readonly DispatcherOptions _options;
|
||||
private RequestDelegate _next;
|
||||
|
||||
public EndpointMiddleware(RequestDelegate next)
|
||||
public EndpointMiddleware(IOptions<DispatcherOptions> options, RequestDelegate next)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
if (next == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
|
||||
_options = options.Value;
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
var feature = context.Features.Get<IDispatcherFeature>();
|
||||
if (feature.RequestDelegate == null)
|
||||
if (feature.Endpoint != null && feature.RequestDelegate == null)
|
||||
{
|
||||
await _next(context);
|
||||
for (var i = 0; i < _options.HandlerFactories.Count; i++)
|
||||
{
|
||||
var handler = _options.HandlerFactories[i](feature.Endpoint);
|
||||
if (handler != null)
|
||||
{
|
||||
feature.RequestDelegate = handler(_next);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if (feature.RequestDelegate != null)
|
||||
{
|
||||
await feature.RequestDelegate(context);
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Dispatcher.Abstractions\Microsoft.AspNetCore.Dispatcher.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
// 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 Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class RouteValuesEndpoint : Endpoint
|
||||
{
|
||||
public RouteValuesEndpoint(string displayName)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
}
|
||||
|
||||
public override string DisplayName { get; }
|
||||
|
||||
public RequestDelegate RequestDelegate { get; set; }
|
||||
|
||||
public RouteValueDictionary RequiredValues { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
// 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.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Dispatcher
|
||||
{
|
||||
public class SimpleEndpoint : Endpoint
|
||||
{
|
||||
public SimpleEndpoint(RequestDelegate requestDelegate)
|
||||
: this(requestDelegate, Array.Empty<object>(), null)
|
||||
{
|
||||
}
|
||||
|
||||
public SimpleEndpoint(Func<RequestDelegate, RequestDelegate> delegateFactory)
|
||||
: this(delegateFactory, Array.Empty<object>(), null)
|
||||
{
|
||||
}
|
||||
|
||||
public SimpleEndpoint(RequestDelegate requestDelegate, IEnumerable<object> metadata)
|
||||
: this(requestDelegate, metadata, null)
|
||||
{
|
||||
}
|
||||
|
||||
public SimpleEndpoint(Func<RequestDelegate, RequestDelegate> delegateFactory, IEnumerable<object> metadata)
|
||||
: this(delegateFactory, metadata, null)
|
||||
{
|
||||
}
|
||||
|
||||
public SimpleEndpoint(RequestDelegate requestDelegate, IEnumerable<object> metadata, string displayName)
|
||||
{
|
||||
if (metadata == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(metadata));
|
||||
}
|
||||
|
||||
if (requestDelegate == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(requestDelegate));
|
||||
}
|
||||
|
||||
DisplayName = displayName;
|
||||
Metadata = metadata.ToArray();
|
||||
DelegateFactory = (next) => requestDelegate;
|
||||
}
|
||||
|
||||
public SimpleEndpoint(Func<RequestDelegate, RequestDelegate> delegateFactory, IEnumerable<object> metadata, string displayName)
|
||||
{
|
||||
if (metadata == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(metadata));
|
||||
}
|
||||
|
||||
if (delegateFactory == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(delegateFactory));
|
||||
}
|
||||
|
||||
DisplayName = displayName;
|
||||
Metadata = metadata.ToArray();
|
||||
DelegateFactory = delegateFactory;
|
||||
}
|
||||
|
||||
public override string DisplayName { get; }
|
||||
|
||||
public override IReadOnlyList<object> Metadata { get; }
|
||||
|
||||
public Func<RequestDelegate, RequestDelegate> DelegateFactory { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
// 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.Dispatcher;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Dispatcher
|
||||
{
|
||||
public class RouteValuesEndpoint : Endpoint
|
||||
{
|
||||
public RouteValuesEndpoint(RouteValueDictionary requiredValues, RequestDelegate requestDelegate)
|
||||
: this(requiredValues, requestDelegate, Array.Empty<object>(), null)
|
||||
{
|
||||
}
|
||||
|
||||
public RouteValuesEndpoint(RouteValueDictionary requiredValues, RequestDelegate requestDelegate, IEnumerable<object> metadata)
|
||||
: this(requiredValues, requestDelegate, metadata, null)
|
||||
{
|
||||
}
|
||||
|
||||
public RouteValuesEndpoint(
|
||||
RouteValueDictionary requiredValues,
|
||||
RequestDelegate requestDelegate,
|
||||
IEnumerable<object> metadata,
|
||||
string displayName)
|
||||
{
|
||||
if (requiredValues == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(requiredValues));
|
||||
}
|
||||
|
||||
if (requestDelegate == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(requestDelegate));
|
||||
}
|
||||
|
||||
if (metadata == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(metadata));
|
||||
}
|
||||
|
||||
RequiredValues = requiredValues;
|
||||
HandlerFactory = (next) => requestDelegate;
|
||||
Metadata = metadata.ToArray();
|
||||
DisplayName = displayName;
|
||||
}
|
||||
|
||||
public override string DisplayName { get; }
|
||||
|
||||
public override IReadOnlyList<object> Metadata { get; }
|
||||
|
||||
public Func<RequestDelegate, RequestDelegate> HandlerFactory { get; set; }
|
||||
|
||||
public RouteValueDictionary RequiredValues { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Dispatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// An adapter to plug an <see cref="IRouter"/> into a dispatcher.
|
||||
/// </summary>
|
||||
public class RouterDispatcher : DispatcherBase
|
||||
{
|
||||
private readonly IRouter _router;
|
||||
|
||||
public RouterDispatcher(IRouter router)
|
||||
{
|
||||
if (router == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(router));
|
||||
}
|
||||
|
||||
_router = router;
|
||||
}
|
||||
|
||||
public async override Task InvokeAsync(HttpContext httpContext)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
var routeContext = new RouteContext(httpContext);
|
||||
await _router.RouteAsync(routeContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
// 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 System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Dispatcher;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Dispatcher
|
||||
{
|
||||
public class RouterEndpointSelector : IRouter, IRouteHandler
|
||||
{
|
||||
private readonly RouteValuesEndpoint[] _endpoints;
|
||||
|
||||
public RouterEndpointSelector(IEnumerable<RouteValuesEndpoint> endpoints)
|
||||
{
|
||||
if (endpoints == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(endpoints));
|
||||
}
|
||||
|
||||
_endpoints = endpoints.ToArray();
|
||||
}
|
||||
|
||||
public RequestDelegate GetRequestHandler(HttpContext httpContext, RouteData routeData)
|
||||
{
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
if (routeData == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeData));
|
||||
}
|
||||
|
||||
var dispatcherFeature = httpContext.Features.Get<IDispatcherFeature>();
|
||||
if (dispatcherFeature == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatDispatcherFeatureIsRequired(
|
||||
nameof(HttpContext),
|
||||
nameof(IDispatcherFeature),
|
||||
nameof(RouterEndpointSelector)));
|
||||
}
|
||||
|
||||
for (var i = 0; i < _endpoints.Length; i++)
|
||||
{
|
||||
var endpoint = _endpoints[i];
|
||||
if (CompareRouteValues(routeData.Values, endpoint.RequiredValues))
|
||||
{
|
||||
dispatcherFeature.Endpoint = endpoint;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public VirtualPathData GetVirtualPath(VirtualPathContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task RouteAsync(RouteContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
var handler = GetRequestHandler(context.HttpContext, context.RouteData);
|
||||
if (handler != null)
|
||||
{
|
||||
context.Handler = handler;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private bool CompareRouteValues(RouteValueDictionary values, RouteValueDictionary requiredValues)
|
||||
{
|
||||
foreach (var kvp in requiredValues)
|
||||
{
|
||||
if (string.IsNullOrEmpty(kvp.Value.ToString()))
|
||||
{
|
||||
if (values.TryGetValue(kvp.Key, out var routeValue) && !string.IsNullOrEmpty(routeValue.ToString()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!values.TryGetValue(kvp.Key, out var routeValue) || !string.Equals(kvp.Value.ToString(), routeValue.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@ Microsoft.AspNetCore.Routing.RouteCollection</Description>
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Routing.Abstractions\Microsoft.AspNetCore.Routing.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Dispatcher.Abstractions\Microsoft.AspNetCore.Dispatcher.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Dispatcher\Microsoft.AspNetCore.Dispatcher.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -402,6 +402,20 @@ namespace Microsoft.AspNetCore.Routing
|
|||
internal static string FormatTemplateRoute_Exception(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_Exception"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' has no '{1}'. '{2}' requires a dispatcher.
|
||||
/// </summary>
|
||||
internal static string DispatcherFeatureIsRequired
|
||||
{
|
||||
get => GetString("DispatcherFeatureIsRequired");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' has no '{1}'. '{2}' requires a dispatcher.
|
||||
/// </summary>
|
||||
internal static string FormatDispatcherFeatureIsRequired(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("DispatcherFeatureIsRequired"), p0, p1, p2);
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
|
|
@ -26,36 +26,36 @@
|
|||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
|
|
@ -201,4 +201,7 @@
|
|||
<data name="TemplateRoute_Exception" xml:space="preserve">
|
||||
<value>An error occurred while creating the route with name '{0}' and template '{1}'.</value>
|
||||
</data>
|
||||
<data name="DispatcherFeatureIsRequired" xml:space="preserve">
|
||||
<value>The '{0}' has no '{1}'. '{2}' requires a dispatcher.</value>
|
||||
</data>
|
||||
</root>
|
||||
Loading…
Reference in New Issue