diff --git a/samples/RoutingSample/OwinRouteEndpoint.cs b/samples/RoutingSample/OwinRouteEndpoint.cs new file mode 100644 index 0000000000..f903bfee2f --- /dev/null +++ b/samples/RoutingSample/OwinRouteEndpoint.cs @@ -0,0 +1,24 @@ +// 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.Routing; + +namespace RoutingSample +{ + internal class OwinRouteEndpoint : IRouteEndpoint + { + private readonly Func, Task> _appFunc; + + public OwinRouteEndpoint(Func, Task> appFunc) + { + _appFunc = appFunc; + } + + public Task Invoke(IDictionary context) + { + return _appFunc(context); + } + } +} diff --git a/samples/RoutingSample/PrefixRoute.cs b/samples/RoutingSample/PrefixRoute.cs new file mode 100644 index 0000000000..fe1f4f092a --- /dev/null +++ b/samples/RoutingSample/PrefixRoute.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNet.Routing; + +namespace RoutingSample +{ + internal class PrefixRoute : IRoute + { + private readonly IRouteEndpoint _endpoint; + private readonly string _prefix; + + public PrefixRoute(IRouteEndpoint endpoint, string prefix) + { + _endpoint = endpoint; + + if (prefix == null) + { + prefix = "/"; + } + else if (prefix.Length > 0 && prefix[0] != '/') + { + // owin.RequestPath starts with a / + prefix = "/" + prefix; + } + + if (prefix.Length > 1 && prefix[prefix.Length - 1] == '/') + { + prefix = prefix.Substring(0, prefix.Length - 1); + } + + _prefix = prefix; + } + + public RouteMatch Match(RouteContext context) + { + if (context.RequestPath.StartsWith(_prefix, StringComparison.OrdinalIgnoreCase)) + { + if (context.RequestPath.Length > _prefix.Length) + { + char next = context.RequestPath[_prefix.Length]; + if (next != '/' && next != '#' && next != '?') + { + return null; + } + } + + return new RouteMatch(_endpoint); + } + + return null; + } + } +} diff --git a/samples/RoutingSample/Program.cs b/samples/RoutingSample/Program.cs new file mode 100644 index 0000000000..a5b500e9e1 --- /dev/null +++ b/samples/RoutingSample/Program.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using Microsoft.Owin.Hosting; + +namespace RoutingSample +{ + internal static class Program + { + public static void Main(string[] args) + { + string url = "http://localhost:30000/"; + using (WebApp.Start(url)) + { + Console.WriteLine("Listening on {0}", url); + Console.WriteLine("Press ENTER to quit"); + + Console.ReadLine(); + } + } + } +} diff --git a/samples/RoutingSample/Startup.cs b/samples/RoutingSample/Startup.cs new file mode 100644 index 0000000000..2f6f4d2d1a --- /dev/null +++ b/samples/RoutingSample/Startup.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNet.Routing.Owin; +using Owin; + +namespace RoutingSample +{ + internal class Startup + { + public void Configuration(IAppBuilder appBuilder) + { + var routes = appBuilder.UseRouter(); + + OwinRouteEndpoint endpoint1 = new OwinRouteEndpoint(async (context) => await WriteToBodyAsync(context, "match1")); + OwinRouteEndpoint endpoint2 = new OwinRouteEndpoint(async (context) => await WriteToBodyAsync(context, "Hello, World!")); + + routes.Add(new PrefixRoute(endpoint1, "api/store")); + routes.Add(new PrefixRoute(endpoint1, "api/checkout")); + routes.Add(new PrefixRoute(endpoint2, "hello/world")); + routes.Add(new PrefixRoute(endpoint1, "")); + } + + private static async Task WriteToBodyAsync(IDictionary context, string text) + { + var stream = (Stream)context["owin.ResponseBody"]; + + byte[] bytes = Encoding.UTF8.GetBytes(text); + await stream.WriteAsync(bytes, 0, bytes.Length); + } + } +} diff --git a/samples/RoutingSample/project.json b/samples/RoutingSample/project.json index 5168efba28..71c3fc2226 100644 --- a/samples/RoutingSample/project.json +++ b/samples/RoutingSample/project.json @@ -5,9 +5,12 @@ }, "configurations": { "net45": { - "dependencies": { - "Owin": "1.0" - } + "dependencies": { + "Owin": "1.0", + "Microsoft.Owin" : "2.1.0", + "Microsoft.Owin.Host.HttpListener" : "2.1.0", + "Microsoft.Owin.Hosting" : "2.1.0" + } } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Routing/DefaultRouteCollection.cs b/src/Microsoft.AspNet.Routing/DefaultRouteCollection.cs new file mode 100644 index 0000000000..81616ed126 --- /dev/null +++ b/src/Microsoft.AspNet.Routing/DefaultRouteCollection.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.AspNet.Routing +{ + public class DefaultRouteCollection : IRouteCollection + { + private readonly List _routes = new List(); + + public IRoute this[int index] + { + get { return _routes[index]; } + } + + public int Count + { + get { return _routes.Count; } + } + + public void Add(IRoute route) + { + _routes.Add(route); + } + } +} diff --git a/src/Microsoft.AspNet.Routing/DefaultRouteEngine.cs b/src/Microsoft.AspNet.Routing/DefaultRouteEngine.cs new file mode 100644 index 0000000000..c4a56b458b --- /dev/null +++ b/src/Microsoft.AspNet.Routing/DefaultRouteEngine.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Routing +{ + internal class DefaultRouteEngine : IRouteEngine + { + public DefaultRouteEngine(IRouteCollection routes) + { + Routes = routes; + } + + public IRouteCollection Routes + { + get; + private set; + } + + public async Task Invoke(IDictionary context) + { + RouteContext routeContext = new RouteContext(context); + + for (int i = 0; i < Routes.Count; i++) + { + IRoute route = Routes[i]; + + RouteMatch match = route.Match(routeContext); + if (match != null) + { + await match.Endpoint.Invoke(context); + return true; + } + } + + return false; + } + } +} diff --git a/src/Microsoft.AspNet.Routing/IRoute.cs b/src/Microsoft.AspNet.Routing/IRoute.cs new file mode 100644 index 0000000000..561299ca4e --- /dev/null +++ b/src/Microsoft.AspNet.Routing/IRoute.cs @@ -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 +{ + public interface IRoute + { + RouteMatch Match(RouteContext context); + } +} diff --git a/src/Microsoft.AspNet.Routing/IRouteCollection.cs b/src/Microsoft.AspNet.Routing/IRouteCollection.cs new file mode 100644 index 0000000000..85b9faf950 --- /dev/null +++ b/src/Microsoft.AspNet.Routing/IRouteCollection.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Routing +{ + public interface IRouteCollection + { + IRoute this[int index] + { + get; + } + + int Count + { + get; + } + + void Add(IRoute route); + } +} diff --git a/src/Microsoft.AspNet.Routing/IRouteEndpoint.cs b/src/Microsoft.AspNet.Routing/IRouteEndpoint.cs new file mode 100644 index 0000000000..badb71f73b --- /dev/null +++ b/src/Microsoft.AspNet.Routing/IRouteEndpoint.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Routing +{ + public interface IRouteEndpoint + { + Task Invoke(IDictionary context); + } +} diff --git a/src/Microsoft.AspNet.Routing/IRouteEngine.cs b/src/Microsoft.AspNet.Routing/IRouteEngine.cs new file mode 100644 index 0000000000..e813f79b42 --- /dev/null +++ b/src/Microsoft.AspNet.Routing/IRouteEngine.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Routing +{ + public interface IRouteEngine + { + Task Invoke(IDictionary context); + } +} diff --git a/src/Microsoft.AspNet.Routing/Owin/AppBuilderExtensions.cs b/src/Microsoft.AspNet.Routing/Owin/AppBuilderExtensions.cs new file mode 100644 index 0000000000..06e70a5ca1 --- /dev/null +++ b/src/Microsoft.AspNet.Routing/Owin/AppBuilderExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +#if NET45 + +using Owin; + +namespace Microsoft.AspNet.Routing.Owin +{ + public static class AppBuilderExtensions + { + public static IRouteCollection UseRouter(this IAppBuilder builder) + { + var routes = new DefaultRouteCollection(); + var engine = new DefaultRouteEngine(routes); + + builder.Use(typeof(RouterMiddleware), engine); + + return routes; + } + } +} + +#endif diff --git a/src/Microsoft.AspNet.Routing/Owin/RouterMiddleware.cs b/src/Microsoft.AspNet.Routing/Owin/RouterMiddleware.cs new file mode 100644 index 0000000000..21e4cf6904 --- /dev/null +++ b/src/Microsoft.AspNet.Routing/Owin/RouterMiddleware.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +#if NET45 + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Routing.Owin +{ + public class RouterMiddleware + { + public RouterMiddleware(Func, Task> next, IRouteEngine engine) + { + Next = next; + Engine = engine; + } + + private IRouteEngine Engine + { + get; + set; + } + + private Func, Task> Next + { + get; + set; + } + + public async Task Invoke(IDictionary context) + { + if (!(await Engine.Invoke(context))) + { + await Next.Invoke(context); + } + } + } +} + +#endif \ No newline at end of file diff --git a/src/Microsoft.AspNet.Routing/RouteContext.cs b/src/Microsoft.AspNet.Routing/RouteContext.cs new file mode 100644 index 0000000000..fb45e7dd6d --- /dev/null +++ b/src/Microsoft.AspNet.Routing/RouteContext.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.AspNet.Routing +{ + public sealed class RouteContext + { + public RouteContext(IDictionary context) + { + Context = context; + + RequestPath = (string)context["owin.RequestPath"]; + } + + public IDictionary Context + { + get; + private set; + } + + public string RequestPath + { + get; + private set; + } + } +} diff --git a/src/Microsoft.AspNet.Routing/RouteMatch.cs b/src/Microsoft.AspNet.Routing/RouteMatch.cs new file mode 100644 index 0000000000..3bea932dd2 --- /dev/null +++ b/src/Microsoft.AspNet.Routing/RouteMatch.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.AspNet.Routing +{ + /// + /// The result of matching a route. Includes an to invoke and an optional collection of + /// captured values. + /// + public sealed class RouteMatch + { + public RouteMatch(IRouteEndpoint endpoint) + : this(endpoint, null) + { + } + + public RouteMatch(IRouteEndpoint endpoint, IDictionary values) + { + Endpoint = endpoint; + Values = values; + } + + public IRouteEndpoint Endpoint + { + get; + private set; + } + + public IDictionary Values + { + get; + private set; + } + } +} diff --git a/src/Microsoft.AspNet.Routing/project.json b/src/Microsoft.AspNet.Routing/project.json index 9fdf6df436..9d789b9e65 100644 --- a/src/Microsoft.AspNet.Routing/project.json +++ b/src/Microsoft.AspNet.Routing/project.json @@ -1,12 +1,13 @@ { "version": "0.1-alpha-*", - "dependencies": {}, + "dependencies": { + }, "configurations": { "net45": { - "dependencies": { + "dependencies": { "Owin": "1.0" } - }, + }, "k10" : { } } } \ No newline at end of file