Fix ArgumentException from duplicate key (#6416)

This commit is contained in:
Martin Costello 2019-01-07 00:42:36 +00:00 committed by James Newton-King
parent 489735d390
commit ca7c48c520
2 changed files with 37 additions and 6 deletions

View File

@ -1,13 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
@ -274,7 +271,11 @@ namespace Microsoft.AspNetCore.Routing.Matching
(context) => (context) =>
{ {
context.Response.StatusCode = 405; context.Response.StatusCode = 405;
context.Response.Headers.Add("Allow", allow);
// Prevent ArgumentException from duplicate key if header already added, such as when the
// request is re-executed by an error handler (see https://github.com/aspnet/AspNetCore/issues/6415)
context.Response.Headers["Allow"] = allow;
return Task.CompletedTask; return Task.CompletedTask;
}, },
EndpointMetadataCollection.Empty, EndpointMetadataCollection.Empty,

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -288,6 +288,36 @@ namespace Microsoft.AspNetCore.Routing.Matching
MatcherAssert.AssertMatch(context, httpContext, endpoint2, ignoreValues: true); MatcherAssert.AssertMatch(context, httpContext, endpoint2, ignoreValues: true);
} }
[Fact] // See https://github.com/aspnet/AspNetCore/issues/6415
public async Task NotMatch_HttpMethod_Returns405Endpoint_ReExecute()
{
// Arrange
var endpoint1 = CreateEndpoint("/hello", httpMethods: new string[] { "GET", "PUT" });
var endpoint2 = CreateEndpoint("/hello", httpMethods: new string[] { "DELETE" });
var matcher = CreateMatcher(endpoint1, endpoint2);
var (httpContext, context) = CreateContext("/hello", "POST");
// Act
await matcher.MatchAsync(httpContext, context);
// Assert
Assert.NotSame(endpoint1, context.Endpoint);
Assert.NotSame(endpoint2, context.Endpoint);
Assert.Same(HttpMethodMatcherPolicy.Http405EndpointDisplayName, context.Endpoint.DisplayName);
// Invoke the endpoint
await context.Endpoint.RequestDelegate(httpContext);
Assert.Equal(405, httpContext.Response.StatusCode);
Assert.Equal("DELETE, GET, PUT", httpContext.Response.Headers["Allow"]);
// Invoke the endpoint again to verify headers not duplicated
await context.Endpoint.RequestDelegate(httpContext);
Assert.Equal(405, httpContext.Response.StatusCode);
Assert.Equal("DELETE, GET, PUT", httpContext.Response.Headers["Allow"]);
}
private static Matcher CreateMatcher(params RouteEndpoint[] endpoints) private static Matcher CreateMatcher(params RouteEndpoint[] endpoints)
{ {
var services = new ServiceCollection() var services = new ServiceCollection()