diff --git a/src/Microsoft.AspNet.Abstractions/Extensions/MapExtensions.cs b/src/Microsoft.AspNet.Abstractions/Extensions/MapExtensions.cs new file mode 100644 index 0000000000..72aa61bc97 --- /dev/null +++ b/src/Microsoft.AspNet.Abstractions/Extensions/MapExtensions.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Abstractions.Extensions; + +namespace Microsoft.AspNet +{ + public static class MapExtensions + { + /// + /// If the request path starts with the given pathMatch, execute the app configured via configuration parameter instead of + /// continuing to the next component in the pipeline. + /// + /// + /// The path to match + /// The branch to take for positive path matches + /// + public static IBuilder Map([NotNull] this IBuilder app, [NotNull] string pathMatch, [NotNull] Action configuration) + { + return Map(app, new PathString(pathMatch), configuration); + } + + /// + /// If the request path starts with the given pathMatch, execute the app configured via configuration parameter instead of + /// continuing to the next component in the pipeline. + /// + /// + /// The path to match + /// The branch to take for positive path matches + /// + public static IBuilder Map([NotNull] this IBuilder app, PathString pathMatch, [NotNull] Action configuration) + { + if (pathMatch.HasValue && pathMatch.Value.EndsWith("/", StringComparison.Ordinal)) + { + throw new ArgumentException("The path must not end with a '/'", "pathMatch"); + } + + // create branch + IBuilder branchBuilder = app.New(); + configuration(branchBuilder); + var branch = branchBuilder.Build(); + + var options = new MapOptions() + { + Branch = branch, + PathMatch = pathMatch, + }; + return app.Use(next => new MapMiddleware(next, options).Invoke); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Abstractions/Extensions/MapMiddleware.cs b/src/Microsoft.AspNet.Abstractions/Extensions/MapMiddleware.cs new file mode 100644 index 0000000000..9a2905ab1b --- /dev/null +++ b/src/Microsoft.AspNet.Abstractions/Extensions/MapMiddleware.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Abstractions.Extensions +{ + public class MapMiddleware + { + private readonly RequestDelegate _next; + private readonly MapOptions _options; + + public MapMiddleware([NotNull] RequestDelegate next, [NotNull] MapOptions options) + { + _next = next; + _options = options; + } + + public async Task Invoke([NotNull] HttpContext context) + { + PathString path = context.Request.Path; + PathString remainingPath; + if (path.StartsWithSegments(_options.PathMatch, out remainingPath)) + { + // Update the path + PathString pathBase = context.Request.PathBase; + context.Request.PathBase = pathBase + _options.PathMatch; + context.Request.Path = remainingPath; + + await _options.Branch(context); + + context.Request.PathBase = pathBase; + context.Request.Path = path; + } + else + { + await _next(context); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Abstractions/Extensions/MapOptions.cs b/src/Microsoft.AspNet.Abstractions/Extensions/MapOptions.cs new file mode 100644 index 0000000000..43b479cbc4 --- /dev/null +++ b/src/Microsoft.AspNet.Abstractions/Extensions/MapOptions.cs @@ -0,0 +1,19 @@ + +namespace Microsoft.AspNet.Abstractions.Extensions +{ + /// + /// Options for the Map middleware + /// + public class MapOptions + { + /// + /// The path to match + /// + public PathString PathMatch { get; set; } + + /// + /// The branch taken for a positive match + /// + public RequestDelegate Branch { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Abstractions/Extensions/MapWhenExtensions.cs b/src/Microsoft.AspNet.Abstractions/Extensions/MapWhenExtensions.cs new file mode 100644 index 0000000000..aaad90ae74 --- /dev/null +++ b/src/Microsoft.AspNet.Abstractions/Extensions/MapWhenExtensions.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNet.Abstractions; +using Microsoft.AspNet.Abstractions.Extensions; + +namespace Microsoft.AspNet +{ + using Predicate = Func; + using PredicateAsync = Func>; + + /// + /// Extension methods for the MapWhenMiddleware + /// + public static class MapWhenExtensions + { + /// + /// Branches the request pipeline based on the result of the given predicate. + /// + /// + /// Invoked with the request environment to determine if the branch should be taken + /// Configures a branch to take + /// + public static IBuilder MapWhen([NotNull] this IBuilder app, [NotNull] Predicate predicate, [NotNull] Action configuration) + { + // create branch + IBuilder branchBuilder = app.New(); + configuration(branchBuilder); + var branch = branchBuilder.Build(); + + // put middleware in pipeline + var options = new MapWhenOptions + { + Predicate = predicate, + Branch = branch, + }; + return app.Use(next => new MapWhenMiddleware(next, options).Invoke); + } + + /// + /// Branches the request pipeline based on the async result of the given predicate. + /// + /// + /// Invoked asynchronously with the request environment to determine if the branch should be taken + /// Configures a branch to take + /// + public static IBuilder MapWhenAsync([NotNull] this IBuilder app, [NotNull] PredicateAsync predicate, [NotNull] Action configuration) + { + // create branch + IBuilder branchBuilder = app.New(); + configuration(branchBuilder); + var branch = branchBuilder.Build(); + + // put middleware in pipeline + var options = new MapWhenOptions + { + PredicateAsync = predicate, + Branch = branch, + }; + return app.Use(next => new MapWhenMiddleware(next, options).Invoke); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Abstractions/Extensions/MapWhenMiddleware.cs b/src/Microsoft.AspNet.Abstractions/Extensions/MapWhenMiddleware.cs new file mode 100644 index 0000000000..597fec36b0 --- /dev/null +++ b/src/Microsoft.AspNet.Abstractions/Extensions/MapWhenMiddleware.cs @@ -0,0 +1,42 @@ +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Abstractions.Extensions +{ + public class MapWhenMiddleware + { + private readonly RequestDelegate _next; + private readonly MapWhenOptions _options; + + public MapWhenMiddleware([NotNull] RequestDelegate next, [NotNull] MapWhenOptions options) + { + _next = next; + _options = options; + } + + public async Task Invoke([NotNull] HttpContext context) + { + if (_options.Predicate != null) + { + if (_options.Predicate(context)) + { + await _options.Branch(context); + } + else + { + await _next(context); + } + } + else + { + if (await _options.PredicateAsync(context)) + { + await _options.Branch(context); + } + else + { + await _next(context); + } + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Abstractions/Extensions/MapWhenOptions.cs b/src/Microsoft.AspNet.Abstractions/Extensions/MapWhenOptions.cs new file mode 100644 index 0000000000..380e69ba57 --- /dev/null +++ b/src/Microsoft.AspNet.Abstractions/Extensions/MapWhenOptions.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Abstractions.Extensions +{ + /// + /// Options for the MapWhen middleware + /// + public class MapWhenOptions + { + /// + /// The user callback that determines if the branch should be taken + /// + public Func Predicate { get; set; } + + /// + /// The async user callback that determines if the branch should be taken + /// + public Func> PredicateAsync { get; set; } + + /// + /// The branch taken for a positive match + /// + public RequestDelegate Branch { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Abstractions/Extensions/RunExtensions.cs b/src/Microsoft.AspNet.Abstractions/Extensions/RunExtensions.cs new file mode 100644 index 0000000000..b03f4d288f --- /dev/null +++ b/src/Microsoft.AspNet.Abstractions/Extensions/RunExtensions.cs @@ -0,0 +1,13 @@ +using System; +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet +{ + public static class RunExtensions + { + public static void Run([NotNull] this IBuilder app, [NotNull] RequestDelegate handler) + { + app.Use(_ => handler); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Abstractions/IBuilder.cs b/src/Microsoft.AspNet.Abstractions/IBuilder.cs index d187c75ec2..13936e6773 100644 --- a/src/Microsoft.AspNet.Abstractions/IBuilder.cs +++ b/src/Microsoft.AspNet.Abstractions/IBuilder.cs @@ -8,7 +8,6 @@ namespace Microsoft.AspNet.Abstractions IServerInformation Server { get; set; } IBuilder Use(Func middleware); - IBuilder Run(RequestDelegate handler); IBuilder New(); RequestDelegate Build(); diff --git a/src/Microsoft.AspNet.Abstractions/Microsoft.AspNet.Abstractions.kproj b/src/Microsoft.AspNet.Abstractions/Microsoft.AspNet.Abstractions.kproj index 12a6a518dc..83c250addc 100644 --- a/src/Microsoft.AspNet.Abstractions/Microsoft.AspNet.Abstractions.kproj +++ b/src/Microsoft.AspNet.Abstractions/Microsoft.AspNet.Abstractions.kproj @@ -21,6 +21,13 @@ + + + + + + + @@ -31,6 +38,7 @@ + diff --git a/src/Microsoft.AspNet.Abstractions/NotNullAttribute.cs b/src/Microsoft.AspNet.Abstractions/NotNullAttribute.cs new file mode 100644 index 0000000000..4a342de834 --- /dev/null +++ b/src/Microsoft.AspNet.Abstractions/NotNullAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Microsoft.AspNet.Abstractions +{ + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] + internal sealed class NotNullAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.PipelineCore/Builder.cs b/src/Microsoft.AspNet.PipelineCore/Builder.cs index e5e4696118..7f4093b7bc 100644 --- a/src/Microsoft.AspNet.PipelineCore/Builder.cs +++ b/src/Microsoft.AspNet.PipelineCore/Builder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNet.Abstractions; namespace Microsoft.AspNet.PipelineCore @@ -29,11 +30,6 @@ namespace Microsoft.AspNet.PipelineCore return this; } - public IBuilder Run(RequestDelegate handler) - { - return Use(next => handler); - } - public IBuilder New() { return new Builder(this); @@ -41,7 +37,11 @@ namespace Microsoft.AspNet.PipelineCore public RequestDelegate Build() { - RequestDelegate app = async context => context.Response.StatusCode = 404; + RequestDelegate app = context => + { + context.Response.StatusCode = 404; + return Task.FromResult(0); + }; foreach (var component in _components.Reverse()) { diff --git a/test/Microsoft.AspNet.Abstractions.Tests/Fakes.cs b/test/Microsoft.AspNet.Abstractions.Tests/Fakes.cs new file mode 100644 index 0000000000..0d4553e08a --- /dev/null +++ b/test/Microsoft.AspNet.Abstractions.Tests/Fakes.cs @@ -0,0 +1,32 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.AspNet.HttpFeature; + +namespace Microsoft.AspNet.Abstractions.Extensions +{ + public class FakeHttpRequestInfo : IHttpRequestInformation + { + public string Protocol { get; set; } + public string Scheme { get; set; } + public string Method { get; set; } + public string PathBase { get; set; } + public string Path { get; set; } + public string QueryString { get; set; } + public IDictionary Headers { get; set; } + public Stream Body { get; set; } + } + + public class FakeHttpResponseInfo : IHttpResponseInformation + { + public int StatusCode { get; set; } + public string ReasonPhrase { get; set; } + public IDictionary Headers { get; set; } + public Stream Body { get; set; } + public void OnSendingHeaders(Action callback, object state) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Abstractions.Tests/MapPathMiddlewareTests.cs b/test/Microsoft.AspNet.Abstractions.Tests/MapPathMiddlewareTests.cs new file mode 100644 index 0000000000..c6f8912747 --- /dev/null +++ b/test/Microsoft.AspNet.Abstractions.Tests/MapPathMiddlewareTests.cs @@ -0,0 +1,196 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNet.HttpFeature; +using Microsoft.AspNet.PipelineCore; +using Shouldly; +using Xunit; + +namespace Microsoft.AspNet.Abstractions.Extensions +{ + public class MapPathMiddlewareTests + { + private static readonly Action ActionNotImplemented = new Action(_ => { throw new NotImplementedException(); }); + + private static Task Success(HttpContext context) + { + context.Response.StatusCode = 200; + context.Items["test.PathBase"] = context.Request.PathBase.Value; + context.Items["test.Path"] = context.Request.Path.Value; + return Task.FromResult(null); + } + + private static void UseSuccess(IBuilder app) + { + app.Run(Success); + } + + private static Task NotImplemented(HttpContext context) + { + throw new NotImplementedException(); + } + + private static void UseNotImplemented(IBuilder app) + { + app.Run(NotImplemented); + } + + [Fact] + public void NullArguments_ArgumentNullException() + { + var builder = new Builder(serviceProvider: null); + var noMiddleware = new Builder(serviceProvider: null).Build(); + var noOptions = new MapOptions(); + // TODO: [NotNull] Assert.Throws(() => builder.Map(null, ActionNotImplemented)); + // TODO: [NotNull] Assert.Throws(() => builder.Map("/foo", (Action)null)); + // TODO: [NotNull] Assert.Throws(() => new MapMiddleware(null, noOptions)); + // TODO: [NotNull] Assert.Throws(() => new MapMiddleware(noMiddleware, null)); + } + + [Theory] + [InlineData("/foo", "", "/foo")] + [InlineData("/foo", "", "/foo/")] + [InlineData("/foo", "/Bar", "/foo")] + [InlineData("/foo", "/Bar", "/foo/cho")] + [InlineData("/foo", "/Bar", "/foo/cho/")] + [InlineData("/foo/cho", "/Bar", "/foo/cho")] + [InlineData("/foo/cho", "/Bar", "/foo/cho/do")] + public void PathMatchFunc_BranchTaken(string matchPath, string basePath, string requestPath) + { + HttpContext context = CreateRequest(basePath, requestPath); + IBuilder builder = new Builder(serviceProvider: null); + builder.Map(matchPath, UseSuccess); + var app = builder.Build(); + app.Invoke(context).Wait(); + + Assert.Equal(200, context.Response.StatusCode); + Assert.Equal(basePath, context.Request.PathBase.Value); + Assert.Equal(requestPath, context.Request.Path.Value); + } + + [Theory] + [InlineData("/foo", "", "/foo")] + [InlineData("/foo", "", "/foo/")] + [InlineData("/foo", "/Bar", "/foo")] + [InlineData("/foo", "/Bar", "/foo/cho")] + [InlineData("/foo", "/Bar", "/foo/cho/")] + [InlineData("/foo/cho", "/Bar", "/foo/cho")] + [InlineData("/foo/cho", "/Bar", "/foo/cho/do")] + public void PathMatchAction_BranchTaken(string matchPath, string basePath, string requestPath) + { + HttpContext context = CreateRequest(basePath, requestPath); + IBuilder builder = new Builder(serviceProvider: null); + builder.Map(matchPath, subBuilder => subBuilder.Run(Success)); + var app = builder.Build(); + app.Invoke(context).Wait(); + + Assert.Equal(200, context.Response.StatusCode); + Assert.Equal(basePath + matchPath, context.Items["test.PathBase"]); + Assert.Equal(requestPath.Substring(matchPath.Length), context.Items["test.Path"]); + } + + [Theory] + [InlineData("/")] + [InlineData("/foo/")] + [InlineData("/foo/cho/")] + public void MatchPathWithTrailingSlashThrowsException(string matchPath) + { + Should.Throw(() => new Builder(serviceProvider: null).Map(matchPath, map => { }).Build()); + } + + [Theory] + [InlineData("/foo", "", "")] + [InlineData("/foo", "/bar", "")] + [InlineData("/foo", "", "/bar")] + [InlineData("/foo", "/foo", "")] + [InlineData("/foo", "/foo", "/bar")] + [InlineData("/foo", "", "/bar/foo")] + [InlineData("/foo/bar", "/foo", "/bar")] + public void PathMismatchFunc_PassedThrough(string matchPath, string basePath, string requestPath) + { + HttpContext context = CreateRequest(basePath, requestPath); + IBuilder builder = new Builder(serviceProvider: null); + builder.Map(matchPath, UseNotImplemented); + builder.Run(Success); + var app = builder.Build(); + app.Invoke(context).Wait(); + + Assert.Equal(200, context.Response.StatusCode); + Assert.Equal(basePath, context.Request.PathBase.Value); + Assert.Equal(requestPath, context.Request.Path.Value); + } + + [Theory] + [InlineData("/foo", "", "")] + [InlineData("/foo", "/bar", "")] + [InlineData("/foo", "", "/bar")] + [InlineData("/foo", "/foo", "")] + [InlineData("/foo", "/foo", "/bar")] + [InlineData("/foo", "", "/bar/foo")] + [InlineData("/foo/bar", "/foo", "/bar")] + public void PathMismatchAction_PassedThrough(string matchPath, string basePath, string requestPath) + { + HttpContext context = CreateRequest(basePath, requestPath); + IBuilder builder = new Builder(serviceProvider: null); + builder.Map(matchPath, UseNotImplemented); + builder.Run(Success); + var app = builder.Build(); + app.Invoke(context).Wait(); + + Assert.Equal(200, context.Response.StatusCode); + Assert.Equal(basePath, context.Request.PathBase.Value); + Assert.Equal(requestPath, context.Request.Path.Value); + } + + [Fact] + public void ChainedRoutes_Success() + { + IBuilder builder = new Builder(serviceProvider: null); + builder.Map("/route1", map => + { + map.Map((string)"/subroute1", UseSuccess); + map.Run(NotImplemented); + }); + builder.Map("/route2/subroute2", UseSuccess); + var app = builder.Build(); + + HttpContext context = CreateRequest(string.Empty, "/route1"); + Assert.Throws(() => app.Invoke(context).Wait()); + + context = CreateRequest(string.Empty, "/route1/subroute1"); + app.Invoke(context).Wait(); + Assert.Equal(200, context.Response.StatusCode); + Assert.Equal(string.Empty, context.Request.PathBase.Value); + Assert.Equal("/route1/subroute1", context.Request.Path.Value); + + context = CreateRequest(string.Empty, "/route2"); + app.Invoke(context).Wait(); + Assert.Equal(404, context.Response.StatusCode); + Assert.Equal(string.Empty, context.Request.PathBase.Value); + Assert.Equal("/route2", context.Request.Path.Value); + + context = CreateRequest(string.Empty, "/route2/subroute2"); + app.Invoke(context).Wait(); + Assert.Equal(200, context.Response.StatusCode); + Assert.Equal(string.Empty, context.Request.PathBase.Value); + Assert.Equal("/route2/subroute2", context.Request.Path.Value); + + context = CreateRequest(string.Empty, "/route2/subroute2/subsub2"); + app.Invoke(context).Wait(); + Assert.Equal(200, context.Response.StatusCode); + Assert.Equal(string.Empty, context.Request.PathBase.Value); + Assert.Equal("/route2/subroute2/subsub2", context.Request.Path.Value); + } + + private HttpContext CreateRequest(string basePath, string requestPath) + { + HttpContext context = new DefaultHttpContext(new FeatureModel.FeatureCollection()); + context.SetFeature(new FakeHttpRequestInfo()); + context.SetFeature(new FakeHttpResponseInfo()); + context.Request.PathBase = new PathString(basePath); + context.Request.Path = new PathString(requestPath); + return context; + } + } +} diff --git a/test/Microsoft.AspNet.Abstractions.Tests/MapPredicateMiddlewareTests.cs b/test/Microsoft.AspNet.Abstractions.Tests/MapPredicateMiddlewareTests.cs new file mode 100644 index 0000000000..689d30ab76 --- /dev/null +++ b/test/Microsoft.AspNet.Abstractions.Tests/MapPredicateMiddlewareTests.cs @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNet.HttpFeature; +using Microsoft.AspNet.PipelineCore; +using Xunit; + +namespace Microsoft.AspNet.Abstractions.Extensions +{ + using AppFunc = Func, Task>; + using Predicate = Func; + using PredicateAsync = Func>; + + public class MapPredicateMiddlewareTests + { + private static readonly Predicate NotImplementedPredicate = new Predicate(envionment => { throw new NotImplementedException(); }); + private static readonly PredicateAsync NotImplementedPredicateAsync = new PredicateAsync(envionment => { throw new NotImplementedException(); }); + + private static Task Success(HttpContext context) + { + context.Response.StatusCode = 200; + return Task.FromResult(null); + } + + private static void UseSuccess(IBuilder app) + { + app.Run(Success); + } + + private static Task NotImplemented(HttpContext context) + { + throw new NotImplementedException(); + } + + private static void UseNotImplemented(IBuilder app) + { + app.Run(NotImplemented); + } + + private bool TruePredicate(HttpContext context) + { + return true; + } + + private bool FalsePredicate(HttpContext context) + { + return false; + } + + private Task TruePredicateAsync(HttpContext context) + { + return Task.FromResult(true); + } + + private Task FalsePredicateAsync(HttpContext context) + { + return Task.FromResult(false); + } + + [Fact] + public void NullArguments_ArgumentNullException() + { + var builder = new Builder(serviceProvider: null); + var noMiddleware = new Builder(serviceProvider: null).Build(); + var noOptions = new MapWhenOptions(); + // TODO: [NotNull] Assert.Throws(() => builder.MapWhen(null, UseNotImplemented)); + // TODO: [NotNull] Assert.Throws(() => builder.MapWhen(NotImplementedPredicate, (Action)null)); + // TODO: [NotNull] Assert.Throws(() => new MapWhenMiddleware(null, noOptions)); + // TODO: [NotNull] Assert.Throws(() => new MapWhenMiddleware(noMiddleware, null)); + + // TODO: [NotNull] Assert.Throws(() => builder.MapWhenAsync(null, UseNotImplemented)); + // TODO: [NotNull] Assert.Throws(() => builder.MapWhenAsync(NotImplementedPredicateAsync, (Action)null)); + // TODO: [NotNull] Assert.Throws(() => new MapWhenMiddleware(null, noOptions)); + // TODO: [NotNull] Assert.Throws(() => new MapWhenMiddleware(noMiddleware, null)); + } + + [Fact] + public void PredicateTrue_BranchTaken() + { + HttpContext context = CreateRequest(); + IBuilder builder = new Builder(serviceProvider: null); + builder.MapWhen(TruePredicate, UseSuccess); + var app = builder.Build(); + app.Invoke(context).Wait(); + + Assert.Equal(200, context.Response.StatusCode); + } + + [Fact] + public void PredicateTrueAction_BranchTaken() + { + HttpContext context = CreateRequest(); + IBuilder builder = new Builder(serviceProvider: null); + builder.MapWhen(TruePredicate, UseSuccess); + var app = builder.Build(); + app.Invoke(context).Wait(); + + Assert.Equal(200, context.Response.StatusCode); + } + + [Fact] + public void PredicateFalseAction_PassThrough() + { + HttpContext context = CreateRequest(); + IBuilder builder = new Builder(serviceProvider: null); + builder.MapWhen(FalsePredicate, UseNotImplemented); + builder.Run(Success); + var app = builder.Build(); + app.Invoke(context).Wait(); + + Assert.Equal(200, context.Response.StatusCode); + } + + [Fact] + public void PredicateAsyncTrueAction_BranchTaken() + { + HttpContext context = CreateRequest(); + IBuilder builder = new Builder(serviceProvider: null); + builder.MapWhenAsync(TruePredicateAsync, UseSuccess); + var app = builder.Build(); + app.Invoke(context).Wait(); + + Assert.Equal(200, context.Response.StatusCode); + } + + [Fact] + public void PredicateAsyncFalseAction_PassThrough() + { + HttpContext context = CreateRequest(); + IBuilder builder = new Builder(serviceProvider: null); + builder.MapWhenAsync(FalsePredicateAsync, UseNotImplemented); + builder.Run(Success); + var app = builder.Build(); + app.Invoke(context).Wait(); + + Assert.Equal(200, context.Response.StatusCode); + } + + [Fact] + public void ChainedPredicates_Success() + { + IBuilder builder = new Builder(serviceProvider: null); + builder.MapWhen(TruePredicate, map1 => + { + map1.MapWhen((Predicate)FalsePredicate, UseNotImplemented); + map1.MapWhen((Predicate)TruePredicate, map2 => map2.MapWhen((Predicate)TruePredicate, UseSuccess)); + map1.Run(NotImplemented); + }); + var app = builder.Build(); + + HttpContext context = CreateRequest(); + app.Invoke(context).Wait(); + Assert.Equal(200, context.Response.StatusCode); + } + + [Fact] + public void ChainedPredicatesAsync_Success() + { + IBuilder builder = new Builder(serviceProvider: null); + builder.MapWhenAsync(TruePredicateAsync, map1 => + { + map1.MapWhenAsync((PredicateAsync)FalsePredicateAsync, UseNotImplemented); + map1.MapWhenAsync((PredicateAsync)TruePredicateAsync, map2 => map2.MapWhenAsync((PredicateAsync)TruePredicateAsync, UseSuccess)); + map1.Run(NotImplemented); + }); + var app = builder.Build(); + + HttpContext context = CreateRequest(); + app.Invoke(context).Wait(); + Assert.Equal(200, context.Response.StatusCode); + } + + private HttpContext CreateRequest() + { + HttpContext context = new DefaultHttpContext(new FeatureModel.FeatureCollection()); + context.SetFeature(new FakeHttpRequestInfo()); + context.SetFeature(new FakeHttpResponseInfo()); + return context; + } + } +} diff --git a/test/Microsoft.AspNet.Abstractions.Tests/Microsoft.AspNet.Abstractions.Tests.kproj b/test/Microsoft.AspNet.Abstractions.Tests/Microsoft.AspNet.Abstractions.Tests.kproj index e8a380615a..8b42411360 100644 --- a/test/Microsoft.AspNet.Abstractions.Tests/Microsoft.AspNet.Abstractions.Tests.kproj +++ b/test/Microsoft.AspNet.Abstractions.Tests/Microsoft.AspNet.Abstractions.Tests.kproj @@ -21,6 +21,9 @@ + + + diff --git a/test/Microsoft.AspNet.Abstractions.Tests/project.json b/test/Microsoft.AspNet.Abstractions.Tests/project.json index 110d62bb6d..5a3029b789 100644 --- a/test/Microsoft.AspNet.Abstractions.Tests/project.json +++ b/test/Microsoft.AspNet.Abstractions.Tests/project.json @@ -2,6 +2,8 @@ "version": "0.1-alpha-*", "dependencies": { "Microsoft.AspNet.Abstractions": "", + "Microsoft.AspNet.PipelineCore": "", + "Microsoft.AspNet.HttpFeature": "", "Microsoft.AspNet.Testing": "0.1-alpha-*", "Xunit.KRunner": "0.1-alpha-*", "xunit.abstractions": "2.0.0-aspnet-*",