diff --git a/samples/DispatcherSample/RouteValueAddress.cs b/samples/DispatcherSample/RouteValueAddress.cs index 5f2923c447..9bb8a59b42 100644 --- a/samples/DispatcherSample/RouteValueAddress.cs +++ b/samples/DispatcherSample/RouteValueAddress.cs @@ -1,5 +1,4 @@ -using System; -// Copyright (c) .NET Foundation. All rights reserved. +// 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; diff --git a/samples/DispatcherSample/Startup.cs b/samples/DispatcherSample/Startup.cs index de78737032..bd1cba2d01 100644 --- a/samples/DispatcherSample/Startup.cs +++ b/samples/DispatcherSample/Startup.cs @@ -1,10 +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.Collections.Generic; 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.Extensions.DependencyInjection; namespace DispatcherSample @@ -13,6 +16,60 @@ namespace DispatcherSample { public void ConfigureServices(IServiceCollection services) { + services.Configure(options => + { + options.DispatcherEntryList = new List() + { + new DispatcherEntry + { + RouteTemplate = TemplateParser.Parse("{Endpoint=example}"), + Endpoints = new [] + { + 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!"); + } + }, + } + }, + + new DispatcherEntry + { + RouteTemplate = TemplateParser.Parse("{Endpoint=example}/{Parameter=foo}"), + Endpoints = new [] + { + 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!"); + } + }, + } + } + }; + }); + services.AddSingleton(); services.AddSingleton(); services.AddDispatcher(); diff --git a/src/Microsoft.AspNetCore.Dispatcher/DispatcherEntry.cs b/src/Microsoft.AspNetCore.Dispatcher/DispatcherEntry.cs new file mode 100644 index 0000000000..e1709db348 --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher/DispatcherEntry.cs @@ -0,0 +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 System.Collections.Generic; +using Microsoft.AspNetCore.Routing.Template; + +namespace Microsoft.AspNetCore.Dispatcher +{ + public class DispatcherEntry + { + public IList Endpoints { get; set; } + + public RouteTemplate RouteTemplate { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.Dispatcher/DispatcherMiddleware.cs b/src/Microsoft.AspNetCore.Dispatcher/DispatcherMiddleware.cs index fd39f52633..274ad424f9 100644 --- a/src/Microsoft.AspNetCore.Dispatcher/DispatcherMiddleware.cs +++ b/src/Microsoft.AspNetCore.Dispatcher/DispatcherMiddleware.cs @@ -1,60 +1,98 @@ // 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.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Template; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Dispatcher { public class DispatcherMiddleware { + private readonly DispatcherOptions _options; private readonly RequestDelegate _next; - public DispatcherMiddleware(RequestDelegate next) + public DispatcherMiddleware(IOptions options, RequestDelegate next) { + _options = options.Value; _next = next; } public async Task Invoke(HttpContext httpContext) { - var dictionary = new Dictionary + foreach (var entry in _options.DispatcherEntryList) { - { - "/example", - new DispatcherFeature - { - Endpoint = new DispatcherEndpoint("example"), - RequestDelegate = async (context) => - { - await context.Response.WriteAsync("Hello from the example!"); - } - } - }, - { - "/example2", - new DispatcherFeature - { - Endpoint = new DispatcherEndpoint("example2"), - RequestDelegate = async (context) => - { - await context.Response.WriteAsync("Hello from the second example!"); - } - } - }, - }; + var parsedTemplate = entry.RouteTemplate; + var defaults = GetDefaults(parsedTemplate); + var templateMatcher = new TemplateMatcher(parsedTemplate, defaults); + var values = new RouteValueDictionary(); - if (dictionary.TryGetValue(httpContext.Request.Path, out var value)) - { - var dispatcherFeature = new DispatcherFeature + foreach (var endpoint in entry.Endpoints) { - Endpoint = value.Endpoint, - RequestDelegate = value.RequestDelegate - }; + if (templateMatcher.TryMatch(httpContext.Request.Path, values)) + { + if (!CompareRouteValues(values, endpoint.RequiredValues)) + { + values.Clear(); + } - httpContext.Features.Set(dispatcherFeature); - await _next(httpContext); + else + { + var dispatcherFeature = new DispatcherFeature + { + Endpoint = endpoint, + RequestDelegate = endpoint.RequestDelegate + }; + + httpContext.Features.Set(dispatcherFeature); + 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; } } } diff --git a/src/Microsoft.AspNetCore.Dispatcher/DispatcherOptions.cs b/src/Microsoft.AspNetCore.Dispatcher/DispatcherOptions.cs new file mode 100644 index 0000000000..ac966dc0b5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher/DispatcherOptions.cs @@ -0,0 +1,12 @@ +// 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 class DispatcherOptions + { + public IList DispatcherEntryList { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.Dispatcher/Microsoft.AspNetCore.Dispatcher.csproj b/src/Microsoft.AspNetCore.Dispatcher/Microsoft.AspNetCore.Dispatcher.csproj index 3ef72ce7a1..ade389a92d 100644 --- a/src/Microsoft.AspNetCore.Dispatcher/Microsoft.AspNetCore.Dispatcher.csproj +++ b/src/Microsoft.AspNetCore.Dispatcher/Microsoft.AspNetCore.Dispatcher.csproj @@ -8,7 +8,8 @@ - + + diff --git a/src/Microsoft.AspNetCore.Dispatcher/RouteValuesEndpoint.cs b/src/Microsoft.AspNetCore.Dispatcher/RouteValuesEndpoint.cs new file mode 100644 index 0000000000..457733d655 --- /dev/null +++ b/src/Microsoft.AspNetCore.Dispatcher/RouteValuesEndpoint.cs @@ -0,0 +1,22 @@ +// 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; } + } +}