Routing Logging

Added scoped logging to RouterMiddleware, RouteCollection, and
TemplateRoute.
This commit is contained in:
Ben Brown 2014-07-17 12:44:53 -07:00
parent 61436fb7d1
commit fca9831115
28 changed files with 1545 additions and 208 deletions

View File

@ -0,0 +1,37 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Routing.Logging
{
public static class LogFormatter
{
/// <summary>
/// A formatter for use with <see cref="Microsoft.Framework.Logging.ILogger.WriteCore(
/// Framework.Logging.TraceType,
/// int,
/// object,
/// Exception, Func{object, Exception, string})"/>.
/// </summary>
public static string Formatter(object o, Exception e)
{
if (o != null && e != null)
{
return o + Environment.NewLine + e;
}
if (o != null)
{
return o.ToString();
}
if (e != null)
{
return e.ToString();
}
return "";
}
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Routing.Logging
{
internal static class LoggerExtensions
{
public static bool WriteValues([NotNull] this ILogger logger, object values)
{
return logger.WriteCore(
eventType: TraceType.Information,
eventId: 0,
state: values,
exception: null,
formatter: LogFormatter.Formatter);
}
}
}

View File

@ -0,0 +1,60 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Text;
namespace Microsoft.AspNet.Routing.Logging
{
/// <summary>
/// Describes the state of
/// <see cref="Microsoft.AspNet.Routing.RouteCollection.RouteAsync(RouteContext)"/>.
/// </summary>
public class RouteCollectionRouteAsyncValues
{
/// <summary>
/// The name of the state.
/// </summary>
public string Name
{
get
{
return "RouteCollection.RouteAsync";
}
}
/// <summary>
/// The available routes.
/// </summary>
public IList<IRouter> Routes { get; set; }
/// <summary>
/// True if the request is handled.
/// </summary>
public bool Handled { get; set; }
/// <summary>
/// A summary of the data for display.
/// </summary>
public string Summary
{
get
{
var builder = new StringBuilder();
builder.AppendLine(Name);
builder.Append("\tRoutes: ");
StringBuilderHelpers.Append(builder, Routes);
builder.AppendLine();
builder.Append("\tHandled? ");
builder.Append(Handled);
return builder.ToString();
}
}
/// <inheritdoc/>
public override string ToString()
{
return Summary;
}
}
}

View File

@ -0,0 +1,71 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text;
namespace Microsoft.AspNet.Routing.Logging
{
/// <summary>
/// Describes the state of <see cref="RouteConstraintMatcher.Match(
/// System.Collections.Generic.IDictionary{string, IRouteConstraint},
/// System.Collections.Generic.IDictionary{string, object},
/// Http.HttpContext,
/// IRouter,
/// RouteDirection,
/// Framework.Logging.ILogger)"/>.
/// </summary>
public class RouteConstraintMatcherMatchValues
{
/// <summary>
/// The name of the state.
/// </summary>
public string Name
{
get
{
return "RouteConstraintMatcher.Match";
}
}
/// <summary>
/// The key of the constraint.
/// </summary>
public string ConstraintKey { get; set; }
/// <summary>
/// The constraint.
/// </summary>
public IRouteConstraint Constraint { get; set; }
/// <summary>
/// True if the <see cref="Constraint"/> matched.
/// </summary>
public bool Matched { get; set; }
/// <summary>
/// A summary of the data for display.
/// </summary>
public string Summary
{
get
{
var builder = new StringBuilder();
builder.AppendLine(Name);
builder.Append("\tConstraint key: ");
builder.AppendLine(ConstraintKey);
builder.Append("\tConstraint: ");
builder.Append(Constraint);
builder.AppendLine();
builder.Append("\tMatched? ");
builder.Append(Matched);
return builder.ToString();
}
}
/// <inheritdoc/>
public override string ToString()
{
return Summary;
}
}
}

View File

@ -0,0 +1,51 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Text;
namespace Microsoft.AspNet.Routing.Logging
{
/// <summary>
/// Describes the state of
/// <see cref="Microsoft.AspNet.Builder.RouterMiddleware.Invoke(Http.HttpContext)"/>.
/// </summary>
public class RouterMiddlewareInvokeValues
{
/// <summary>
/// The name of the state.
/// </summary>
public string Name
{
get
{
return "RouterMiddleware.Invoke";
}
}
/// <summary>
/// True if the request is handled.
/// </summary>
public bool Handled { get; set; }
/// <summary>
/// A summary of the data for display.
/// </summary>
public string Summary
{
get
{
var builder = new StringBuilder();
builder.AppendLine(Name);
builder.Append("\tHandled? ");
builder.Append(Handled);
return builder.ToString();
}
}
/// <inheritdoc />
public override string ToString()
{
return Summary;
}
}
}

View File

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Text;
namespace Microsoft.AspNet.Routing.Logging
{
internal static class StringBuilderHelpers
{
public static void Append<T>(StringBuilder builder, IEnumerable<T> items)
{
if (items == null)
{
return;
}
foreach (var item in items)
{
builder.Append(Environment.NewLine);
builder.Append("\t\t");
builder.Append(item != null ? item.ToString() : "null");
}
}
public static void Append<K, V>(StringBuilder builder, IDictionary<K, V> dict)
{
if (dict == null)
{
return;
}
foreach (var kvp in dict)
{
builder.Append(Environment.NewLine);
builder.Append("\t\t");
builder.Append(kvp.Key != null ? kvp.Key.ToString() : "null");
builder.Append(" : ");
builder.Append(kvp.Value != null ? kvp.Value.ToString() : "null");
}
}
}
}

View File

@ -0,0 +1,122 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Text;
namespace Microsoft.AspNet.Routing.Logging
{
/// <summary>
/// Describes the state of
/// <see cref="Microsoft.AspNet.Routing.Template.TemplateRoute.RouteAsync(RouteContext)"/>.
/// </summary>
public class TemplateRouteRouteAsyncValues
{
/// <summary>
/// The name of the state.
/// </summary>
public string Name
{
get
{
return "TemplateRoute.RouteAsync";
}
}
/// <summary>
/// The target.
/// </summary>
public IRouter Target { get; set; }
/// <summary>
/// The template.
/// </summary>
public string Template { get; set; }
/// <summary>
/// The request path.
/// </summary>
public string RequestPath { get; set; }
/// <summary>
/// The values produced by default.
/// </summary>
public IDictionary<string, object> DefaultValues { get; set; }
/// <summary>
/// The values produced from the request.
/// </summary>
public IDictionary<string, object> ProducedValues { get; set; }
/// <summary>
/// The constraints matched on the produced values.
/// </summary>
public IDictionary<string, IRouteConstraint> Constraints { get; set; }
/// <summary>
/// True if the <see cref="ProducedValues"/> matched.
/// </summary>
public bool MatchedTemplate { get; set; }
/// <summary>
/// True if the <see cref="Constraints"/> matched.
/// </summary>
public bool MatchedConstraints { get; set; }
/// <summary>
/// True if this route matched.
/// </summary>
public bool Matched { get; set; }
/// <summary>
/// True if the request is handled.
/// </summary>
public bool Handled { get; set; }
/// <summary>
/// A summary of the data for display.
/// </summary>
public string Summary
{
get
{
var builder = new StringBuilder();
builder.AppendLine(Name);
builder.Append("\tTarget: ");
builder.Append(Target);
builder.AppendLine();
builder.Append("\tTemplate: ");
builder.AppendLine(Template);
builder.Append("\tRequest path: ");
builder.AppendLine(RequestPath);
builder.Append("\tDefault values: ");
StringBuilderHelpers.Append(builder, DefaultValues);
builder.AppendLine();
builder.Append("\tProduced values: ");
StringBuilderHelpers.Append(builder, ProducedValues);
builder.AppendLine();
builder.Append("\tConstraints: ");
StringBuilderHelpers.Append(builder, Constraints);
builder.AppendLine();
builder.Append("\tMatched template? ");
builder.Append(MatchedTemplate);
builder.AppendLine();
builder.Append("\tMatched constraints? ");
builder.Append(MatchedConstraints);
builder.AppendLine();
builder.Append("\tMatched? ");
builder.Append(Matched);
builder.AppendLine();
builder.Append("\tHandled? ");
builder.Append(Handled);
return builder.ToString();
}
}
/// <inheritdoc/>
public override string ToString()
{
return Summary;
}
}
}

View File

@ -47,10 +47,17 @@
<Compile Include="INamedRouter.cs" />
<Compile Include="InlineRouteParameterParser.cs" />
<Compile Include="IRouteBuilder.cs" />
<Compile Include="Logging\LoggerExtensions.cs" />
<Compile Include="Logging\RouteConstraintMatcherMatchValues.cs" />
<Compile Include="Logging\StringBuilderHelpers.cs" />
<Compile Include="RouteOptions.cs" />
<Compile Include="IRouteCollection.cs" />
<Compile Include="IRouteConstraint.cs" />
<Compile Include="IRouter.cs" />
<Compile Include="Logging\RouteCollectionRouteAsyncValues.cs" />
<Compile Include="Logging\RouterMiddlewareInvokeValues.cs" />
<Compile Include="Logging\TemplateRouteRouteAsyncValues.cs" />
<Compile Include="Logging\LogFormatter.cs" />
<Compile Include="NotNullAttribute.cs" />
<Compile Include="Properties\Resources.Designer.cs" />
<Compile Include="RouteBuilder.cs" />

View File

@ -4,6 +4,10 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Logging;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Routing
{
@ -11,8 +15,9 @@ namespace Microsoft.AspNet.Routing
{
private readonly List<IRouter> _routes = new List<IRouter>();
private readonly List<IRouter> _unnamedRoutes = new List<IRouter>();
private readonly Dictionary<string, INamedRouter> _namedRoutes =
private readonly Dictionary<string, INamedRouter> _namedRoutes =
new Dictionary<string, INamedRouter>(StringComparer.OrdinalIgnoreCase);
private ILogger _logger;
public IRouter this[int index]
{
@ -44,14 +49,27 @@ namespace Microsoft.AspNet.Routing
public async virtual Task RouteAsync(RouteContext context)
{
for (var i = 0; i < Count; i++)
EnsureLogger(context.HttpContext);
using (_logger.BeginScope("RouteCollection.RouteAsync"))
{
var route = this[i];
await route.RouteAsync(context);
if (context.IsHandled)
for (var i = 0; i < Count; i++)
{
return;
var route = this[i];
await route.RouteAsync(context);
if (context.IsHandled)
{
break;
}
}
if (_logger.IsEnabled(TraceType.Information))
{
_logger.WriteValues(new RouteCollectionRouteAsyncValues()
{
Handled = context.IsHandled,
Routes = _routes
});
}
}
}
@ -98,5 +116,14 @@ namespace Microsoft.AspNet.Routing
return null;
}
private void EnsureLogger(HttpContext context)
{
if (_logger == null)
{
var factory = context.RequestServices.GetService<ILoggerFactory>();
_logger = factory.Create<RouteCollection>();
}
}
}
}

View File

@ -3,6 +3,8 @@
using System.Collections.Generic;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Logging;
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Routing
{
@ -12,7 +14,8 @@ namespace Microsoft.AspNet.Routing
[NotNull] IDictionary<string, object> routeValues,
[NotNull] HttpContext httpContext,
[NotNull] IRouter route,
[NotNull] RouteDirection routeDirection)
[NotNull] RouteDirection routeDirection,
[NotNull] ILogger logger)
{
if (constraints == null)
{
@ -24,8 +27,30 @@ namespace Microsoft.AspNet.Routing
var constraint = kvp.Value;
if (!constraint.Match(httpContext, route, kvp.Key, routeValues, routeDirection))
{
if (routeDirection.Equals(RouteDirection.IncomingRequest)
&& logger.IsEnabled(TraceType.Information))
{
logger.WriteValues(new RouteConstraintMatcherMatchValues()
{
ConstraintKey = kvp.Key,
Constraint = kvp.Value,
Matched = false
});
}
return false;
}
if (routeDirection.Equals(RouteDirection.IncomingRequest)
&& logger.IsEnabled(TraceType.Information))
{
logger.WriteValues(new RouteConstraintMatcherMatchValues()
{
ConstraintKey = kvp.Key,
Constraint = kvp.Value,
Matched = true
});
}
}
return true;

View File

@ -4,11 +4,16 @@
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Routing.Logging;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Builder
{
public class RouterMiddleware
{
private ILogger _logger;
public RouterMiddleware(RequestDelegate next, IRouter router)
{
Next = next;
@ -29,13 +34,32 @@ namespace Microsoft.AspNet.Builder
public async Task Invoke(HttpContext httpContext)
{
var context = new RouteContext(httpContext);
context.RouteData.Routers.Add(Router);
await Router.RouteAsync(context);
if (!context.IsHandled)
EnsureLogger(httpContext);
using (_logger.BeginScope("RouterMiddleware.Invoke"))
{
await Next.Invoke(httpContext);
var context = new RouteContext(httpContext);
context.RouteData.Routers.Add(Router);
await Router.RouteAsync(context);
if (_logger.IsEnabled(TraceType.Information))
{
_logger.WriteValues(new RouterMiddlewareInvokeValues() { Handled = context.IsHandled });
}
if (!context.IsHandled)
{
await Next.Invoke(httpContext);
}
}
}
private void EnsureLogger(HttpContext context)
{
if (_logger == null)
{
var factory = context.RequestServices.GetService<ILoggerFactory>();
_logger = factory.Create<RouterMiddleware>();
}
}
}

View File

@ -4,7 +4,10 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNet.Routing.Constraints;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Logging;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Routing.Template
{
@ -17,6 +20,8 @@ namespace Microsoft.AspNet.Routing.Template
private readonly string _routeTemplate;
private readonly TemplateMatcher _matcher;
private readonly TemplateBinder _binder;
private ILogger _logger;
private ILogger _constraintLogger;
public TemplateRoute(IRouter target, string routeTemplate, IInlineConstraintResolver inlineConstraintResolver)
: this(target, routeTemplate, null, null, inlineConstraintResolver)
@ -73,30 +78,69 @@ namespace Microsoft.AspNet.Routing.Template
public async virtual Task RouteAsync([NotNull] RouteContext context)
{
var requestPath = context.HttpContext.Request.Path.Value;
if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
EnsureLoggers(context.HttpContext);
using (_logger.BeginScope("TemplateRoute.RouteAsync"))
{
requestPath = requestPath.Substring(1);
}
var requestPath = context.HttpContext.Request.Path.Value;
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.RouteData.Values = values;
if (RouteConstraintMatcher.Match(Constraints,
values,
context.HttpContext,
this,
RouteDirection.IncomingRequest))
if (!string.IsNullOrEmpty(requestPath) && requestPath[0] == '/')
{
await _target.RouteAsync(context);
requestPath = requestPath.Substring(1);
}
var values = _matcher.Match(requestPath, Defaults);
if (values == null)
{
if (_logger.IsEnabled(TraceType.Information))
{
_logger.WriteValues(CreateRouteAsyncValues(
requestPath,
values,
matchedValues: false,
matchedConstraints: false,
handled: context.IsHandled));
}
// 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.RouteData.Values = values;
if (RouteConstraintMatcher.Match(Constraints,
values,
context.HttpContext,
this,
RouteDirection.IncomingRequest,
_constraintLogger))
{
await _target.RouteAsync(context);
if (_logger.IsEnabled(TraceType.Information))
{
_logger.WriteValues(CreateRouteAsyncValues(
requestPath,
values,
matchedValues: true,
matchedConstraints: true,
handled: context.IsHandled));
}
}
else
{
if (_logger.IsEnabled(TraceType.Information))
{
_logger.WriteValues(CreateRouteAsyncValues(
requestPath,
values,
matchedValues: true,
matchedConstraints: false,
handled: context.IsHandled));
}
}
}
}
}
@ -110,11 +154,13 @@ namespace Microsoft.AspNet.Routing.Template
return null;
}
EnsureLoggers(context.Context);
if (!RouteConstraintMatcher.Match(Constraints,
values.CombinedValues,
context.Context,
this,
RouteDirection.UrlGeneration))
RouteDirection.UrlGeneration,
_constraintLogger))
{
return null;
}
@ -208,5 +254,41 @@ namespace Microsoft.AspNet.Routing.Template
}
}
}
private TemplateRouteRouteAsyncValues CreateRouteAsyncValues(
string requestPath,
IDictionary<string, object> producedValues,
bool matchedValues,
bool matchedConstraints,
bool handled)
{
var values = new TemplateRouteRouteAsyncValues();
values.Template = _routeTemplate;
values.RequestPath = requestPath;
values.DefaultValues = Defaults;
values.ProducedValues = producedValues;
values.Constraints = _constraints;
values.Target = _target;
values.MatchedTemplate = matchedValues;
values.MatchedConstraints = matchedConstraints;
values.Matched = matchedValues && matchedConstraints;
values.Handled = handled;
return values;
}
private void EnsureLoggers(HttpContext context)
{
if (_logger == null)
{
var factory = context.RequestServices.GetService<ILoggerFactory>();
_logger = factory.Create<TemplateRoute>();
_constraintLogger = factory.Create(typeof(RouteConstraintMatcher).FullName);
}
}
public override string ToString()
{
return _routeTemplate;
}
}
}

View File

@ -5,8 +5,9 @@
},
"dependencies": {
"Microsoft.AspNet.Http": "1.0.0-*",
"Microsoft.Framework.DependencyInjection" : "1.0.0-*",
"Microsoft.AspNet.RequestContainer": "1.0.0-*",
"Microsoft.Framework.DependencyInjection" : "1.0.0-*",
"Microsoft.Framework.Logging": "1.0.0-*",
"Microsoft.Framework.OptionsModel": "1.0.0-*"
},
"frameworks": {

View File

@ -0,0 +1,313 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Http;
using Microsoft.AspNet.Routing.Logging;
#if NET45
using Moq;
#endif
using Xunit;
namespace Microsoft.AspNet.Routing
{
public class ConstraintMatcherTest
{
#if NET45
[Fact]
public void MatchUrlGeneration_DoesNotLogData()
{
// Arrange
var name = "name";
var sink = new TestSink();
var logger = new TestLogger(name, sink);
var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new PassConstraint()},
{"b", new FailConstraint()}
};
// Act
RouteConstraintMatcher.Match(
constraints: constraints,
routeValues: routeValueDictionary,
httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeDirection: RouteDirection.UrlGeneration,
logger: logger);
// Assert
// There are no BeginScopes called.
Assert.Equal(0, sink.Scopes.Count);
// There are no WriteCores called.
Assert.Equal(0, sink.Writes.Count);
}
[Fact]
public void MatchFail_LogsCorrectData()
{
// Arrange
var name = "name";
var sink = new TestSink();
var logger = new TestLogger(name, sink);
var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new PassConstraint()},
{"b", new FailConstraint()}
};
// Act
RouteConstraintMatcher.Match(
constraints: constraints,
routeValues: routeValueDictionary,
httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeDirection: RouteDirection.IncomingRequest,
logger: logger);
// Assert
// There are no begin scopes called.
Assert.Equal(0, sink.Scopes.Count);
// There are two records for IsEnabled and two for WriteCore.
Assert.Equal(4, sink.Writes.Count);
var enabled = sink.Writes[0];
Assert.Equal(name, enabled.LoggerName);
Assert.Null(enabled.State);
var write = sink.Writes[1];
Assert.Equal(name, write.LoggerName);
var values = Assert.IsType<RouteConstraintMatcherMatchValues>(write.State);
Assert.Equal("RouteConstraintMatcher.Match", values.Name);
Assert.Equal("a", values.ConstraintKey);
Assert.Equal(constraints["a"], values.Constraint);
Assert.Equal(true, values.Matched);
enabled = sink.Writes[2];
Assert.Equal(name, enabled.LoggerName);
Assert.Null(enabled.State);
write = sink.Writes[3];
Assert.Equal(name, write.LoggerName);
values = Assert.IsType<RouteConstraintMatcherMatchValues>(write.State);
Assert.Equal("RouteConstraintMatcher.Match", values.Name);
Assert.Equal("b", values.ConstraintKey);
Assert.Equal(constraints["b"], values.Constraint);
Assert.Equal(false, values.Matched);
}
[Fact]
public void MatchSuccess_LogsCorrectData()
{
// Arrange
var name = "name";
var sink = new TestSink();
var logger = new TestLogger(name, sink);
var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new PassConstraint()},
{"b", new PassConstraint()}
};
// Act
RouteConstraintMatcher.Match(
constraints: constraints,
routeValues: routeValueDictionary,
httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeDirection: RouteDirection.IncomingRequest,
logger: logger);
// Assert
// There are no begin scopes called.
Assert.Equal(0, sink.Scopes.Count);
// There are two records for IsEnabled and two for WriteCore.
Assert.Equal(4, sink.Writes.Count);
var enabled = sink.Writes[0];
Assert.Equal(name, enabled.LoggerName);
Assert.Null(enabled.State);
var write = sink.Writes[1];
Assert.Equal(name, write.LoggerName);
var values = Assert.IsType<RouteConstraintMatcherMatchValues>(write.State);
Assert.Equal("RouteConstraintMatcher.Match", values.Name);
Assert.Equal("a", values.ConstraintKey);
Assert.Equal(constraints["a"], values.Constraint);
Assert.Equal(true, values.Matched);
enabled = sink.Writes[2];
Assert.Equal(name, enabled.LoggerName);
Assert.Null(enabled.State);
write = sink.Writes[3];
Assert.Equal(name, write.LoggerName);
values = Assert.IsType<RouteConstraintMatcherMatchValues>(write.State);
Assert.Equal("RouteConstraintMatcher.Match", values.Name);
Assert.Equal("b", values.ConstraintKey);
Assert.Equal(constraints["b"], values.Constraint);
Assert.Equal(true, values.Matched);
}
[Fact]
public void ReturnsTrueOnValidConstraints()
{
var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new PassConstraint()},
{"b", new PassConstraint()}
};
var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
Assert.True(RouteConstraintMatcher.Match(
constraints: constraints,
routeValues: routeValueDictionary,
httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeDirection: RouteDirection.IncomingRequest,
logger: NullLogger.Instance));
}
[Fact]
public void ConstraintsGetTheRightKey()
{
var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new PassConstraint("a")},
{"b", new PassConstraint("b")}
};
var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
Assert.True(RouteConstraintMatcher.Match(
constraints: constraints,
routeValues: routeValueDictionary,
httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeDirection: RouteDirection.IncomingRequest,
logger: NullLogger.Instance));
}
[Fact]
public void ReturnsFalseOnInvalidConstraintsThatDontMatch()
{
var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new FailConstraint()},
{"b", new FailConstraint()}
};
var routeValueDictionary = new RouteValueDictionary(new { c = "value", d = "value" });
Assert.False(RouteConstraintMatcher.Match(
constraints: constraints,
routeValues: routeValueDictionary,
httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeDirection: RouteDirection.IncomingRequest,
logger: NullLogger.Instance));
}
[Fact]
public void ReturnsFalseOnInvalidConstraintsThatMatch()
{
var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new FailConstraint()},
{"b", new FailConstraint()}
};
var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
Assert.False(RouteConstraintMatcher.Match(
constraints: constraints,
routeValues: routeValueDictionary,
httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeDirection: RouteDirection.IncomingRequest,
logger: NullLogger.Instance));
}
[Fact]
public void ReturnsFalseOnValidAndInvalidConstraintsMixThatMatch()
{
var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new PassConstraint()},
{"b", new FailConstraint()}
};
var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
Assert.False(RouteConstraintMatcher.Match(
constraints: constraints,
routeValues: routeValueDictionary,
httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeDirection: RouteDirection.IncomingRequest,
logger: NullLogger.Instance));
}
[Fact]
public void ReturnsTrueOnNullInput()
{
Assert.True(RouteConstraintMatcher.Match(
constraints: null,
routeValues: new RouteValueDictionary(),
httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeDirection: RouteDirection.IncomingRequest,
logger: NullLogger.Instance));
}
#endif
private class PassConstraint : IRouteConstraint
{
private readonly string _expectedKey;
public PassConstraint(string expectedKey = null)
{
_expectedKey = expectedKey;
}
public bool Match(HttpContext httpContext,
IRouter route,
string routeKey,
IDictionary<string, object> values,
RouteDirection routeDirection)
{
if (_expectedKey != null)
{
Assert.Equal(_expectedKey, routeKey);
}
return true;
}
}
private class FailConstraint : IRouteConstraint
{
public bool Match(HttpContext httpContext,
IRouter route,
string routeKey,
IDictionary<string, object> values,
RouteDirection routeDirection)
{
return false;
}
}
}
}

View File

@ -1,157 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if NET45
using System.Collections.Generic;
using Microsoft.AspNet.Http;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Routing.Tests
{
public class ConstraintMatcherTests
{
[Fact]
public void ReturnsTrueOnValidConstraints()
{
var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new PassConstraint()},
{"b", new PassConstraint()}
};
var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
Assert.True(RouteConstraintMatcher.Match(
constraints: constraints,
routeValues: routeValueDictionary,
httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeDirection: RouteDirection.IncomingRequest));
}
[Fact]
public void ConstraintsGetTheRightKey()
{
var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new PassConstraint("a")},
{"b", new PassConstraint("b")}
};
var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
Assert.True(RouteConstraintMatcher.Match(
constraints: constraints,
routeValues: routeValueDictionary,
httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeDirection: RouteDirection.IncomingRequest));
}
[Fact]
public void ReturnsFalseOnInvalidConstraintsThatDontMatch()
{
var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new FailConstraint()},
{"b", new FailConstraint()}
};
var routeValueDictionary = new RouteValueDictionary(new { c = "value", d = "value" });
Assert.False(RouteConstraintMatcher.Match(
constraints: constraints,
routeValues: routeValueDictionary,
httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeDirection: RouteDirection.IncomingRequest));
}
[Fact]
public void ReturnsFalseOnInvalidConstraintsThatMatch()
{
var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new FailConstraint()},
{"b", new FailConstraint()}
};
var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
Assert.False(RouteConstraintMatcher.Match(
constraints: constraints,
routeValues: routeValueDictionary,
httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeDirection: RouteDirection.IncomingRequest));
}
[Fact]
public void ReturnsFalseOnValidAndInvalidConstraintsMixThatMatch()
{
var constraints = new Dictionary<string, IRouteConstraint>
{
{"a", new PassConstraint()},
{"b", new FailConstraint()}
};
var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
Assert.False(RouteConstraintMatcher.Match(
constraints: constraints,
routeValues: routeValueDictionary,
httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeDirection: RouteDirection.IncomingRequest));
}
[Fact]
public void ReturnsTrueOnNullInput()
{
Assert.True(RouteConstraintMatcher.Match(
constraints: null,
routeValues: new RouteValueDictionary(),
httpContext: new Mock<HttpContext>().Object,
route: new Mock<IRouter>().Object,
routeDirection: RouteDirection.IncomingRequest));
}
private class PassConstraint : IRouteConstraint
{
private readonly string _expectedKey;
public PassConstraint(string expectedKey = null)
{
_expectedKey = expectedKey;
}
public bool Match(HttpContext httpContext,
IRouter route,
string routeKey,
IDictionary<string, object> values,
RouteDirection routeDirection)
{
if (_expectedKey != null)
{
Assert.Equal(_expectedKey, routeKey);
}
return true;
}
}
private class FailConstraint : IRouteConstraint
{
public bool Match(HttpContext httpContext,
IRouter route,
string routeKey,
IDictionary<string, object> values,
RouteDirection routeDirection)
{
return false;
}
}
}
}
#endif

View File

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Routing
{
public class BeginScopeContext
{
public object Scope { get; set; }
public string LoggerName { get; set; }
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Routing
{
public class NullDisposable : IDisposable
{
public static NullDisposable Instance = new NullDisposable();
public void Dispose()
{
// intentionally does nothing
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Framework.Logging;
namespace Microsoft.AspNet.Routing
{
public class NullLogger : ILogger
{
public static NullLogger Instance = new NullLogger();
public IDisposable BeginScope(object state)
{
return NullDisposable.Instance;
}
public bool WriteCore(TraceType eventType, int eventId, object state, Exception exception, Func<object, Exception, string> formatter)
{
return false;
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Routing
{
public class NullLoggerFactory : ILoggerFactory
{
public static NullLoggerFactory Instance = new NullLoggerFactory();
public ILogger Create(string name)
{
return NullLogger.Instance;
}
}
}

View File

@ -0,0 +1,52 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Framework.Logging;
namespace Microsoft.AspNet.Routing
{
public class TestLogger : ILogger
{
private object _scope;
private TestSink _sink;
private string _name;
public TestLogger(string name, TestSink sink)
{
_sink = sink;
_name = name;
}
public string Name { get; set; }
public IDisposable BeginScope(object state)
{
_scope = state;
_sink.Begin(new BeginScopeContext()
{
LoggerName = _name,
Scope = state,
});
return NullDisposable.Instance;
}
public bool WriteCore(TraceType eventType, int eventId, object state, Exception exception, Func<object, Exception, string> formatter)
{
_sink.Write(new WriteCoreContext()
{
EventType = eventType,
EventId = eventId,
State = state,
Exception = exception,
Formatter = formatter,
LoggerName = _name,
Scope = _scope
});
return true;
}
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Routing
{
public class TestLoggerFactory : ILoggerFactory
{
private TestSink _sink;
public TestLoggerFactory(TestSink sink)
{
_sink = sink;
}
public ILogger Create(string name)
{
return new TestLogger(name, _sink);
}
}
}

View File

@ -0,0 +1,56 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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;
namespace Microsoft.AspNet.Routing
{
public class TestSink
{
public TestSink(
Func<WriteCoreContext, bool> writeEnabled = null,
Func<BeginScopeContext, bool> beginEnabled = null)
{
WriteEnabled = writeEnabled;
BeginEnabled = beginEnabled;
Scopes = new List<BeginScopeContext>();
Writes = new List<WriteCoreContext>();
}
public Func<WriteCoreContext, bool> WriteEnabled { get; set; }
public Func<BeginScopeContext, bool> BeginEnabled { get; set; }
public List<BeginScopeContext> Scopes { get; set; }
public List<WriteCoreContext> Writes { get; set; }
public void Write(WriteCoreContext context)
{
if (WriteEnabled == null || WriteEnabled(context))
{
Writes.Add(context);
}
}
public void Begin(BeginScopeContext context)
{
if (BeginEnabled == null || BeginEnabled(context))
{
Scopes.Add(context);
}
}
public static bool EnableWithTypeName<T>(WriteCoreContext context)
{
return context.LoggerName.Equals(typeof(T).FullName);
}
public static bool EnableWithTypeName<T>(BeginScopeContext context)
{
return context.LoggerName.Equals(typeof(T).FullName);
}
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.Framework.Logging;
namespace Microsoft.AspNet.Routing
{
public class WriteCoreContext
{
public TraceType EventType { get; set; }
public int EventId { get; set; }
public object State { get; set; }
public Exception Exception { get; set; }
public Func<object, Exception, string> Formatter { get; set; }
public object Scope { get; set; }
public string LoggerName { get; set; }
}
}

View File

@ -20,7 +20,7 @@
<Content Include="project.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="ConstraintMatcherTests.cs" />
<Compile Include="ConstraintMatcherTest.cs" />
<Compile Include="ConstraintsBuilderTests.cs" />
<Compile Include="Constraints\ConstraintsTestHelper.cs" />
<Compile Include="Constraints\BoolRouteConstraintTests.cs" />
@ -42,15 +42,24 @@
<Compile Include="Constraints\IntRouteConstraintsTests.cs" />
<Compile Include="Constraints\RequiredRouteConstraintTests.cs" />
<Compile Include="DefaultInlineConstraintResolverTest.cs" />
<Compile Include="Logging\BeginScopeContext.cs" />
<Compile Include="Logging\NullDisposable.cs" />
<Compile Include="Logging\NullLogger.cs" />
<Compile Include="Logging\NullLoggerFactory.cs" />
<Compile Include="Logging\TestLogger.cs" />
<Compile Include="Logging\TestLoggerFactory.cs" />
<Compile Include="Logging\TestSink.cs" />
<Compile Include="Logging\WriteCoreContext.cs" />
<Compile Include="RouteCollectionTest.cs" />
<Compile Include="InlineRouteParameterParserTests.cs" />
<Compile Include="RouteOptionsTests.cs" />
<Compile Include="RouterMiddlewareTest.cs" />
<Compile Include="RouteValueDictionaryTests.cs" />
<Compile Include="TemplateParserDefaultValuesTests.cs" />
<Compile Include="Template\TemplateBinderTests.cs" />
<Compile Include="Template\TemplateMatcherTests.cs" />
<Compile Include="Template\TemplateParserTests.cs" />
<Compile Include="Template\TemplateRouteTests.cs" />
<Compile Include="Template\TemplateRouteTest.cs" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -2,19 +2,102 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if NET45
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Logging;
using Microsoft.Framework.Logging;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Routing.Tests
namespace Microsoft.AspNet.Routing
{
public class RouteCollectionTest
{
[Fact]
public async Task RouteAsync_LogsCorrectValuesWhenHandled()
{
// Arrange
var sink = new TestSink(
TestSink.EnableWithTypeName<RouteCollection>,
TestSink.EnableWithTypeName<RouteCollection>);
var loggerFactory = new TestLoggerFactory(sink);
var routes = new RouteCollection();
var route = CreateRoute(accept: true);
routes.Add(route.Object);
var context = CreateRouteContext("/Cool", loggerFactory);
// Act
await routes.RouteAsync(context);
// Assert
Assert.Equal(1, sink.Scopes.Count);
var scope = sink.Scopes[0];
Assert.Equal(typeof(RouteCollection).FullName, scope.LoggerName);
Assert.Equal("RouteCollection.RouteAsync", scope.Scope);
// There is a record for IsEnabled and one for WriteCore.
Assert.Equal(2, sink.Writes.Count);
var enabled = sink.Writes[0];
Assert.Equal(typeof(RouteCollection).FullName, enabled.LoggerName);
Assert.Equal("RouteCollection.RouteAsync", enabled.Scope);
Assert.Null(enabled.State);
var write = sink.Writes[1];
Assert.Equal(typeof(RouteCollection).FullName, write.LoggerName);
Assert.Equal("RouteCollection.RouteAsync", write.Scope);
var values = Assert.IsType<RouteCollectionRouteAsyncValues>(write.State);
Assert.Equal("RouteCollection.RouteAsync", values.Name);
Assert.NotNull(values.Routes);
Assert.Equal(true, values.Handled);
}
[Fact]
public async Task RouteAsync_LogsCorrectValuesWhenNotHandled()
{
// Arrange
var sink = new TestSink(
TestSink.EnableWithTypeName<RouteCollection>,
TestSink.EnableWithTypeName<RouteCollection>);
var loggerFactory = new TestLoggerFactory(sink);
var routes = new RouteCollection();
var route = CreateRoute(accept: false);
routes.Add(route.Object);
var context = CreateRouteContext("/Cool", loggerFactory);
// Act
await routes.RouteAsync(context);
// Assert
Assert.Equal(1, sink.Scopes.Count);
var scope = sink.Scopes[0];
Assert.Equal(typeof(RouteCollection).FullName, scope.LoggerName);
Assert.Equal("RouteCollection.RouteAsync", scope.Scope);
// There is a record for IsEnabled and one for WriteCore.
Assert.Equal(2, sink.Writes.Count);
var enabled = sink.Writes[0];
Assert.Equal(typeof(RouteCollection).FullName, enabled.LoggerName);
Assert.Equal("RouteCollection.RouteAsync", enabled.Scope);
Assert.Null(enabled.State);
var write = sink.Writes[1];
Assert.Equal(typeof(RouteCollection).FullName, write.LoggerName);
Assert.Equal("RouteCollection.RouteAsync", write.Scope);
var values = Assert.IsType<RouteCollectionRouteAsyncValues>(write.State);
Assert.Equal("RouteCollection.RouteAsync", values.Name);
Assert.NotNull(values.Routes);
Assert.Equal(false, values.Handled);
}
[Fact]
public async Task RouteAsync_FirstMatches()
{
@ -214,12 +297,19 @@ namespace Microsoft.AspNet.Routing.Tests
return new VirtualPathContext(null, null, null, routeName);
}
private static RouteContext CreateRouteContext(string requestPath)
private static RouteContext CreateRouteContext(string requestPath, ILoggerFactory factory = null)
{
if (factory == null)
{
factory = NullLoggerFactory.Instance;
}
var request = new Mock<HttpRequest>(MockBehavior.Strict);
request.SetupGet(r => r.Path).Returns(new PathString(requestPath));
var context = new Mock<HttpContext>(MockBehavior.Strict);
context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
.Returns(factory);
context.SetupGet(c => c.Request).Returns(request.Object);
return new RouteContext(context.Object);
@ -244,5 +334,4 @@ namespace Microsoft.AspNet.Routing.Tests
}
}
}
#endif

View File

@ -0,0 +1,139 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Logging;
using Microsoft.Framework.Logging;
#if NET45
using Moq;
#endif
using Xunit;
namespace Microsoft.AspNet.Routing
{
public class RouterMiddlewareTest
{
#if NET45
[Fact]
public async void Invoke_LogsCorrectValuesWhenNotHandled()
{
// Arrange
var isHandled = false;
var sink = new TestSink(
TestSink.EnableWithTypeName<RouterMiddleware>,
TestSink.EnableWithTypeName<RouterMiddleware>);
var loggerFactory = new TestLoggerFactory(sink);
var mockContext = new Mock<HttpContext>(MockBehavior.Strict);
mockContext.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
.Returns(loggerFactory);
RequestDelegate next = (c) =>
{
return Task.FromResult<object>(null);
};
var router = new TestRouter(isHandled);
var middleware = new RouterMiddleware(next, router);
// Act
await middleware.Invoke(mockContext.Object);
// Assert
Assert.Equal(1, sink.Scopes.Count);
var scope = sink.Scopes[0];
Assert.Equal(typeof(RouterMiddleware).FullName, scope.LoggerName);
Assert.Equal("RouterMiddleware.Invoke", scope.Scope);
// There is a record for IsEnabled and one for WriteCore.
Assert.Equal(2, sink.Writes.Count);
var enabled = sink.Writes[0];
Assert.Equal(typeof(RouterMiddleware).FullName, enabled.LoggerName);
Assert.Equal("RouterMiddleware.Invoke", enabled.Scope);
Assert.Null(enabled.State);
var write = sink.Writes[1];
Assert.Equal(typeof(RouterMiddleware).FullName, write.LoggerName);
Assert.Equal("RouterMiddleware.Invoke", write.Scope);
var values = Assert.IsType<RouterMiddlewareInvokeValues>(write.State);
Assert.Equal("RouterMiddleware.Invoke", values.Name);
Assert.Equal(false, values.Handled);
}
[Fact]
public async void Invoke_LogsCorrectValuesWhenHandled()
{
// Arrange
var isHandled = true;
var sink = new TestSink(
TestSink.EnableWithTypeName<RouterMiddleware>,
TestSink.EnableWithTypeName<RouterMiddleware>);
var loggerFactory = new TestLoggerFactory(sink);
var mockContext = new Mock<HttpContext>(MockBehavior.Strict);
mockContext.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
.Returns(loggerFactory);
RequestDelegate next = (c) =>
{
return Task.FromResult<object>(null);
};
var router = new TestRouter(isHandled);
var middleware = new RouterMiddleware(next, router);
// Act
await middleware.Invoke(mockContext.Object);
// Assert
// exists a BeginScope, verify contents
Assert.Equal(1, sink.Scopes.Count);
var scope = sink.Scopes[0];
Assert.Equal(typeof(RouterMiddleware).FullName, scope.LoggerName);
Assert.Equal("RouterMiddleware.Invoke", scope.Scope);
// There is a record for IsEnabled and one for WriteCore.
Assert.Equal(2, sink.Writes.Count);
var enabled = sink.Writes[0];
Assert.Equal(typeof(RouterMiddleware).FullName, enabled.LoggerName);
Assert.Equal("RouterMiddleware.Invoke", enabled.Scope);
Assert.Null(enabled.State);
var write = sink.Writes[1];
Assert.Equal(typeof(RouterMiddleware).FullName, write.LoggerName);
Assert.Equal("RouterMiddleware.Invoke", write.Scope);
Assert.Equal(typeof(RouterMiddlewareInvokeValues), write.State.GetType());
var values = (RouterMiddlewareInvokeValues)write.State;
Assert.Equal("RouterMiddleware.Invoke", values.Name);
Assert.Equal(true, values.Handled);
}
#endif
private class TestRouter : IRouter
{
private bool _isHandled;
public TestRouter(bool isHandled)
{
_isHandled = isHandled;
}
public string GetVirtualPath(VirtualPathContext context)
{
return "";
}
public Task RouteAsync(RouteContext context)
{
context.IsHandled = _isHandled;
return Task.FromResult<object>(null);
}
}
}
}

View File

@ -8,17 +8,158 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Constraints;
using Microsoft.AspNet.Routing.Logging;
using Microsoft.AspNet.Testing;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Routing.Template.Tests
namespace Microsoft.AspNet.Routing.Template
{
public class TemplateRouteTests
public class TemplateRouteTest
{
private static IInlineConstraintResolver _inlineConstraintResolver = GetInlineConstraintResolver();
[Fact]
public async Task RouteAsync_MatchSuccess_LogsCorrectValues()
{
// Arrange
var sink = new TestSink(
TestSink.EnableWithTypeName<TemplateRoute>,
TestSink.EnableWithTypeName<TemplateRoute>);
var loggerFactory = new TestLoggerFactory(sink);
var template = "{controller}/{action}";
var route = CreateRoute(template);
var context = CreateRouteContext("/Home/Index", loggerFactory);
// Act
await route.RouteAsync(context);
// Assert
Assert.Equal(1, sink.Scopes.Count);
var scope = sink.Scopes[0];
Assert.Equal(typeof(TemplateRoute).FullName, scope.LoggerName);
Assert.Equal("TemplateRoute.RouteAsync", scope.Scope);
// There is a record for IsEnabled and one for WriteCore.
Assert.Equal(2, sink.Writes.Count);
var enabled = sink.Writes[0];
Assert.Equal(typeof(TemplateRoute).FullName, enabled.LoggerName);
Assert.Equal("TemplateRoute.RouteAsync", enabled.Scope);
Assert.Null(enabled.State);
var write = sink.Writes[1];
Assert.Equal(typeof(TemplateRoute).FullName, write.LoggerName);
Assert.Equal("TemplateRoute.RouteAsync", write.Scope);
// verify WriteCore state contents
var values = Assert.IsType<TemplateRouteRouteAsyncValues>(write.State);
Assert.Equal("TemplateRoute.RouteAsync", values.Name);
Assert.Equal("Home/Index", values.RequestPath);
Assert.Equal(template, values.Template);
Assert.NotNull(values.DefaultValues);
Assert.NotNull(values.ProducedValues);
Assert.Equal(true, values.MatchedTemplate);
Assert.Equal(true, values.MatchedConstraints);
Assert.Equal(true, values.Matched);
Assert.Equal(context.IsHandled, values.Handled);
}
[Fact]
public async Task RouteAsync_MatchFailOnValues_LogsCorrectValues()
{
// Arrange
var sink = new TestSink(
TestSink.EnableWithTypeName<TemplateRoute>,
TestSink.EnableWithTypeName<TemplateRoute>);
var loggerFactory = new TestLoggerFactory(sink);
var template = "{controller}/{action}";
var route = CreateRoute(template);
var context = CreateRouteContext("/Home/Index/Failure", loggerFactory);
// Act
await route.RouteAsync(context);
// Assert
Assert.Equal(1, sink.Scopes.Count);
var scope = sink.Scopes[0];
Assert.Equal(typeof(TemplateRoute).FullName, scope.LoggerName);
Assert.Equal("TemplateRoute.RouteAsync", scope.Scope);
// There is a record for IsEnabled and one for WriteCore.
Assert.Equal(2, sink.Writes.Count);
var enabled = sink.Writes[0];
Assert.Equal(typeof(TemplateRoute).FullName, enabled.LoggerName);
Assert.Equal("TemplateRoute.RouteAsync", enabled.Scope);
Assert.Null(enabled.State);
var write = sink.Writes[1];
Assert.Equal(typeof(TemplateRoute).FullName, write.LoggerName);
Assert.Equal("TemplateRoute.RouteAsync", write.Scope);
var values = Assert.IsType<TemplateRouteRouteAsyncValues>(write.State);
Assert.Equal("TemplateRoute.RouteAsync", values.Name);
Assert.Equal("Home/Index/Failure", values.RequestPath);
Assert.Equal(template, values.Template);
Assert.NotNull(values.DefaultValues);
Assert.Null(values.ProducedValues);
Assert.Equal(false, values.MatchedTemplate);
Assert.Equal(false, values.MatchedConstraints);
Assert.Equal(false, values.Matched);
Assert.Equal(context.IsHandled, values.Handled);
}
[Fact]
public async Task RouteAsync_MatchFailOnConstraints_LogsCorrectValues()
{
// Arrange
var sink = new TestSink(
TestSink.EnableWithTypeName<TemplateRoute>,
TestSink.EnableWithTypeName<TemplateRoute>);
var loggerFactory = new TestLoggerFactory(sink);
var template = "{controller}/{action}/{id:int}";
var route = CreateRoute(template);
var context = CreateRouteContext("/Home/Index/Failure", loggerFactory);
// Act
await route.RouteAsync(context);
// Assert
Assert.Equal(1, sink.Scopes.Count);
var scope = sink.Scopes[0];
Assert.Equal(typeof(TemplateRoute).FullName, scope.LoggerName);
Assert.Equal("TemplateRoute.RouteAsync", scope.Scope);
// There is a record for IsEnabled and one for WriteCore.
Assert.Equal(2, sink.Writes.Count);
var enabled = sink.Writes[0];
Assert.Equal(typeof(TemplateRoute).FullName, enabled.LoggerName);
Assert.Equal("TemplateRoute.RouteAsync", enabled.Scope);
Assert.Null(enabled.State);
var write = sink.Writes[1];
Assert.Equal(typeof(TemplateRoute).FullName, write.LoggerName);
Assert.Equal("TemplateRoute.RouteAsync", write.Scope);
var values = Assert.IsType<TemplateRouteRouteAsyncValues>(write.State);
Assert.Equal("TemplateRoute.RouteAsync", values.Name);
Assert.Equal("Home/Index/Failure", values.RequestPath);
Assert.Equal(template, values.Template);
Assert.NotNull(values.DefaultValues);
Assert.NotNull(values.ProducedValues);
Assert.Equal(true, values.MatchedTemplate);
Assert.Equal(false, values.MatchedConstraints);
Assert.Equal(false, values.Matched);
Assert.Equal(context.IsHandled, values.Handled);
}
#region Route Matching
// PathString in HttpAbstractions guarantees a leading slash - so no value in testing other cases.
@ -116,20 +257,26 @@ namespace Microsoft.AspNet.Routing.Template.Tests
Assert.Null(context.RouteData.Values["1controller"]);
}
private static RouteContext CreateRouteContext(string requestPath)
private static RouteContext CreateRouteContext(string requestPath, ILoggerFactory factory = null)
{
if (factory == null)
{
factory = NullLoggerFactory.Instance;
}
var request = new Mock<HttpRequest>(MockBehavior.Strict);
request.SetupGet(r => r.Path).Returns(new PathString(requestPath));
var context = new Mock<HttpContext>(MockBehavior.Strict);
context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
.Returns(factory);
context.SetupGet(c => c.Request).Returns(request.Object);
return new RouteContext(context.Object);
}
#endregion
#region Route Binding
#region Route Binding
[Fact]
public void GetVirtualPath_Success()
@ -502,6 +649,8 @@ namespace Microsoft.AspNet.Routing.Template.Tests
private static VirtualPathContext CreateVirtualPathContext(IDictionary<string, object> values, IDictionary<string, object> ambientValues)
{
var context = new Mock<HttpContext>(MockBehavior.Strict);
context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
.Returns(NullLoggerFactory.Instance);
return new VirtualPathContext(context.Object, ambientValues, values);
}
@ -712,5 +861,4 @@ namespace Microsoft.AspNet.Routing.Template.Tests
}
}
}
#endif

View File

@ -6,6 +6,7 @@
"Microsoft.AspNet.Http" : "1.0.0-*",
"Microsoft.AspNet.Routing" : "",
"Microsoft.AspNet.Testing" : "1.0.0-*",
"Microsoft.Framework.Logging" : "1.0.0-*",
"Xunit.KRunner": "1.0.0-*"
},
"frameworks": {