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
+
+
+
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-*",