Use RouteTemplate and TemplateMatcher (#443)

Addresses #438
This commit is contained in:
Jass Bagga 2017-09-14 15:24:40 -07:00 committed by GitHub
parent a9b47f6722
commit b01072eb47
7 changed files with 181 additions and 37 deletions

View File

@ -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;

View File

@ -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<DispatcherOptions>(options =>
{
options.DispatcherEntryList = new List<DispatcherEntry>()
{
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<UrlGenerator>();
services.AddSingleton<RouteValueAddressTable>();
services.AddDispatcher();

View File

@ -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<RouteValuesEndpoint> Endpoints { get; set; }
public RouteTemplate RouteTemplate { get; set; }
}
}

View File

@ -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<DispatcherOptions> options, RequestDelegate next)
{
_options = options.Value;
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
var dictionary = new Dictionary<string, DispatcherFeature>
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<IDispatcherFeature>(dispatcherFeature);
await _next(httpContext);
else
{
var dispatcherFeature = new DispatcherFeature
{
Endpoint = endpoint,
RequestDelegate = endpoint.RequestDelegate
};
httpContext.Features.Set<IDispatcherFeature>(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;
}
}
}

View File

@ -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<DispatcherEntry> DispatcherEntryList { get; set; }
}
}

View File

@ -8,7 +8,8 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Dispatcher.Abstractions\Microsoft.AspNetCore.Dispatcher.Abstractions.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Dispatcher.Abstractions\Microsoft.AspNetCore.Dispatcher.Abstractions.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Routing\Microsoft.AspNetCore.Routing.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -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; }
}
}