Versioning with endpoint constraint (#8098)
This commit is contained in:
parent
b62499e02c
commit
42218d5fb5
|
|
@ -293,6 +293,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
return invoker.InvokeAsync();
|
||||
};
|
||||
|
||||
var metadataCollection = BuildEndpointMetadata(action, routeName, source);
|
||||
var endpoint = new MatcherEndpoint(
|
||||
next => invokerDelegate,
|
||||
template,
|
||||
new RouteValueDictionary(nonInlineDefaults),
|
||||
new RouteValueDictionary(action.RouteValues),
|
||||
order,
|
||||
metadataCollection,
|
||||
action.DisplayName);
|
||||
|
||||
// Use defaults after the endpoint is created as it merges both the inline and
|
||||
// non-inline defaults into one.
|
||||
EnsureRequiredValuesInDefaults(endpoint.RequiredValues, endpoint.Defaults);
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
private static EndpointMetadataCollection BuildEndpointMetadata(ActionDescriptor action, string routeName, object source)
|
||||
{
|
||||
var metadata = new List<object>();
|
||||
// REVIEW: Used for debugging. Consider removing before release
|
||||
metadata.Add(source);
|
||||
|
|
@ -312,30 +331,27 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
if (action.ActionConstraints != null && action.ActionConstraints.Count > 0)
|
||||
{
|
||||
// REVIEW: What is the best way to pick up endpoint constraints of an ActionDescriptor?
|
||||
// Currently they need to implement IActionConstraintMetadata
|
||||
foreach (var actionConstraint in action.ActionConstraints)
|
||||
{
|
||||
if (actionConstraint is HttpMethodActionConstraint httpMethodActionConstraint)
|
||||
{
|
||||
metadata.Add(new HttpMethodEndpointConstraint(httpMethodActionConstraint.HttpMethods));
|
||||
}
|
||||
else if (actionConstraint is IEndpointConstraintMetadata)
|
||||
{
|
||||
// The constraint might have been added earlier, e.g. it is also a filter descriptor
|
||||
if (!metadata.Contains(actionConstraint))
|
||||
{
|
||||
metadata.Add(actionConstraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var metadataCollection = new EndpointMetadataCollection(metadata);
|
||||
var endpoint = new MatcherEndpoint(
|
||||
next => invokerDelegate,
|
||||
template,
|
||||
new RouteValueDictionary(nonInlineDefaults),
|
||||
new RouteValueDictionary(action.RouteValues),
|
||||
order,
|
||||
metadataCollection,
|
||||
action.DisplayName);
|
||||
|
||||
// Use defaults after the endpoint is created as it merges both the inline and
|
||||
// non-inline defaults into one.
|
||||
EnsureRequiredValuesInDefaults(endpoint.RequiredValues, endpoint.Defaults);
|
||||
|
||||
return endpoint;
|
||||
return metadataCollection;
|
||||
}
|
||||
|
||||
// Ensure required values are a subset of defaults
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class VersioningDispatchingTests : VersioningTestsBase<VersioningWebSite.StartupWithDispatching>
|
||||
{
|
||||
public VersioningDispatchingTests(MvcTestFixture<VersioningWebSite.StartupWithDispatching> fixture)
|
||||
: base(fixture)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,562 +1,13 @@
|
|||
// 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.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class VersioningTests : IClassFixture<MvcTestFixture<VersioningWebSite.Startup>>
|
||||
public class VersioningTests : VersioningTestsBase<VersioningWebSite.Startup>
|
||||
{
|
||||
public VersioningTests(MvcTestFixture<VersioningWebSite.Startup> fixture)
|
||||
: base(fixture)
|
||||
{
|
||||
Client = fixture.CreateDefaultClient();
|
||||
}
|
||||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
[Theory]
|
||||
[InlineData("1")]
|
||||
[InlineData("2")]
|
||||
public async Task AttributeRoutedAction_WithVersionedRoutes_IsNotAmbiguous(string version)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/Addresses?version=" + version);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Contains("api/addresses", result.ExpectedUrls);
|
||||
Assert.Equal("Address", result.Controller);
|
||||
Assert.Equal("GetV" + version, result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("1")]
|
||||
[InlineData("2")]
|
||||
public async Task AttributeRoutedAction_WithAmbiguousVersionedRoutes_CanBeDisambiguatedUsingOrder(string version)
|
||||
{
|
||||
// Arrange
|
||||
var query = "?version=" + version;
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/Addresses/All" + query);
|
||||
|
||||
// Act
|
||||
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Contains("/api/addresses/all?version=" + version, result.ExpectedUrls);
|
||||
Assert.Equal("Address", result.Controller);
|
||||
Assert.Equal("GetAllV" + version, result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_CanReachV1Operations_OnTheSameController_WithNoVersionSpecified()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Tickets", result.Controller);
|
||||
Assert.Equal("Get", result.Action);
|
||||
|
||||
Assert.DoesNotContain("id", result.RouteValues.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_CanReachV1Operations_OnTheSameController_WithVersionSpecified()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets?version=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Tickets", result.Controller);
|
||||
Assert.Equal("Get", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheSameController()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets/5");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Tickets", result.Controller);
|
||||
Assert.Equal("GetById", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheSameController_WithVersionSpecified()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets/5?version=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Tickets", result.Controller);
|
||||
Assert.Equal("GetById", result.Action);
|
||||
Assert.NotEmpty(result.RouteValues);
|
||||
|
||||
Assert.Contains(
|
||||
new KeyValuePair<string, object>("id", "5"),
|
||||
result.RouteValues);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("2")]
|
||||
[InlineData("3")]
|
||||
[InlineData("4")]
|
||||
public async Task VersionedApi_CanReachOtherVersionOperations_OnTheSameController(string version)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Tickets?version=" + version);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Tickets", result.Controller);
|
||||
Assert.Equal("Post", result.Action);
|
||||
Assert.NotEmpty(result.RouteValues);
|
||||
|
||||
Assert.DoesNotContain(
|
||||
new KeyValuePair<string, object>("id", "5"),
|
||||
result.RouteValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_CanNotReachOtherVersionOperations_OnTheSameController_WithNoVersionSpecified()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Tickets");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsByteArrayAsync();
|
||||
Assert.Empty(body);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("PUT", "Put", "2")]
|
||||
[InlineData("PUT", "Put", "3")]
|
||||
[InlineData("PUT", "Put", "4")]
|
||||
[InlineData("DELETE", "Delete", "2")]
|
||||
[InlineData("DELETE", "Delete", "3")]
|
||||
[InlineData("DELETE", "Delete", "4")]
|
||||
public async Task VersionedApi_CanReachOtherVersionOperationsWithParameters_OnTheSameController(
|
||||
string method,
|
||||
string action,
|
||||
string version)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Tickets/5?version=" + version);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Tickets", result.Controller);
|
||||
Assert.Equal(action, result.Action);
|
||||
Assert.NotEmpty(result.RouteValues);
|
||||
|
||||
Assert.Contains(
|
||||
new KeyValuePair<string, object>("id", "5"),
|
||||
result.RouteValues);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("PUT")]
|
||||
[InlineData("DELETE")]
|
||||
public async Task VersionedApi_CanNotReachOtherVersionOperationsWithParameters_OnTheSameController_WithNoVersionSpecified(string method)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Tickets/5");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsByteArrayAsync();
|
||||
Assert.Empty(body);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("3")]
|
||||
[InlineData("4")]
|
||||
[InlineData("5")]
|
||||
public async Task VersionedApi_CanUseOrderToDisambiguate_OverlappingVersionRanges(string version)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Books?version=" + version);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Books", result.Controller);
|
||||
Assert.Equal("GetBreakingChange", result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("1")]
|
||||
[InlineData("2")]
|
||||
[InlineData("6")]
|
||||
public async Task VersionedApi_OverlappingVersionRanges_FallsBackToLowerOrderAction(string version)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Books?version=" + version);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Books", result.Controller);
|
||||
Assert.Equal("Get", result.Action);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "Get")]
|
||||
[InlineData("POST", "Post")]
|
||||
public async Task VersionedApi_CanReachV1Operations_OnTheOriginalController_WithNoVersionSpecified(string method, string action)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Movies", result.Controller);
|
||||
Assert.Equal(action, result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "Get")]
|
||||
[InlineData("POST", "Post")]
|
||||
public async Task VersionedApi_CanReachV1Operations_OnTheOriginalController_WithVersionSpecified(string method, string action)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies?version=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Movies", result.Controller);
|
||||
Assert.Equal(action, result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "GetById")]
|
||||
[InlineData("PUT", "Put")]
|
||||
[InlineData("DELETE", "Delete")]
|
||||
public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheOriginalController(string method, string action)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies/5");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Movies", result.Controller);
|
||||
Assert.Equal(action, result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "GetById")]
|
||||
[InlineData("DELETE", "Delete")]
|
||||
public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheOriginalController_WithVersionSpecified(string method, string action)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies/5?version=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Movies", result.Controller);
|
||||
Assert.Equal(action, result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_CanReachOtherVersionOperationsWithParameters_OnTheV2Controller()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Put, "http://localhost/Movies/5?version=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("MoviesV2", result.Controller);
|
||||
Assert.Equal("Put", result.Action);
|
||||
Assert.NotEmpty(result.RouteValues);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("v1/Pets")]
|
||||
[InlineData("v2/Pets")]
|
||||
public async Task VersionedApi_CanHaveTwoRoutesWithVersionOnTheUrl_OnTheSameAction(string url)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/" + url);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Pets", result.Controller);
|
||||
Assert.Equal("Get", result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("v1/Pets/5", "V1")]
|
||||
[InlineData("v2/Pets/5", "V2")]
|
||||
public async Task VersionedApi_CanHaveTwoRoutesWithVersionOnTheUrl_OnDifferentActions(string url, string version)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/" + url);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Pets", result.Controller);
|
||||
Assert.Equal("Get" + version, result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("v1/Pets", "V1")]
|
||||
[InlineData("v2/Pets", "V2")]
|
||||
public async Task VersionedApi_CanHaveTwoRoutesWithVersionOnTheUrl_OnDifferentActions_WithInlineConstraint(string url, string version)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/" + url);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Pets", result.Controller);
|
||||
Assert.Equal("Post" + version, result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Customers/5", "?version=1", "Get")]
|
||||
[InlineData("Customers/5", "?version=2", "Get")]
|
||||
[InlineData("Customers/5", "?version=3", "GetV3ToV5")]
|
||||
[InlineData("Customers/5", "?version=4", "GetV3ToV5")]
|
||||
[InlineData("Customers/5", "?version=5", "GetV3ToV5")]
|
||||
public async Task VersionedApi_CanProvideVersioningInformation_UsingPlainActionConstraint(string url, string query, string actionName)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/" + url + query);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Customers", result.Controller);
|
||||
Assert.Equal(actionName, result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_ConstraintOrder_IsRespected()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/" + "Customers?version=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Customers", result.Controller);
|
||||
Assert.Equal("AnyV2OrHigher", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_CanUseConstraintOrder_ToChangeSelectedAction()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Delete, "http://localhost/" + "Customers/5?version=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Customers", result.Controller);
|
||||
Assert.Equal("Delete", result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("1")]
|
||||
[InlineData("2")]
|
||||
public async Task VersionedApi_MultipleVersionsUsingAttributeRouting_OnTheSameMethod(string version)
|
||||
{
|
||||
// Arrange
|
||||
var path = "/" + version + "/Vouchers?version=" + version;
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost" + path);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Vouchers", result.Controller);
|
||||
Assert.Equal("GetVouchersMultipleVersions", result.Action);
|
||||
|
||||
var actualUrl = Assert.Single(result.ExpectedUrls);
|
||||
Assert.Equal(path, actualUrl);
|
||||
}
|
||||
|
||||
private class RoutingResult
|
||||
{
|
||||
public string[] ExpectedUrls { get; set; }
|
||||
|
||||
public string ActualUrl { get; set; }
|
||||
|
||||
public Dictionary<string, object> RouteValues { get; set; }
|
||||
|
||||
public string RouteName { get; set; }
|
||||
|
||||
public string Action { get; set; }
|
||||
|
||||
public string Controller { get; set; }
|
||||
|
||||
public string Link { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,568 @@
|
|||
// 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.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public abstract class VersioningTestsBase<TStartup> : IClassFixture<MvcTestFixture<TStartup>> where TStartup : class
|
||||
{
|
||||
protected VersioningTestsBase(MvcTestFixture<TStartup> fixture)
|
||||
{
|
||||
var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
|
||||
Client = factory.CreateDefaultClient();
|
||||
}
|
||||
|
||||
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
|
||||
builder.UseStartup<TStartup>();
|
||||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
[Theory]
|
||||
[InlineData("1")]
|
||||
[InlineData("2")]
|
||||
public async Task AttributeRoutedAction_WithVersionedRoutes_IsNotAmbiguous(string version)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/Addresses?version=" + version);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Contains("api/addresses", result.ExpectedUrls);
|
||||
Assert.Equal("Address", result.Controller);
|
||||
Assert.Equal("GetV" + version, result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("1")]
|
||||
[InlineData("2")]
|
||||
public async Task AttributeRoutedAction_WithAmbiguousVersionedRoutes_CanBeDisambiguatedUsingOrder(string version)
|
||||
{
|
||||
// Arrange
|
||||
var query = "?version=" + version;
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/Addresses/All" + query);
|
||||
|
||||
// Act
|
||||
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Contains("/api/addresses/all?version=" + version, result.ExpectedUrls);
|
||||
Assert.Equal("Address", result.Controller);
|
||||
Assert.Equal("GetAllV" + version, result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_CanReachV1Operations_OnTheSameController_WithNoVersionSpecified()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Tickets", result.Controller);
|
||||
Assert.Equal("Get", result.Action);
|
||||
|
||||
Assert.DoesNotContain("id", result.RouteValues.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_CanReachV1Operations_OnTheSameController_WithVersionSpecified()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets?version=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Tickets", result.Controller);
|
||||
Assert.Equal("Get", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheSameController()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets/5");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Tickets", result.Controller);
|
||||
Assert.Equal("GetById", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheSameController_WithVersionSpecified()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Tickets/5?version=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Tickets", result.Controller);
|
||||
Assert.Equal("GetById", result.Action);
|
||||
Assert.NotEmpty(result.RouteValues);
|
||||
|
||||
Assert.Contains(
|
||||
new KeyValuePair<string, object>("id", "5"),
|
||||
result.RouteValues);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("2")]
|
||||
[InlineData("3")]
|
||||
[InlineData("4")]
|
||||
public async Task VersionedApi_CanReachOtherVersionOperations_OnTheSameController(string version)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Tickets?version=" + version);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Tickets", result.Controller);
|
||||
Assert.Equal("Post", result.Action);
|
||||
Assert.NotEmpty(result.RouteValues);
|
||||
|
||||
Assert.DoesNotContain(
|
||||
new KeyValuePair<string, object>("id", "5"),
|
||||
result.RouteValues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_CanNotReachOtherVersionOperations_OnTheSameController_WithNoVersionSpecified()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Tickets");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsByteArrayAsync();
|
||||
Assert.Empty(body);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("PUT", "Put", "2")]
|
||||
[InlineData("PUT", "Put", "3")]
|
||||
[InlineData("PUT", "Put", "4")]
|
||||
[InlineData("DELETE", "Delete", "2")]
|
||||
[InlineData("DELETE", "Delete", "3")]
|
||||
[InlineData("DELETE", "Delete", "4")]
|
||||
public async Task VersionedApi_CanReachOtherVersionOperationsWithParameters_OnTheSameController(
|
||||
string method,
|
||||
string action,
|
||||
string version)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Tickets/5?version=" + version);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Tickets", result.Controller);
|
||||
Assert.Equal(action, result.Action);
|
||||
Assert.NotEmpty(result.RouteValues);
|
||||
|
||||
Assert.Contains(
|
||||
new KeyValuePair<string, object>("id", "5"),
|
||||
result.RouteValues);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("PUT")]
|
||||
[InlineData("DELETE")]
|
||||
public async Task VersionedApi_CanNotReachOtherVersionOperationsWithParameters_OnTheSameController_WithNoVersionSpecified(string method)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Tickets/5");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsByteArrayAsync();
|
||||
Assert.Empty(body);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("3")]
|
||||
[InlineData("4")]
|
||||
[InlineData("5")]
|
||||
public async Task VersionedApi_CanUseOrderToDisambiguate_OverlappingVersionRanges(string version)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Books?version=" + version);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Books", result.Controller);
|
||||
Assert.Equal("GetBreakingChange", result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("1")]
|
||||
[InlineData("2")]
|
||||
[InlineData("6")]
|
||||
public async Task VersionedApi_OverlappingVersionRanges_FallsBackToLowerOrderAction(string version)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Books?version=" + version);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Books", result.Controller);
|
||||
Assert.Equal("Get", result.Action);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "Get")]
|
||||
[InlineData("POST", "Post")]
|
||||
public async Task VersionedApi_CanReachV1Operations_OnTheOriginalController_WithNoVersionSpecified(string method, string action)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Movies", result.Controller);
|
||||
Assert.Equal(action, result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "Get")]
|
||||
[InlineData("POST", "Post")]
|
||||
public async Task VersionedApi_CanReachV1Operations_OnTheOriginalController_WithVersionSpecified(string method, string action)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies?version=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Movies", result.Controller);
|
||||
Assert.Equal(action, result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "GetById")]
|
||||
[InlineData("PUT", "Put")]
|
||||
[InlineData("DELETE", "Delete")]
|
||||
public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheOriginalController(string method, string action)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies/5");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Movies", result.Controller);
|
||||
Assert.Equal(action, result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GET", "GetById")]
|
||||
[InlineData("DELETE", "Delete")]
|
||||
public async Task VersionedApi_CanReachV1OperationsWithParameters_OnTheOriginalController_WithVersionSpecified(string method, string action)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Movies/5?version=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Movies", result.Controller);
|
||||
Assert.Equal(action, result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_CanReachOtherVersionOperationsWithParameters_OnTheV2Controller()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Put, "http://localhost/Movies/5?version=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("MoviesV2", result.Controller);
|
||||
Assert.Equal("Put", result.Action);
|
||||
Assert.NotEmpty(result.RouteValues);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("v1/Pets")]
|
||||
[InlineData("v2/Pets")]
|
||||
public async Task VersionedApi_CanHaveTwoRoutesWithVersionOnTheUrl_OnTheSameAction(string url)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/" + url);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Pets", result.Controller);
|
||||
Assert.Equal("Get", result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("v1/Pets/5", "V1")]
|
||||
[InlineData("v2/Pets/5", "V2")]
|
||||
public async Task VersionedApi_CanHaveTwoRoutesWithVersionOnTheUrl_OnDifferentActions(string url, string version)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/" + url);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Pets", result.Controller);
|
||||
Assert.Equal("Get" + version, result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("v1/Pets", "V1")]
|
||||
[InlineData("v2/Pets", "V2")]
|
||||
public async Task VersionedApi_CanHaveTwoRoutesWithVersionOnTheUrl_OnDifferentActions_WithInlineConstraint(string url, string version)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/" + url);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Pets", result.Controller);
|
||||
Assert.Equal("Post" + version, result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Customers/5", "?version=1", "Get")]
|
||||
[InlineData("Customers/5", "?version=2", "Get")]
|
||||
[InlineData("Customers/5", "?version=3", "GetV3ToV5")]
|
||||
[InlineData("Customers/5", "?version=4", "GetV3ToV5")]
|
||||
[InlineData("Customers/5", "?version=5", "GetV3ToV5")]
|
||||
public async Task VersionedApi_CanProvideVersioningInformation_UsingPlainActionConstraint(string url, string query, string actionName)
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/" + url + query);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Customers", result.Controller);
|
||||
Assert.Equal(actionName, result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_ConstraintOrder_IsRespected()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/" + "Customers?version=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Customers", result.Controller);
|
||||
Assert.Equal("AnyV2OrHigher", result.Action);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VersionedApi_CanUseConstraintOrder_ToChangeSelectedAction()
|
||||
{
|
||||
// Arrange
|
||||
var message = new HttpRequestMessage(HttpMethod.Delete, "http://localhost/" + "Customers/5?version=2");
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Customers", result.Controller);
|
||||
Assert.Equal("Delete", result.Action);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("1")]
|
||||
[InlineData("2")]
|
||||
public async Task VersionedApi_MultipleVersionsUsingAttributeRouting_OnTheSameMethod(string version)
|
||||
{
|
||||
// Arrange
|
||||
var path = "/" + version + "/Vouchers?version=" + version;
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost" + path);
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(message);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonConvert.DeserializeObject<RoutingResult>(body);
|
||||
|
||||
Assert.Equal("Vouchers", result.Controller);
|
||||
Assert.Equal("GetVouchersMultipleVersions", result.Action);
|
||||
|
||||
var actualUrl = Assert.Single(result.ExpectedUrls);
|
||||
Assert.Equal(path, actualUrl);
|
||||
}
|
||||
|
||||
private class RoutingResult
|
||||
{
|
||||
public string[] ExpectedUrls { get; set; }
|
||||
|
||||
public string ActualUrl { get; set; }
|
||||
|
||||
public Dictionary<string, object> RouteValues { get; set; }
|
||||
|
||||
public string RouteName { get; set; }
|
||||
|
||||
public string Action { get; set; }
|
||||
|
||||
public string Controller { get; set; }
|
||||
|
||||
public string Link { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// 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.IO;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace VersioningWebSite
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = CreateWebHostBuilder(args)
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
}
|
||||
|
||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
new WebHostBuilder()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseStartup<Startup>()
|
||||
.UseKestrel()
|
||||
.UseIISIntegration();
|
||||
}
|
||||
}
|
||||
|
|
@ -24,21 +24,5 @@ namespace VersioningWebSite
|
|||
{
|
||||
app.UseMvcWithDefaultRoute();
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var host = CreateWebHostBuilder(args)
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
}
|
||||
|
||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
new WebHostBuilder()
|
||||
.UseContentRoot(Directory.GetCurrentDirectory())
|
||||
.UseStartup<Startup>()
|
||||
.UseKestrel()
|
||||
.UseIISIntegration();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// 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.IO;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace VersioningWebSite
|
||||
{
|
||||
public class StartupWithDispatching
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddDispatcher();
|
||||
|
||||
// Add MVC services to the services container
|
||||
services.AddMvc();
|
||||
|
||||
services.AddScoped<TestResponseGenerator>();
|
||||
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseDispatcher();
|
||||
|
||||
app.UseMvcWithEndpoint(endpoints =>
|
||||
{
|
||||
endpoints.MapEndpoint(
|
||||
name: "default",
|
||||
template: "{controller=Home}/{action=Index}/{id?}");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,11 @@
|
|||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||
using Microsoft.AspNetCore.Routing.EndpointConstraints;
|
||||
|
||||
namespace VersioningWebSite
|
||||
{
|
||||
public class VersionAttribute : Attribute, IActionConstraintFactory
|
||||
public class VersionAttribute : Attribute, IActionConstraintFactory, IEndpointConstraintFactory
|
||||
{
|
||||
private int? _maxVersion;
|
||||
private int? _minVersion;
|
||||
|
|
@ -32,7 +33,12 @@ namespace VersioningWebSite
|
|||
|
||||
public bool IsReusable => true;
|
||||
|
||||
public IActionConstraint CreateInstance(IServiceProvider services)
|
||||
IActionConstraint IActionConstraintFactory.CreateInstance(IServiceProvider services)
|
||||
{
|
||||
return new VersionRangeValidator(_minVersion, _maxVersion) { Order = _order ?? 0 };
|
||||
}
|
||||
|
||||
IEndpointConstraint IEndpointConstraintFactory.CreateInstance(IServiceProvider services)
|
||||
{
|
||||
return new VersionRangeValidator(_minVersion, _maxVersion) { Order = _order ?? 0 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc.Routing;
|
|||
|
||||
namespace VersioningWebSite
|
||||
{
|
||||
public class VersionDeleteAttribute : VersionRoute, IActionHttpMethodProvider
|
||||
public class VersionDeleteAttribute : VersionRouteAttribute, IActionHttpMethodProvider
|
||||
{
|
||||
public VersionDeleteAttribute(string template)
|
||||
: base(template)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc.Routing;
|
|||
|
||||
namespace VersioningWebSite
|
||||
{
|
||||
public class VersionGetAttribute : VersionRoute, IActionHttpMethodProvider
|
||||
public class VersionGetAttribute : VersionRouteAttribute, IActionHttpMethodProvider
|
||||
{
|
||||
public VersionGetAttribute(string template)
|
||||
: base(template)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc.Routing;
|
|||
|
||||
namespace VersioningWebSite
|
||||
{
|
||||
public class VersionPostAttribute : VersionRoute, IActionHttpMethodProvider
|
||||
public class VersionPostAttribute : VersionRouteAttribute, IActionHttpMethodProvider
|
||||
{
|
||||
public VersionPostAttribute(string template)
|
||||
: base(template)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc.Routing;
|
|||
|
||||
namespace VersioningWebSite
|
||||
{
|
||||
public class VersionPutAttribute : VersionRoute, IActionHttpMethodProvider
|
||||
public class VersionPutAttribute : VersionRouteAttribute, IActionHttpMethodProvider
|
||||
{
|
||||
public VersionPutAttribute(string template)
|
||||
: base(template)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@
|
|||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||
using Microsoft.AspNetCore.Routing.EndpointConstraints;
|
||||
|
||||
namespace VersioningWebSite
|
||||
{
|
||||
public class VersionRangeValidator : IActionConstraint
|
||||
public class VersionRangeValidator : IActionConstraint, IEndpointConstraint
|
||||
{
|
||||
private readonly int? _minVersion;
|
||||
private readonly int? _maxVersion;
|
||||
|
|
@ -25,9 +26,19 @@ namespace VersioningWebSite
|
|||
}
|
||||
|
||||
public bool Accept(ActionConstraintContext context)
|
||||
{
|
||||
return ProcessRequest(context.RouteContext.HttpContext.Request);
|
||||
}
|
||||
|
||||
public bool Accept(EndpointConstraintContext context)
|
||||
{
|
||||
return ProcessRequest(context.HttpContext.Request);
|
||||
}
|
||||
|
||||
private bool ProcessRequest(HttpRequest request)
|
||||
{
|
||||
int version;
|
||||
if (int.TryParse(GetVersion(context.RouteContext.HttpContext.Request), out version))
|
||||
if (int.TryParse(GetVersion(request), out version))
|
||||
{
|
||||
return (_minVersion == null || _minVersion <= version) &&
|
||||
(_maxVersion == null || _maxVersion >= version);
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@ using System;
|
|||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||
using Microsoft.AspNetCore.Routing.EndpointConstraints;
|
||||
|
||||
namespace VersioningWebSite
|
||||
{
|
||||
public class VersionRoute : RouteAttribute, IActionConstraintFactory
|
||||
public class VersionRouteAttribute : RouteAttribute, IActionConstraintFactory, IEndpointConstraintFactory
|
||||
{
|
||||
private readonly IActionConstraint _constraint;
|
||||
private readonly IActionConstraint _actionConstraint;
|
||||
|
||||
// 5
|
||||
// [5]
|
||||
|
|
@ -28,12 +29,12 @@ namespace VersioningWebSite
|
|||
|
||||
public bool IsReusable => true;
|
||||
|
||||
public VersionRoute(string template)
|
||||
public VersionRouteAttribute(string template)
|
||||
: base(template)
|
||||
{
|
||||
}
|
||||
|
||||
public VersionRoute(string template, string versionRange)
|
||||
public VersionRouteAttribute(string template, string versionRange)
|
||||
: base(template)
|
||||
{
|
||||
var constraint = CreateVersionConstraint(versionRange);
|
||||
|
|
@ -44,7 +45,7 @@ namespace VersioningWebSite
|
|||
throw new ArgumentException(message, "versionRange");
|
||||
}
|
||||
|
||||
_constraint = constraint;
|
||||
_actionConstraint = constraint;
|
||||
}
|
||||
|
||||
private static IActionConstraint CreateVersionConstraint(string versionRange)
|
||||
|
|
@ -122,9 +123,14 @@ namespace VersioningWebSite
|
|||
}
|
||||
}
|
||||
|
||||
public IActionConstraint CreateInstance(IServiceProvider services)
|
||||
IActionConstraint IActionConstraintFactory.CreateInstance(IServiceProvider services)
|
||||
{
|
||||
return _constraint;
|
||||
return _actionConstraint;
|
||||
}
|
||||
|
||||
IEndpointConstraint IEndpointConstraintFactory.CreateInstance(IServiceProvider services)
|
||||
{
|
||||
return (IEndpointConstraint)_actionConstraint;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue