// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Microsoft.AspNet.Routing.Template { public class TemplateRoute : IRouter { private readonly IDictionary _defaults; private readonly IDictionary _constraints; private readonly IRouter _target; private readonly string _routeTemplate; private readonly TemplateMatcher _matcher; private readonly TemplateBinder _binder; public TemplateRoute(IRouter target, string routeTemplate) : this(target, routeTemplate, null, null) { } public TemplateRoute([NotNull] IRouter target, string routeTemplate, IDictionary defaults, IDictionary constraints) { _target = target; _routeTemplate = routeTemplate ?? string.Empty; _defaults = defaults ?? new Dictionary(StringComparer.OrdinalIgnoreCase); _constraints = RouteConstraintBuilder.BuildConstraints(constraints, _routeTemplate); // The parser will throw for invalid routes. var parsedTemplate = TemplateParser.Parse(RouteTemplate); _matcher = new TemplateMatcher(parsedTemplate); _binder = new TemplateBinder(_parsedTemplate, _defaults); } public IDictionary Defaults { get { return _defaults; } } public string RouteTemplate { get { return _routeTemplate; } } public async virtual Task RouteAsync([NotNull] RouteContext context) { var requestPath = context.RequestPath; if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/') { requestPath = requestPath.Substring(1); } var values = _matcher.Match(requestPath, _defaults); if (values == null) { // If we got back a null value set, that means the URI did not match return; } else { // Not currently doing anything to clean this up if it's not a match. Consider hardening this. context.Values = values; if (RouteConstraintMatcher.Match(_constraints, values, context.HttpContext, this, RouteDirection.IncomingRequest)) { await _target.RouteAsync(context); } } } public string GetVirtualPath(VirtualPathContext context) { var values = _binder.GetAcceptedValues(context.AmbientValues, context.Values); if (values == null) { // We're missing one the required values for this route. return null; } // Validate that the target can accept these values. var path = _target.GetVirtualPath(context); if (path != null) { // If the target generates a value then that can short circuit. return path; } else if (!context.IsBound) { // The target has rejected these values. return null; } path = _binder.BindValues(values); if (path == null) { context.IsBound = false; } return path; } } }