Skip to content
Permalink
Browse files

Productize HttpMethodEndpointSelector (#463)

Addresses #452
  • Loading branch information...
jbagga committed Oct 5, 2017
1 parent f49cbd1 commit 2f8951e24424b9c113af87f8d59da2c59eae6903
@@ -7,11 +7,9 @@
using Microsoft.AspNetCore.Dispatcher;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Dispatcher;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace DispatcherSample
{
@@ -4,9 +4,8 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Dispatcher;

namespace DispatcherSample
namespace Microsoft.AspNetCore.Dispatcher
{
public class HttpMethodEndpointSelector : EndpointSelector
{
@@ -19,19 +18,19 @@ public override async Task SelectAsync(EndpointSelectorContext context)

var snapshot = context.CreateSnapshot();

var fallback = new List<Endpoint>();
var fallbackEndpoints = new List<Endpoint>();
for (var i = context.Endpoints.Count - 1; i >= 0; i--)
{
var endpoint = context.Endpoints[i] as ITemplateEndpoint;
if (endpoint == null || endpoint.HttpMethod == null)
{
// No metadata.
fallback.Add(context.Endpoints[i]);
fallbackEndpoints.Add(context.Endpoints[i]);
context.Endpoints.RemoveAt(i);
}
else if (string.Equals(endpoint.HttpMethod, context.HttpContext.Request.Method, StringComparison.OrdinalIgnoreCase))
{
// This one matches.
// The request method matches the endpoint's HTTP method.
}
else
{
@@ -50,9 +49,9 @@ public override async Task SelectAsync(EndpointSelectorContext context)
context.RestoreSnapshot(snapshot);
context.Endpoints.Clear();

for (var i = 0; i < fallback.Count; i++)
for (var i = 0; i < fallbackEndpoints.Count; i++)
{
context.Endpoints.Add(fallback[i]);
context.Endpoints.Add(fallbackEndpoints[i]);
}

await context.InvokeNextAsync();
@@ -167,7 +167,7 @@ private Cache CreateCache()
trees.Add(new UrlMatchingTree(trees.Count));
}

var tree = trees[i];
var tree = trees[entry.Order];

TreeRouteBuilder.AddEntryToTree(tree, entry);
}
@@ -62,15 +62,31 @@ public void ConfigureDispatcher(DispatcherOptions options)
{
Endpoints =
{
new TemplateEndpoint("api/products", Products_Get),
new TemplateEndpoint("api/products", Products_Fallback),
new TemplateEndpoint("api/products", new { controller = "Products", action = "Get", }, "GET", Products_Get),
new TemplateEndpoint("api/products/{id}", new { controller = "Products", action = "Get", }, "GET", Products_GetWithId),
new TemplateEndpoint("api/products", new { controller = "Products", action = "Post", }, "POST", Products_Post),
new TemplateEndpoint("api/products/{id}", new { controller = "Products", action = "Put", }, "PUT", Products_Put),
},
});

Selectors =
{
new HttpMethodEndpointSelector(),
}
});
options.HandlerFactories.Add(endpoint => (endpoint as TemplateEndpoint)?.HandlerFactory);
}

private Task Products_Fallback(HttpContext httpContext) => httpContext.Response.WriteAsync("Hello, Products_Fallback");

private Task Products_Get(HttpContext httpContext) => httpContext.Response.WriteAsync("Hello, Products_Get");

private Task Products_GetWithId(HttpContext httpContext) => httpContext.Response.WriteAsync("Hello, Products_GetWithId");

private Task Products_Post(HttpContext httpContext) => httpContext.Response.WriteAsync("Hello, Products_Post");

private Task Products_Put(HttpContext httpContext) => httpContext.Response.WriteAsync("Hello, Products_Put");

private class CorsPolicyMetadata
{
public string Name { get; set; }
@@ -30,5 +30,73 @@ public async Task ApiApp_CanRouteTo_LiteralEndpoint()
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Hello, Products_Get", await response.Content.ReadAsStringAsync());
}

[Fact]
public async Task ApiApp_RoutesTo_EndpointWithMatchingHttpMethod()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Post, "/api/products");

// Act
var response = await Client.SendAsync(request);

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Hello, Products_Post", await response.Content.ReadAsStringAsync());
}

[Fact]
public async Task ApiApp_RoutesTo_EndpointWithMatchingHttpMethod_AndMatchingRoute()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Get, "/api/products/3");

// Act
var response = await Client.SendAsync(request);

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Hello, Products_GetWithId", await response.Content.ReadAsStringAsync());
}

[Fact]
public async Task ApiApp_RoutesTo_EndpointWithMatchingHttpMethod_DoesNotMatchExpectedRoute()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Put, "/api/services/2");

// Act
var response = await Client.SendAsync(request);

// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

[Fact]
public async Task ApiApp_NoEndpointWithMatchingHttpMethod_FallbackEndpointSelected()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Delete, "/api/products");

// Act
var response = await Client.SendAsync(request);

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("Hello, Products_Fallback", await response.Content.ReadAsStringAsync());
}

[Fact]
public async Task ApiApp_NoEndpointWithMatchingHttpMethod_NoFallbackEndpointMatched()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Delete, "/api/products/4");

// Act
var response = await Client.SendAsync(request);

// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
}

This file was deleted.

@@ -0,0 +1,101 @@
// Copyright (c) .NET Foundation. 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.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Xunit;

namespace Microsoft.AspNetCore.Dispatcher
{
public class HttpMethodEndpointSelectorTest
{
[Theory]
[InlineData("get")]
[InlineData("Get")]
[InlineData("GET")]
public async Task RequestMethod_MatchesEndpointMethod_IgnoresCase(string httpMethod)
{
// Arrange
var endpoints = new List<Endpoint>()
{
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Products", action = "Get", }, "GET", Products_Get, "Products:Get()"),
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Products", action = "Create", }, "POST", Products_Post, "Products:Post()"),
};

var (context, selector) = CreateContextAndSelector(httpMethod, endpoints);

// Act
await selector.SelectAsync(context);
var templateEndpoints = context.Endpoints.Cast<TemplateEndpoint>();

// Assert
Assert.Collection(
templateEndpoints,
endpoint => Assert.Equal(httpMethod.ToUpperInvariant(), endpoint.HttpMethod));
}

[Fact]
public async Task RequestMethod_DoesNotMatch_AnyEndpointMethod()
{
// Arrange
var endpoints = new List<Endpoint>()
{
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Products", action = "Get", }, "GET", Products_Get, "Products:Get()"),
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Products", action = "Create", }, "POST", Products_Post, "Products:Post()"),
};

var (context, selector) = CreateContextAndSelector("PUT", endpoints);

// Act
await selector.SelectAsync(context);

// Assert
Assert.Equal(0, context.Endpoints.Count);
}

[Theory]
[InlineData("PUT")]
[InlineData(null)]
public async Task RequestMethod_NotSpecifiedOrNotFound_ReturnsFallbackEndpointMethod(string httpMethod)
{
// Arrange
var endpoints = new List<Endpoint>()
{
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Products", action = "Get", }, "GET", Products_Get, "Products:Get()"),
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Products", action = "Create", }, "POST", Products_Post, "Products:Post()"),
new TemplateEndpoint("{controller=Home}/{action=Index}/{id?}", new { controller = "Products", action = "Get", }, Products_Get),
};

var (context, selector) = CreateContextAndSelector(httpMethod, endpoints);

// Act
await selector.SelectAsync(context);
var templateEndpoints = context.Endpoints.Cast<TemplateEndpoint>();

// Assert
Assert.Collection(
templateEndpoints,
endpoint => Assert.Null(endpoint.HttpMethod));
}

private (EndpointSelectorContext, EndpointSelector) CreateContextAndSelector(string httpMethod, List<Endpoint> endpoints)
{
var httpContext = new DefaultHttpContext();
httpContext.Request.Method = httpMethod;
var selector = new HttpMethodEndpointSelector();
var selectors = new List<EndpointSelector>()
{
selector
};

var selectorContext = new EndpointSelectorContext(httpContext, endpoints, selectors);
return (selectorContext, selector);
}

private Task Products_Get(HttpContext httpContext) => httpContext.Response.WriteAsync("Hello, Products_Get");

private Task Products_Post(HttpContext httpContext) => httpContext.Response.WriteAsync("Hello, Products_Post");
}
}
@@ -9,4 +9,8 @@
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Dispatcher\Microsoft.AspNetCore.Dispatcher.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" />
</ItemGroup>

</Project>

0 comments on commit 2f8951e

Please sign in to comment.
You can’t perform that action at this time.