Code dump of algorthmic code for url generation
This doesn't yet expose url generation via public api, that will come in the next change.
This commit is contained in:
parent
4f71137cbd
commit
4022e5a5a4
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Template
|
||||
{
|
||||
public class BoundRouteTemplate
|
||||
{
|
||||
public string Path { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Template
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerToString()}")]
|
||||
public class Template
|
||||
{
|
||||
private const string SeparatorString = "/";
|
||||
|
||||
private readonly TemplateMatcher _matcher;
|
||||
private readonly TemplateBinder _binder;
|
||||
|
||||
public Template(List<TemplateSegment> segments)
|
||||
{
|
||||
if (segments == null)
|
||||
{
|
||||
throw new ArgumentNullException("segments");
|
||||
}
|
||||
|
||||
Segments = segments;
|
||||
|
||||
Parameters = new List<TemplatePart>();
|
||||
for (var i = 0; i < segments.Count; i++)
|
||||
{
|
||||
var segment = Segments[i];
|
||||
for (var j = 0; j < segment.Parts.Count; j++)
|
||||
{
|
||||
var part = segment.Parts[j];
|
||||
if (part.IsParameter)
|
||||
{
|
||||
Parameters.Add(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_matcher = new TemplateMatcher(this);
|
||||
_binder = new TemplateBinder(this);
|
||||
}
|
||||
|
||||
public List<TemplatePart> Parameters { get; private set; }
|
||||
|
||||
public List<TemplateSegment> Segments { get; private set; }
|
||||
|
||||
public IDictionary<string, object> Match(string requestPath, IDictionary<string, object> defaults)
|
||||
{
|
||||
return _matcher.Match(requestPath, defaults);
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return string.Join(SeparatorString, Segments.Select(s => s.DebuggerToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,580 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Template
|
||||
{
|
||||
public class TemplateBinder
|
||||
{
|
||||
public TemplateBinder(Template template)
|
||||
{
|
||||
if (template == null)
|
||||
{
|
||||
throw new ArgumentNullException("template");
|
||||
}
|
||||
|
||||
Template = template;
|
||||
}
|
||||
|
||||
public Template Template { get; private set; }
|
||||
|
||||
public BoundRouteTemplate Bind(IDictionary<string, object> defaults, IDictionary<string, object> ambientValues, IDictionary<string, object> values)
|
||||
{
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException("values");
|
||||
}
|
||||
|
||||
var context = GetAcceptedValues(defaults, ambientValues, values);
|
||||
if (context == null)
|
||||
{
|
||||
// We couldn't get values for all the required parameters
|
||||
return null;
|
||||
}
|
||||
|
||||
return BindValues(context);
|
||||
}
|
||||
|
||||
// Step 1: Get the list of values we're going to try to use to match and generate this URI
|
||||
private TemplateBindingContext GetAcceptedValues(IDictionary<string, object> defaults, IDictionary<string, object> ambientValues, IDictionary<string, object> values)
|
||||
{
|
||||
Contract.Assert(values != null);
|
||||
|
||||
var context = new TemplateBindingContext(defaults, values);
|
||||
|
||||
// Find out which entries in the URI are valid for the URI we want to generate.
|
||||
// If the URI had ordered parameters a="1", b="2", c="3" and the new values
|
||||
// specified that b="9", then we need to invalidate everything after it. The new
|
||||
// values should then be a="1", b="9", c=<no value>.
|
||||
for (var i = 0; i < Template.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = Template.Parameters[i];
|
||||
|
||||
// If it's a parameter subsegment, examine the current value to see if it matches the new value
|
||||
var parameterName = parameter.Name;
|
||||
|
||||
object newParameterValue;
|
||||
var hasNewParameterValue = values.TryGetValue(parameterName, out newParameterValue);
|
||||
if (hasNewParameterValue)
|
||||
{
|
||||
context.Use(parameterName);
|
||||
}
|
||||
|
||||
object currentParameterValue = null;
|
||||
var hasCurrentParameterValue = ambientValues != null && ambientValues.TryGetValue(parameterName, out currentParameterValue);
|
||||
|
||||
if (hasNewParameterValue && hasCurrentParameterValue)
|
||||
{
|
||||
if (!RoutePartsEqual(currentParameterValue, newParameterValue))
|
||||
{
|
||||
// Stop copying current values when we find one that doesn't match
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the parameter is a match, add it to the list of values we will use for URI generation
|
||||
if (hasNewParameterValue)
|
||||
{
|
||||
if (IsRoutePartNonEmpty(newParameterValue))
|
||||
{
|
||||
context.Accept(parameterName, newParameterValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hasCurrentParameterValue)
|
||||
{
|
||||
context.Accept(parameterName, currentParameterValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add all remaining new values to the list of values we will use for URI generation
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
if (IsRoutePartNonEmpty(kvp.Value))
|
||||
{
|
||||
context.Accept(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// Add all current values that aren't in the URI at all
|
||||
if (ambientValues != null)
|
||||
{
|
||||
foreach (var kvp in ambientValues)
|
||||
{
|
||||
var parameter = GetParameter(kvp.Key);
|
||||
if (parameter == null)
|
||||
{
|
||||
context.Accept(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Accept all remaining default values if they match a required parameter
|
||||
for (int i = 0; i < Template.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = Template.Parameters[i];
|
||||
if (parameter.IsOptional || parameter.IsCatchAll)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (context.NeedsValue(parameter.Name))
|
||||
{
|
||||
// Add the default value only if there isn't already a new value for it and
|
||||
// only if it actually has a default value, which we determine based on whether
|
||||
// the parameter value is required.
|
||||
context.AcceptDefault(parameter.Name);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that all required parameters have a value.
|
||||
for (var i = 0; i < Template.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = Template.Parameters[i];
|
||||
if (parameter.IsOptional || parameter.IsCatchAll)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!context.AcceptedValues.ContainsKey(parameter.Name))
|
||||
{
|
||||
// We don't have a value for this parameter, so we can't generate a url.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Any default values that don't appear as parameters are treated like filters. Any new values
|
||||
// provided must match these defaults.
|
||||
if (context.Filters != null)
|
||||
{
|
||||
foreach (var filter in context.Filters)
|
||||
{
|
||||
var parameter = GetParameter(filter.Key);
|
||||
if (parameter != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
object value;
|
||||
if (values.TryGetValue(filter.Key, out value))
|
||||
{
|
||||
if (RoutePartsEqual(value, filter.Value))
|
||||
{
|
||||
context.Use(filter.Key);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there is a non-parameterized value in the route and there is a
|
||||
// new value for it and it doesn't match, this route won't match.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
// Step 2: If the route is a match generate the appropriate URI
|
||||
private BoundRouteTemplate BindValues(TemplateBindingContext bindingContext)
|
||||
{
|
||||
var context = new UriBuildingContext();
|
||||
|
||||
for (var i = 0; i < Template.Segments.Count; i++)
|
||||
{
|
||||
Contract.Assert(context.BufferState == SegmentState.Beginning);
|
||||
Contract.Assert(context.UriState == SegmentState.Beginning);
|
||||
|
||||
var segment = Template.Segments[i];
|
||||
|
||||
for (var j = 0; j < segment.Parts.Count; j++)
|
||||
{
|
||||
var part = segment.Parts[j];
|
||||
|
||||
if (part.IsLiteral)
|
||||
{
|
||||
if (!context.Accept(part.Text))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (part.IsParameter)
|
||||
{
|
||||
// If it's a parameter, get its value
|
||||
object value;
|
||||
var hasValue = bindingContext.AcceptedValues.TryGetValue(part.Name, out value);
|
||||
if (hasValue)
|
||||
{
|
||||
bindingContext.Use(part.Name);
|
||||
}
|
||||
|
||||
var converted = Convert.ToString(value, CultureInfo.InvariantCulture);
|
||||
if (bindingContext.AcceptedDefaultValues.Contains(part.Name))
|
||||
{
|
||||
// If the accepted value is the same as the default value buffer it since
|
||||
// we won't necessarily add it to the URI we generate.
|
||||
if (!context.Buffer(converted))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!context.Accept(converted))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.EndSegment();
|
||||
}
|
||||
|
||||
// Encode the URI before we append the query string, otherwise we would double encode the query string
|
||||
var encoded = new StringBuilder();
|
||||
encoded.Append(UriEncode(context.Build()));
|
||||
|
||||
// Generate the query string
|
||||
var firstParam = true;
|
||||
foreach (var kvp in bindingContext.UnusedValues)
|
||||
{
|
||||
var converted = Convert.ToString(kvp.Value, CultureInfo.InvariantCulture);
|
||||
if (String.IsNullOrEmpty(converted))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
encoded.Append(firstParam ? '?' : '&');
|
||||
firstParam = false;
|
||||
|
||||
encoded.Append(Uri.EscapeDataString(kvp.Key));
|
||||
encoded.Append('=');
|
||||
encoded.Append(Uri.EscapeDataString(converted));
|
||||
}
|
||||
|
||||
return new BoundRouteTemplate()
|
||||
{
|
||||
Path = encoded.ToString(),
|
||||
};
|
||||
}
|
||||
|
||||
private static string UriEncode(string str)
|
||||
{
|
||||
string escape = Uri.EscapeUriString(str);
|
||||
return Regex.Replace(escape, "([#;?:@&=+$,])", EscapeReservedCharacters);
|
||||
}
|
||||
|
||||
private static string EscapeReservedCharacters(Match m)
|
||||
{
|
||||
return "%" + Convert.ToUInt16(m.Value[0]).ToString("x2", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private TemplatePart GetParameter(string name)
|
||||
{
|
||||
for (int i = 0; i < Template.Parameters.Count; i++)
|
||||
{
|
||||
var parameter = Template.Parameters[i];
|
||||
if (string.Equals(parameter.Name, name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return parameter;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool RoutePartsEqual(object a, object b)
|
||||
{
|
||||
string sa = a as string;
|
||||
string sb = b as string;
|
||||
|
||||
if (sa != null && sb != null)
|
||||
{
|
||||
// For strings do a case-insensitive comparison
|
||||
return string.Equals(sa, sb, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (a != null && b != null)
|
||||
{
|
||||
// Explicitly call .Equals() in case it is overridden in the type
|
||||
return a.Equals(b);
|
||||
}
|
||||
else
|
||||
{
|
||||
// At least one of them is null. Return true if they both are
|
||||
return a == b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsRoutePartNonEmpty(object routePart)
|
||||
{
|
||||
var routePartString = routePart as string;
|
||||
if (routePartString == null)
|
||||
{
|
||||
return routePart != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return routePartString.Length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
private class TemplateBindingContext
|
||||
{
|
||||
private readonly IDictionary<string, object> _defaults;
|
||||
|
||||
private readonly Dictionary<string, object> _acceptedValues;
|
||||
private readonly HashSet<string> _acceptedDefaultValues;
|
||||
private readonly Dictionary<string, object> _unusedValues;
|
||||
private readonly Dictionary<string, object> _filters;
|
||||
|
||||
public TemplateBindingContext(IDictionary<string, object> defaults, IDictionary<string, object> values)
|
||||
{
|
||||
if (values == null)
|
||||
{
|
||||
throw new ArgumentNullException("values");
|
||||
}
|
||||
|
||||
_defaults = defaults;
|
||||
|
||||
_acceptedValues = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
_acceptedDefaultValues = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
_unusedValues = new Dictionary<string, object>(values, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (_defaults != null)
|
||||
{
|
||||
_filters = new Dictionary<string, object>(defaults, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, object> AcceptedValues
|
||||
{
|
||||
get { return _acceptedValues; }
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// These are values that are equivalent to the default. These aren't written to the url unless
|
||||
/// necessary.
|
||||
/// </remarks>>
|
||||
public HashSet<string> AcceptedDefaultValues
|
||||
{
|
||||
get { return _acceptedDefaultValues; }
|
||||
}
|
||||
|
||||
public Dictionary<string, object> UnusedValues
|
||||
{
|
||||
get { return _unusedValues; }
|
||||
}
|
||||
|
||||
public Dictionary<string, object> Filters
|
||||
{
|
||||
get { return _filters; }
|
||||
}
|
||||
|
||||
public void Accept(string key, object value)
|
||||
{
|
||||
if (!_acceptedValues.ContainsKey(key))
|
||||
{
|
||||
_acceptedValues.Add(key, value);
|
||||
|
||||
object defaultValue;
|
||||
if (_defaults != null && _defaults.TryGetValue(key, out defaultValue))
|
||||
{
|
||||
if (RoutePartsEqual(value, defaultValue))
|
||||
{
|
||||
_acceptedDefaultValues.Add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AcceptDefault(string key)
|
||||
{
|
||||
Contract.Assert(!_acceptedValues.ContainsKey(key));
|
||||
|
||||
object value;
|
||||
if (_defaults != null && _defaults.TryGetValue(key, out value))
|
||||
{
|
||||
_filters.Remove(key);
|
||||
_acceptedValues.Add(key, value);
|
||||
|
||||
_acceptedDefaultValues.Add(key);
|
||||
}
|
||||
}
|
||||
|
||||
public bool NeedsValue(string key)
|
||||
{
|
||||
return !_acceptedValues.ContainsKey(key);
|
||||
}
|
||||
|
||||
public void Use(string key)
|
||||
{
|
||||
_unusedValues.Remove(key);
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return string.Format(
|
||||
"{{Accepted: '{0}' Filters: '{1}'}}",
|
||||
string.Join(", ", _acceptedValues.Keys),
|
||||
string.Join(", ", _filters.Keys));
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
private class UriBuildingContext
|
||||
{
|
||||
// Holds the 'accepted' parts of the uri.
|
||||
private readonly StringBuilder _uri;
|
||||
|
||||
// Holds the 'optional' parts of the uri. We need a secondary buffer to handle cases where an optional
|
||||
// segment is in the middle of the uri. We don't know whether or not we need to write it out - if it's
|
||||
// followed by other optional segments than we will just throw it away.
|
||||
private readonly StringBuilder _buffer;
|
||||
|
||||
private bool _hasEmptySegment;
|
||||
|
||||
public UriBuildingContext()
|
||||
{
|
||||
_uri = new StringBuilder();
|
||||
_buffer = new StringBuilder();
|
||||
|
||||
BufferState = SegmentState.Beginning;
|
||||
UriState = SegmentState.Beginning;
|
||||
|
||||
}
|
||||
|
||||
public SegmentState BufferState { get; private set; }
|
||||
|
||||
public SegmentState UriState { get; private set; }
|
||||
|
||||
public bool Accept(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
if (UriState == SegmentState.Inside || BufferState == SegmentState.Inside)
|
||||
{
|
||||
// We can't write an 'empty' part inside a segment
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_hasEmptySegment = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (_hasEmptySegment)
|
||||
{
|
||||
// We're trying to write text after an empty segment - this is not allowed.
|
||||
return false;
|
||||
}
|
||||
|
||||
_uri.Append(_buffer);
|
||||
_buffer.Clear();
|
||||
|
||||
if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
|
||||
{
|
||||
if (_uri.Length != 0)
|
||||
{
|
||||
_uri.Append("/");
|
||||
}
|
||||
}
|
||||
|
||||
BufferState = SegmentState.Inside;
|
||||
UriState = SegmentState.Inside;
|
||||
|
||||
_uri.Append(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Buffer(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
if (BufferState == SegmentState.Inside)
|
||||
{
|
||||
// We can't write an 'empty' part inside a segment
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_hasEmptySegment = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (_hasEmptySegment)
|
||||
{
|
||||
// We're trying to write text after an empty segment - this is not allowed.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (UriState == SegmentState.Inside)
|
||||
{
|
||||
// We've already written part of this segment so there's no point in buffering, we need to
|
||||
// write out the rest or give up.
|
||||
var result = Accept(value);
|
||||
|
||||
// We've already checked the conditions that could result in a rejected part, so this should
|
||||
// always be true.
|
||||
Contract.Assert(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (UriState == SegmentState.Beginning && BufferState == SegmentState.Beginning)
|
||||
{
|
||||
if (_uri.Length != 0 || _buffer.Length != 0)
|
||||
{
|
||||
_buffer.Append("/");
|
||||
}
|
||||
|
||||
BufferState = SegmentState.Inside;
|
||||
}
|
||||
|
||||
_buffer.Append(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void EndSegment()
|
||||
{
|
||||
BufferState = SegmentState.Beginning;
|
||||
UriState = SegmentState.Beginning;
|
||||
}
|
||||
|
||||
internal string Build()
|
||||
{
|
||||
// We can ignore any currently buffered segments - they are are guaranteed to be 'defaults'.
|
||||
return _uri.ToString();
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return string.Format("{{Accepted: '{0}' Buffered: '{1}'}}", _uri.ToString(), _buffer.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Segments are treated as all-or-none. We should never output a partial segment.
|
||||
// If we add any subsegment of this segment to the generated URI, we have to add
|
||||
// the complete match. For example, if the subsegment is "{p1}-{p2}.xml" and we
|
||||
// used a value for {p1}, we have to output the entire segment up to the next "/".
|
||||
// Otherwise we could end up with the partial segment "v1" instead of the entire
|
||||
// segment "v1-v2.xml".
|
||||
private enum SegmentState
|
||||
{
|
||||
Beginning,
|
||||
Inside,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,31 +2,28 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Template
|
||||
{
|
||||
[DebuggerDisplay("{DebuggerToString()}")]
|
||||
public class ParsedTemplate
|
||||
public class TemplateMatcher
|
||||
{
|
||||
private const string SeparatorString = "/";
|
||||
private const char SeparatorChar = '/';
|
||||
|
||||
private static readonly char[] Delimiters = new char[] { SeparatorChar };
|
||||
|
||||
public ParsedTemplate(List<TemplateSegment> segments)
|
||||
public TemplateMatcher(Template template)
|
||||
{
|
||||
if (segments == null)
|
||||
if (template == null)
|
||||
{
|
||||
throw new ArgumentNullException("segments");
|
||||
throw new ArgumentNullException("template");
|
||||
}
|
||||
|
||||
Segments = segments;
|
||||
Template = template;
|
||||
}
|
||||
|
||||
public List<TemplateSegment> Segments { get; private set; }
|
||||
public Template Template { get; private set; }
|
||||
|
||||
public IDictionary<string, object> Match(string requestPath, IDictionary<string, object> defaults)
|
||||
{
|
||||
|
|
@ -41,7 +38,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
|
||||
for (int i = 0; i < requestSegments.Length; i++)
|
||||
{
|
||||
var routeSegment = Segments.Count > i ? Segments[i] : null;
|
||||
var routeSegment = Template.Segments.Count > i ? Template.Segments[i] : null;
|
||||
var requestSegment = requestSegments[i];
|
||||
|
||||
if (routeSegment == null)
|
||||
|
|
@ -64,7 +61,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
Contract.Assert(part.IsParameter);
|
||||
|
||||
|
|
@ -118,11 +115,11 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
}
|
||||
}
|
||||
|
||||
for (int i = requestSegments.Length; i < Segments.Count; i++)
|
||||
for (int i = requestSegments.Length; i < Template.Segments.Count; i++)
|
||||
{
|
||||
// We've matched the request path so far, but still have remaining route segments. These need
|
||||
// to be all single-part parameter segments with default values or else they won't match.
|
||||
var routeSegment = Segments[i];
|
||||
var routeSegment = Template.Segments[i];
|
||||
if (routeSegment.Parts.Count > 1)
|
||||
{
|
||||
// If it has more than one part it must contain literals, so it can't match.
|
||||
|
|
@ -297,9 +294,5 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
return (lastIndex == 0) || routeSegment.Parts[0].IsParameter;
|
||||
}
|
||||
|
||||
private string DebuggerToString()
|
||||
{
|
||||
return string.Join(SeparatorString, Segments.Select(s => s.DebuggerToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
private const char EqualsSign = '=';
|
||||
private const char QuestionMark = '?';
|
||||
|
||||
public static ParsedTemplate Parse(string routeTemplate)
|
||||
public static Template Parse(string routeTemplate)
|
||||
{
|
||||
if (routeTemplate == null)
|
||||
{
|
||||
|
|
@ -51,7 +51,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
|
||||
if (IsAllValid(context, segments))
|
||||
{
|
||||
return new ParsedTemplate(segments);
|
||||
return new Template(segments);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
{
|
||||
private readonly IDictionary<string, object> _defaults;
|
||||
private readonly IRouteEndpoint _endpoint;
|
||||
private readonly ParsedTemplate _parsedRoute;
|
||||
private readonly Template _parsedTemplate;
|
||||
private readonly string _routeTemplate;
|
||||
|
||||
public TemplateRoute(IRouteEndpoint endpoint, string routeTemplate)
|
||||
|
|
@ -25,11 +25,11 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
}
|
||||
|
||||
_endpoint = endpoint;
|
||||
_routeTemplate = routeTemplate == null ? String.Empty : routeTemplate;
|
||||
_routeTemplate = routeTemplate ?? String.Empty;
|
||||
_defaults = defaults ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// The parser will throw for invalid routes.
|
||||
_parsedRoute = TemplateParser.Parse(RouteTemplate);
|
||||
_parsedTemplate = TemplateParser.Parse(RouteTemplate);
|
||||
}
|
||||
|
||||
public IDictionary<string, object> Defaults
|
||||
|
|
@ -60,7 +60,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
requestPath = requestPath.Substring(1);
|
||||
}
|
||||
|
||||
IDictionary<string, object> values = _parsedRoute.Match(requestPath, _defaults);
|
||||
IDictionary<string, object> values = _parsedTemplate.Match(requestPath, _defaults);
|
||||
if (values == null)
|
||||
{
|
||||
// If we got back a null value set, that means the URI did not match
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Template.Tests
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Template.Tests
|
||||
{
|
||||
// This is just a placeholder
|
||||
public class RouteValueDictionary : Dictionary<string, object>
|
||||
{
|
||||
public RouteValueDictionary()
|
||||
: base(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
}
|
||||
|
||||
public RouteValueDictionary(object obj)
|
||||
: base(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
if (obj != null)
|
||||
{
|
||||
foreach (var property in obj.GetType().GetTypeInfo().GetProperties())
|
||||
{
|
||||
Add(property.Name, property.GetValue(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,783 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
using Xunit.Extensions;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Template.Tests
|
||||
{
|
||||
public class TemplateMatcherTests
|
||||
{
|
||||
[Fact]
|
||||
public void MatchSingleRoute()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{controller}/{action}/{id}");
|
||||
|
||||
// Act
|
||||
var match = matcher.Match("Bank/DoAction/123", null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(match);
|
||||
Assert.Equal("Bank", match["controller"]);
|
||||
Assert.Equal("DoAction", match["action"]);
|
||||
Assert.Equal("123", match["id"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoMatchSingleRoute()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{controller}/{action}/{id}");
|
||||
|
||||
// Act
|
||||
var match = matcher.Match("Bank/DoAction", null);
|
||||
|
||||
// Assert
|
||||
Assert.Null(match);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchSingleRouteWithDefaults()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{controller}/{action}/{id}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("Bank/DoAction", new RouteValueDictionary(new { id = "default id" }));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Bank", rd["controller"]);
|
||||
Assert.Equal("DoAction", rd["action"]);
|
||||
Assert.Equal("default id", rd["id"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoMatchSingleRouteWithDefaults()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{controller}/{action}/{id}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("Bank", new RouteValueDictionary(new { id = "default id" }));
|
||||
|
||||
// Assert
|
||||
Assert.Null(rd);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchRouteWithLiterals()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("moo/{p1}/bar/{p2}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/111/bar/222", new RouteValueDictionary(new { p2 = "default p2" }));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("111", rd["p1"]);
|
||||
Assert.Equal("222", rd["p2"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchRouteWithLiteralsAndDefaults()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("moo/{p1}/bar/{p2}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/111/bar/", new RouteValueDictionary(new { p2 = "default p2" }));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("111", rd["p1"]);
|
||||
Assert.Equal("default p2", rd["p2"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchRouteWithOnlyLiterals()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("moo/bar");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/bar", null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
Assert.Equal<int>(0, rd.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoMatchRouteWithOnlyLiterals()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("moo/bars");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/bar", null);
|
||||
|
||||
// Assert
|
||||
Assert.Null(rd);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchRouteWithExtraSeparators()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("moo/bar");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/bar/", null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
Assert.Equal<int>(0, rd.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchRouteUrlWithExtraSeparators()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("moo/bar/");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/bar", null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
Assert.Equal<int>(0, rd.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchRouteUrlWithParametersAndExtraSeparators()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{p1}/{p2}/");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/bar", null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
Assert.Equal("moo", rd["p1"]);
|
||||
Assert.Equal("bar", rd["p2"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoMatchRouteUrlWithDifferentLiterals()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{p1}/{p2}/baz");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/bar/boo", null);
|
||||
|
||||
// Assert
|
||||
Assert.Null(rd);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoMatchLongerUrl()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{p1}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("moo/bar", null);
|
||||
|
||||
// Assert
|
||||
Assert.Null(rd);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchSimpleFilename()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("DEFAULT.ASPX");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("default.aspx", null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("{prefix}x{suffix}", "xxxxxxxxxx")]
|
||||
[InlineData("{prefix}xyz{suffix}", "xxxxyzxyzxxxxxxyz")]
|
||||
[InlineData("{prefix}xyz{suffix}", "abcxxxxyzxyzxxxxxxyzxx")]
|
||||
[InlineData("{prefix}xyz{suffix}", "xyzxyzxyzxyzxyz")]
|
||||
[InlineData("{prefix}xyz{suffix}", "xyzxyzxyzxyzxyz1")]
|
||||
[InlineData("{prefix}xyz{suffix}", "xyzxyzxyz")]
|
||||
[InlineData("{prefix}aa{suffix}", "aaaaa")]
|
||||
[InlineData("{prefix}aaa{suffix}", "aaaaa")]
|
||||
public void VerifyRouteMatchesWithContext(string template, string path)
|
||||
{
|
||||
var matcher = CreateMatcher(template);
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match(path, null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchRouteWithExtraDefaultValues()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{p1}/{p2}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("v1", new RouteValueDictionary(new { p2 = (string)null, foo = "bar" }));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
Assert.Equal<int>(3, rd.Count);
|
||||
Assert.Equal("v1", rd["p1"]);
|
||||
Assert.Null(rd["p2"]);
|
||||
Assert.Equal("bar", rd["foo"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchPrettyRouteWithExtraDefaultValues()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("date/{y}/{m}/{d}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("date/2007/08", new RouteValueDictionary(new { controller = "blog", action = "showpost", m = (string)null, d = (string)null }));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
Assert.Equal<int>(5, rd.Count);
|
||||
Assert.Equal("blog", rd["controller"]);
|
||||
Assert.Equal("showpost", rd["action"]);
|
||||
Assert.Equal("2007", rd["y"]);
|
||||
Assert.Equal("08", rd["m"]);
|
||||
Assert.Null(rd["d"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithMultiSegmentParamsOnBothEndsMatches()
|
||||
{
|
||||
RunTest(
|
||||
"language/{lang}-{region}",
|
||||
"language/en-US",
|
||||
null,
|
||||
new RouteValueDictionary(new { lang = "en", region = "US" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithMultiSegmentParamsOnLeftEndMatches()
|
||||
{
|
||||
RunTest(
|
||||
"language/{lang}-{region}a",
|
||||
"language/en-USa",
|
||||
null,
|
||||
new RouteValueDictionary(new { lang = "en", region = "US" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithMultiSegmentParamsOnRightEndMatches()
|
||||
{
|
||||
RunTest(
|
||||
"language/a{lang}-{region}",
|
||||
"language/aen-US",
|
||||
null,
|
||||
new RouteValueDictionary(new { lang = "en", region = "US" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithMultiSegmentParamsOnNeitherEndMatches()
|
||||
{
|
||||
RunTest(
|
||||
"language/a{lang}-{region}a",
|
||||
"language/aen-USa",
|
||||
null,
|
||||
new RouteValueDictionary(new { lang = "en", region = "US" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithMultiSegmentParamsOnNeitherEndDoesNotMatch()
|
||||
{
|
||||
RunTest(
|
||||
"language/a{lang}-{region}a",
|
||||
"language/a-USa",
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithMultiSegmentParamsOnNeitherEndDoesNotMatch2()
|
||||
{
|
||||
RunTest(
|
||||
"language/a{lang}-{region}a",
|
||||
"language/aen-a",
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithSimpleMultiSegmentParamsOnBothEndsMatches()
|
||||
{
|
||||
RunTest(
|
||||
"language/{lang}",
|
||||
"language/en",
|
||||
null,
|
||||
new RouteValueDictionary(new { lang = "en" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithSimpleMultiSegmentParamsOnBothEndsTrailingSlashDoesNotMatch()
|
||||
{
|
||||
RunTest(
|
||||
"language/{lang}",
|
||||
"language/",
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithSimpleMultiSegmentParamsOnBothEndsDoesNotMatch()
|
||||
{
|
||||
RunTest(
|
||||
"language/{lang}",
|
||||
"language",
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithSimpleMultiSegmentParamsOnLeftEndMatches()
|
||||
{
|
||||
RunTest(
|
||||
"language/{lang}-",
|
||||
"language/en-",
|
||||
null,
|
||||
new RouteValueDictionary(new { lang = "en" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithSimpleMultiSegmentParamsOnRightEndMatches()
|
||||
{
|
||||
RunTest(
|
||||
"language/a{lang}",
|
||||
"language/aen",
|
||||
null,
|
||||
new RouteValueDictionary(new { lang = "en" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithSimpleMultiSegmentParamsOnNeitherEndMatches()
|
||||
{
|
||||
RunTest(
|
||||
"language/a{lang}a",
|
||||
"language/aena",
|
||||
null,
|
||||
new RouteValueDictionary(new { lang = "en" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithMultiSegmentStandardMvcRouteMatches()
|
||||
{
|
||||
RunTest(
|
||||
"{controller}.mvc/{action}/{id}",
|
||||
"home.mvc/index",
|
||||
new RouteValueDictionary(new { action = "Index", id = (string)null }),
|
||||
new RouteValueDictionary(new { controller = "home", action = "index", id = (string)null }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithMultiSegmentParamsOnBothEndsWithDefaultValuesMatches()
|
||||
{
|
||||
RunTest(
|
||||
"language/{lang}-{region}",
|
||||
"language/-",
|
||||
new RouteValueDictionary(new { lang = "xx", region = "yy" }),
|
||||
null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithUrlWithMultiSegmentWithRepeatedDots()
|
||||
{
|
||||
RunTest(
|
||||
"{Controller}..mvc/{id}/{Param1}",
|
||||
"Home..mvc/123/p1",
|
||||
null,
|
||||
new RouteValueDictionary(new { Controller = "Home", id = "123", Param1 = "p1" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithUrlWithTwoRepeatedDots()
|
||||
{
|
||||
RunTest(
|
||||
"{Controller}.mvc/../{action}",
|
||||
"Home.mvc/../index",
|
||||
null,
|
||||
new RouteValueDictionary(new { Controller = "Home", action = "index" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithUrlWithThreeRepeatedDots()
|
||||
{
|
||||
RunTest(
|
||||
"{Controller}.mvc/.../{action}",
|
||||
"Home.mvc/.../index",
|
||||
null,
|
||||
new RouteValueDictionary(new { Controller = "Home", action = "index" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithUrlWithManyRepeatedDots()
|
||||
{
|
||||
RunTest(
|
||||
"{Controller}.mvc/../../../{action}",
|
||||
"Home.mvc/../../../index",
|
||||
null,
|
||||
new RouteValueDictionary(new { Controller = "Home", action = "index" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithUrlWithExclamationPoint()
|
||||
{
|
||||
RunTest(
|
||||
"{Controller}.mvc!/{action}",
|
||||
"Home.mvc!/index",
|
||||
null,
|
||||
new RouteValueDictionary(new { Controller = "Home", action = "index" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithUrlWithStartingDotDotSlash()
|
||||
{
|
||||
RunTest(
|
||||
"../{Controller}.mvc",
|
||||
"../Home.mvc",
|
||||
null,
|
||||
new RouteValueDictionary(new { Controller = "Home" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithUrlWithStartingBackslash()
|
||||
{
|
||||
RunTest(
|
||||
@"\{Controller}.mvc",
|
||||
@"\Home.mvc",
|
||||
null,
|
||||
new RouteValueDictionary(new { Controller = "Home" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithUrlWithBackslashSeparators()
|
||||
{
|
||||
RunTest(
|
||||
@"{Controller}.mvc\{id}\{Param1}",
|
||||
@"Home.mvc\123\p1",
|
||||
null,
|
||||
new RouteValueDictionary(new { Controller = "Home", id = "123", Param1 = "p1" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithUrlWithParenthesesLiterals()
|
||||
{
|
||||
RunTest(
|
||||
@"(Controller).mvc",
|
||||
@"(Controller).mvc",
|
||||
null,
|
||||
new RouteValueDictionary());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithUrlWithTrailingSlashSpace()
|
||||
{
|
||||
RunTest(
|
||||
@"Controller.mvc/ ",
|
||||
@"Controller.mvc/ ",
|
||||
null,
|
||||
new RouteValueDictionary());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithUrlWithTrailingSpace()
|
||||
{
|
||||
RunTest(
|
||||
@"Controller.mvc ",
|
||||
@"Controller.mvc ",
|
||||
null,
|
||||
new RouteValueDictionary());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithCatchAllCapturesDots()
|
||||
{
|
||||
// DevDiv Bugs 189892: UrlRouting: Catch all parameter cannot capture url segments that contain the "."
|
||||
RunTest(
|
||||
"Home/ShowPilot/{missionId}/{*name}",
|
||||
"Home/ShowPilot/777/12345./foobar",
|
||||
new RouteValueDictionary(new
|
||||
{
|
||||
controller = "Home",
|
||||
action = "ShowPilot",
|
||||
missionId = (string)null,
|
||||
name = (string)null
|
||||
}),
|
||||
new RouteValueDictionary(new { controller = "Home", action = "ShowPilot", missionId = "777", name = "12345./foobar" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RouteWithCatchAllClauseCapturesManySlashes()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{p1}/{*p2}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("v1/v2/v3", null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
Assert.Equal<int>(2, rd.Count);
|
||||
Assert.Equal("v1", rd["p1"]);
|
||||
Assert.Equal("v2/v3", rd["p2"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RouteWithCatchAllClauseCapturesTrailingSlash()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{p1}/{*p2}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("v1/", null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
Assert.Equal<int>(2, rd.Count);
|
||||
Assert.Equal("v1", rd["p1"]);
|
||||
Assert.Null(rd["p2"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RouteWithCatchAllClauseCapturesEmptyContent()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{p1}/{*p2}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("v1", null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
Assert.Equal<int>(2, rd.Count);
|
||||
Assert.Equal("v1", rd["p1"]);
|
||||
Assert.Null(rd["p2"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RouteWithCatchAllClauseUsesDefaultValueForEmptyContent()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{p1}/{*p2}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("v1", new RouteValueDictionary(new { p2 = "catchall" }));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
Assert.Equal<int>(2, rd.Count);
|
||||
Assert.Equal("v1", rd["p1"]);
|
||||
Assert.Equal("catchall", rd["p2"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RouteWithCatchAllClauseIgnoresDefaultValueForNonEmptyContent()
|
||||
{
|
||||
// Arrange
|
||||
var matcher = CreateMatcher("{p1}/{*p2}");
|
||||
|
||||
// Act
|
||||
var rd = matcher.Match("v1/hello/whatever", new RouteValueDictionary(new { p2 = "catchall" }));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(rd);
|
||||
Assert.Equal<int>(2, rd.Count);
|
||||
Assert.Equal("v1", rd["p1"]);
|
||||
Assert.Equal("hello/whatever", rd["p2"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataDoesNotMatchOnlyLeftLiteralMatch()
|
||||
{
|
||||
// DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
|
||||
RunTest(
|
||||
"foo",
|
||||
"fooBAR",
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataDoesNotMatchOnlyRightLiteralMatch()
|
||||
{
|
||||
// DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
|
||||
RunTest(
|
||||
"foo",
|
||||
"BARfoo",
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataDoesNotMatchMiddleLiteralMatch()
|
||||
{
|
||||
// DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
|
||||
RunTest(
|
||||
"foo",
|
||||
"BARfooBAR",
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataDoesMatchesExactLiteralMatch()
|
||||
{
|
||||
// DevDiv Bugs 191180: UrlRouting: Wrong template getting matched if a url segment is a substring of the requested url
|
||||
RunTest(
|
||||
"foo",
|
||||
"foo",
|
||||
null,
|
||||
new RouteValueDictionary());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithWeirdParameterNames()
|
||||
{
|
||||
RunTest(
|
||||
"foo/{ }/{.!$%}/{dynamic.data}/{op.tional}",
|
||||
"foo/space/weird/orderid",
|
||||
new RouteValueDictionary() { { " ", "not a space" }, { "op.tional", "default value" }, { "ran!dom", "va@lue" } },
|
||||
new RouteValueDictionary() { { " ", "space" }, { ".!$%", "weird" }, { "dynamic.data", "orderid" }, { "op.tional", "default value" }, { "ran!dom", "va@lue" } });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataDoesNotMatchRouteWithLiteralSeparatorDefaultsButNoValue()
|
||||
{
|
||||
RunTest(
|
||||
"{controller}/{language}-{locale}",
|
||||
"foo",
|
||||
new RouteValueDictionary(new { language = "en", locale = "US" }),
|
||||
null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataDoesNotMatchesRouteWithLiteralSeparatorDefaultsAndLeftValue()
|
||||
{
|
||||
RunTest(
|
||||
"{controller}/{language}-{locale}",
|
||||
"foo/xx-",
|
||||
new RouteValueDictionary(new { language = "en", locale = "US" }),
|
||||
null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataDoesNotMatchesRouteWithLiteralSeparatorDefaultsAndRightValue()
|
||||
{
|
||||
RunTest(
|
||||
"{controller}/{language}-{locale}",
|
||||
"foo/-yy",
|
||||
new RouteValueDictionary(new { language = "en", locale = "US" }),
|
||||
null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataMatchesRouteWithLiteralSeparatorDefaultsAndValue()
|
||||
{
|
||||
RunTest(
|
||||
"{controller}/{language}-{locale}",
|
||||
"foo/xx-yy",
|
||||
new RouteValueDictionary(new { language = "en", locale = "US" }),
|
||||
new RouteValueDictionary { { "language", "xx" }, { "locale", "yy" }, { "controller", "foo" } });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchSetsOptionalParameter()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateMatcher("{controller}/{action?}");
|
||||
var url = "Home/Index";
|
||||
|
||||
// Act
|
||||
var match = route.Match(url, null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(match);
|
||||
Assert.Equal(2, match.Values.Count);
|
||||
Assert.Equal("Home", match["controller"]);
|
||||
Assert.Equal("Index", match["action"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchDoesNotSetOptionalParameter()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateMatcher("{controller}/{action?}");
|
||||
var url = "Home";
|
||||
|
||||
// Act
|
||||
var match = route.Match(url, null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(match);
|
||||
Assert.Equal(1, match.Values.Count);
|
||||
Assert.Equal("Home", match["controller"]);
|
||||
Assert.False(match.ContainsKey("action"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchMultipleOptionalParameters()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateMatcher("{controller}/{action?}/{id?}");
|
||||
var url = "Home/Index";
|
||||
|
||||
// Act
|
||||
var match = route.Match(url, null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(match);
|
||||
Assert.Equal(2, match.Values.Count);
|
||||
Assert.Equal("Home", match["controller"]);
|
||||
Assert.Equal("Index", match["action"]);
|
||||
Assert.False(match.ContainsKey("id"));
|
||||
}
|
||||
|
||||
private TemplateMatcher CreateMatcher(string template)
|
||||
{
|
||||
return new TemplateMatcher(TemplateParser.Parse(template));
|
||||
}
|
||||
|
||||
private static void RunTest(string template, string path, IDictionary<string, object> defaults, IDictionary<string, object> expected)
|
||||
{
|
||||
// Arrange
|
||||
var matcher = new TemplateMatcher(TemplateParser.Parse(template));
|
||||
|
||||
// Act
|
||||
var match = matcher.Match(path, defaults);
|
||||
|
||||
// Assert
|
||||
if (expected == null)
|
||||
{
|
||||
Assert.Null(match);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.NotNull(match);
|
||||
Assert.Equal(expected.Count, match.Values.Count);
|
||||
foreach (string key in match.Keys)
|
||||
{
|
||||
Assert.Equal(expected[key], match[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
// Arrange
|
||||
var template = "cool";
|
||||
|
||||
var expected = new ParsedTemplate(new List<TemplateSegment>());
|
||||
var expected = new Template(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool"));
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<ParsedTemplate>(expected, actual, new TemplateParsedRouteEqualityComparer());
|
||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -32,15 +32,16 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
// Arrange
|
||||
var template = "{p}";
|
||||
|
||||
var expected = new ParsedTemplate(new List<TemplateSegment>());
|
||||
var expected = new Template(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p", false, false));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<ParsedTemplate>(expected, actual, new TemplateParsedRouteEqualityComparer());
|
||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -49,15 +50,16 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
// Arrange
|
||||
var template = "{p?}";
|
||||
|
||||
var expected = new ParsedTemplate(new List<TemplateSegment>());
|
||||
var expected = new Template(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p", false, true));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<ParsedTemplate>(expected, actual, new TemplateParsedRouteEqualityComparer());
|
||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -66,7 +68,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
// Arrange
|
||||
var template = "cool/awesome/super";
|
||||
|
||||
var expected = new ParsedTemplate(new List<TemplateSegment>());
|
||||
var expected = new Template(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool"));
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
|
|
@ -78,7 +80,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<ParsedTemplate>(expected, actual, new TemplateParsedRouteEqualityComparer());
|
||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -87,19 +89,25 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
// Arrange
|
||||
var template = "{p1}/{p2}/{*p3}";
|
||||
|
||||
var expected = new ParsedTemplate(new List<TemplateSegment>());
|
||||
var expected = new Template(new List<TemplateSegment>());
|
||||
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false, false));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[1].Parts.Add(TemplatePart.CreateParameter("p2", false, false));
|
||||
expected.Parameters.Add(expected.Segments[1].Parts[0]);
|
||||
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[2].Parts.Add(TemplatePart.CreateParameter("p3", true, false));
|
||||
expected.Parameters.Add(expected.Segments[2].Parts[0]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<ParsedTemplate>(expected, actual, new TemplateParsedRouteEqualityComparer());
|
||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -108,16 +116,17 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
// Arrange
|
||||
var template = "cool-{p1}";
|
||||
|
||||
var expected = new ParsedTemplate(new List<TemplateSegment>());
|
||||
var expected = new Template(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false, false));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[1]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<ParsedTemplate>(expected, actual, new TemplateParsedRouteEqualityComparer());
|
||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -126,16 +135,17 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
// Arrange
|
||||
var template = "{p1}-cool";
|
||||
|
||||
var expected = new ParsedTemplate(new List<TemplateSegment>());
|
||||
var expected = new Template(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false, false));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<ParsedTemplate>(expected, actual, new TemplateParsedRouteEqualityComparer());
|
||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -144,17 +154,19 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
// Arrange
|
||||
var template = "{p1}-cool-{p2}";
|
||||
|
||||
var expected = new ParsedTemplate(new List<TemplateSegment>());
|
||||
var expected = new Template(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false, false));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[0]);
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p2", false, false));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[2]);
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<ParsedTemplate>(expected, actual, new TemplateParsedRouteEqualityComparer());
|
||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -163,17 +175,18 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
// Arrange
|
||||
var template = "cool-{p1}-awesome";
|
||||
|
||||
var expected = new ParsedTemplate(new List<TemplateSegment>());
|
||||
var expected = new Template(new List<TemplateSegment>());
|
||||
expected.Segments.Add(new TemplateSegment());
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("cool-"));
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateParameter("p1", false, false));
|
||||
expected.Parameters.Add(expected.Segments[0].Parts[1]);
|
||||
expected.Segments[0].Parts.Add(TemplatePart.CreateLiteral("-awesome"));
|
||||
|
||||
// Act
|
||||
var actual = TemplateParser.Parse(template);
|
||||
|
||||
// Assert
|
||||
Assert.Equal<ParsedTemplate>(expected, actual, new TemplateParsedRouteEqualityComparer());
|
||||
Assert.Equal<Template>(expected, actual, new TemplateEqualityComparer());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -393,9 +406,9 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
"Parameter name: routeTemplate");
|
||||
}
|
||||
|
||||
private class TemplateParsedRouteEqualityComparer : IEqualityComparer<ParsedTemplate>
|
||||
private class TemplateEqualityComparer : IEqualityComparer<Template>
|
||||
{
|
||||
public bool Equals(ParsedTemplate x, ParsedTemplate y)
|
||||
public bool Equals(Template x, Template y)
|
||||
{
|
||||
if (x == null && y == null)
|
||||
{
|
||||
|
|
@ -421,26 +434,47 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
|
||||
for (int j = 0; j < x.Segments[i].Parts.Count; j++)
|
||||
{
|
||||
var xPart = x.Segments[i].Parts[j];
|
||||
var yPart = y.Segments[i].Parts[j];
|
||||
|
||||
if (xPart.IsLiteral != yPart.IsLiteral ||
|
||||
xPart.IsParameter != yPart.IsParameter ||
|
||||
xPart.IsCatchAll != yPart.IsCatchAll ||
|
||||
xPart.IsOptional != yPart.IsOptional ||
|
||||
!String.Equals(xPart.Name, yPart.Name, StringComparison.Ordinal) ||
|
||||
!String.Equals(xPart.Name, yPart.Name, StringComparison.Ordinal))
|
||||
if (!Equals(x.Segments[i].Parts[j], y.Segments[i].Parts[j]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (x.Parameters.Count != y.Parameters.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < x.Parameters.Count; i++)
|
||||
{
|
||||
if (!Equals(x.Parameters[i], y.Parameters[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public int GetHashCode(ParsedTemplate obj)
|
||||
private bool Equals(TemplatePart x, TemplatePart y)
|
||||
{
|
||||
if (x.IsLiteral != y.IsLiteral ||
|
||||
x.IsParameter != y.IsParameter ||
|
||||
x.IsCatchAll != y.IsCatchAll ||
|
||||
x.IsOptional != y.IsOptional ||
|
||||
!String.Equals(x.Name, y.Name, StringComparison.Ordinal) ||
|
||||
!String.Equals(x.Name, y.Name, StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int GetHashCode(Template obj)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue