Initial mock up of key interfaces for routing.

This is heavily based right now on raw Owin as the request/response model
since that will work for building samples/tests, and doesn't require
ifdefs in the code for k10. Once it's feasible, I'll port this to use the
Microsoft.AspNet.Abstractions types.

The sample here includes some basic route/endpoint implementations just to
prove that the route engine works in naive scenarios. These might live on
in the future in some form, but for now it's just part of the sample.

The next step is to integrate this with WebFX and start developing the
integration/configuration story for WebFX.
This commit is contained in:
Ryan Nowak 2014-01-29 14:29:10 -08:00
parent 9f9f92d18a
commit 84f4a2d047
16 changed files with 392 additions and 6 deletions

View File

@ -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<IDictionary<string, object>, Task> _appFunc;
public OwinRouteEndpoint(Func<IDictionary<string, object>, Task> appFunc)
{
_appFunc = appFunc;
}
public Task Invoke(IDictionary<string, object> context)
{
return _appFunc(context);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<Startup>(url))
{
Console.WriteLine("Listening on {0}", url);
Console.WriteLine("Press ENTER to quit");
Console.ReadLine();
}
}
}
}

View File

@ -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<string, object> context, string text)
{
var stream = (Stream)context["owin.ResponseBody"];
byte[] bytes = Encoding.UTF8.GetBytes(text);
await stream.WriteAsync(bytes, 0, bytes.Length);
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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<IRoute> _routes = new List<IRoute>();
public IRoute this[int index]
{
get { return _routes[index]; }
}
public int Count
{
get { return _routes.Count; }
}
public void Add(IRoute route)
{
_routes.Add(route);
}
}
}

View File

@ -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<bool> Invoke(IDictionary<string, object> 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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<string, object> context);
}
}

View File

@ -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<bool> Invoke(IDictionary<string, object> context);
}
}

View File

@ -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

View File

@ -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<IDictionary<string, object>, Task> next, IRouteEngine engine)
{
Next = next;
Engine = engine;
}
private IRouteEngine Engine
{
get;
set;
}
private Func<IDictionary<string, object>, Task> Next
{
get;
set;
}
public async Task Invoke(IDictionary<string, object> context)
{
if (!(await Engine.Invoke(context)))
{
await Next.Invoke(context);
}
}
}
}
#endif

View File

@ -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<string, object> context)
{
Context = context;
RequestPath = (string)context["owin.RequestPath"];
}
public IDictionary<string, object> Context
{
get;
private set;
}
public string RequestPath
{
get;
private set;
}
}
}

View File

@ -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
{
/// <summary>
/// The result of matching a route. Includes an <see cref="IRouteEndpoint"/> to invoke and an optional collection of
/// captured values.
/// </summary>
public sealed class RouteMatch
{
public RouteMatch(IRouteEndpoint endpoint)
: this(endpoint, null)
{
}
public RouteMatch(IRouteEndpoint endpoint, IDictionary<string, object> values)
{
Endpoint = endpoint;
Values = values;
}
public IRouteEndpoint Endpoint
{
get;
private set;
}
public IDictionary<string, object> Values
{
get;
private set;
}
}
}

View File

@ -1,12 +1,13 @@
{
"version": "0.1-alpha-*",
"dependencies": {},
"dependencies": {
},
"configurations": {
"net45": {
"dependencies": {
"dependencies": {
"Owin": "1.0"
}
},
},
"k10" : { }
}
}