Skip to content

Commit 6706a77

Browse files
Improve file fallback routing behavior when using ConsumesAttribute (#43984)
* File fallback fix when using ConsumesAttribute * PR feedback
1 parent 430fd46 commit 6706a77

File tree

4 files changed

+75
-53
lines changed

4 files changed

+75
-53
lines changed

src/Middleware/StaticFiles/src/StaticFilesEndpointRouteBuilderExtensions.cs

Lines changed: 28 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ namespace Microsoft.AspNetCore.Builder;
1313
/// </summary>
1414
public static class StaticFilesEndpointRouteBuilderExtensions
1515
{
16+
// By explicitly stating the supported HTTP methods for static files,
17+
// we limit the types of situations where the fallback to file is matched
18+
// after an endpoint is discarded, for example, due to a mismatched content type.
19+
// See: https://github.com/dotnet/aspnetcore/issues/41060
20+
private static readonly string[] _supportedHttpMethods = new[] { HttpMethods.Get, HttpMethods.Head };
21+
1622
/// <summary>
1723
/// Adds a specialized <see cref="RouteEndpoint"/> to the <see cref="IEndpointRouteBuilder"/> that will match
1824
/// requests for non-filenames with the lowest possible priority. The request will be routed to a
@@ -42,17 +48,12 @@ public static IEndpointConventionBuilder MapFallbackToFile(
4248
this IEndpointRouteBuilder endpoints,
4349
string filePath)
4450
{
45-
if (endpoints == null)
46-
{
47-
throw new ArgumentNullException(nameof(endpoints));
48-
}
51+
ArgumentNullException.ThrowIfNull(endpoints);
52+
ArgumentNullException.ThrowIfNull(filePath);
4953

50-
if (filePath == null)
51-
{
52-
throw new ArgumentNullException(nameof(filePath));
53-
}
54-
55-
return endpoints.MapFallback(CreateRequestDelegate(endpoints, filePath));
54+
return endpoints
55+
.MapFallback(CreateRequestDelegate(endpoints, filePath))
56+
.WithMetadata(new HttpMethodMetadata(_supportedHttpMethods));
5657
}
5758

5859
/// <summary>
@@ -83,17 +84,12 @@ public static IEndpointConventionBuilder MapFallbackToFile(
8384
string filePath,
8485
StaticFileOptions options)
8586
{
86-
if (endpoints == null)
87-
{
88-
throw new ArgumentNullException(nameof(endpoints));
89-
}
90-
91-
if (filePath == null)
92-
{
93-
throw new ArgumentNullException(nameof(filePath));
94-
}
87+
ArgumentNullException.ThrowIfNull(endpoints);
88+
ArgumentNullException.ThrowIfNull(filePath);
9589

96-
return endpoints.MapFallback(CreateRequestDelegate(endpoints, filePath, options));
90+
return endpoints
91+
.MapFallback(CreateRequestDelegate(endpoints, filePath, options))
92+
.WithMetadata(new HttpMethodMetadata(_supportedHttpMethods));
9793
}
9894

9995
/// <summary>
@@ -130,22 +126,13 @@ public static IEndpointConventionBuilder MapFallbackToFile(
130126
[StringSyntax("Route")] string pattern,
131127
string filePath)
132128
{
133-
if (endpoints == null)
134-
{
135-
throw new ArgumentNullException(nameof(endpoints));
136-
}
129+
ArgumentNullException.ThrowIfNull(endpoints);
130+
ArgumentNullException.ThrowIfNull(pattern);
131+
ArgumentNullException.ThrowIfNull(filePath);
137132

138-
if (pattern == null)
139-
{
140-
throw new ArgumentNullException(nameof(filePath));
141-
}
142-
143-
if (filePath == null)
144-
{
145-
throw new ArgumentNullException(nameof(filePath));
146-
}
147-
148-
return endpoints.MapFallback(pattern, CreateRequestDelegate(endpoints, filePath));
133+
return endpoints
134+
.MapFallback(pattern, CreateRequestDelegate(endpoints, filePath))
135+
.WithMetadata(new HttpMethodMetadata(_supportedHttpMethods));
149136
}
150137

151138
/// <summary>
@@ -181,22 +168,13 @@ public static IEndpointConventionBuilder MapFallbackToFile(
181168
string filePath,
182169
StaticFileOptions options)
183170
{
184-
if (endpoints == null)
185-
{
186-
throw new ArgumentNullException(nameof(endpoints));
187-
}
188-
189-
if (pattern == null)
190-
{
191-
throw new ArgumentNullException(nameof(filePath));
192-
}
193-
194-
if (filePath == null)
195-
{
196-
throw new ArgumentNullException(nameof(filePath));
197-
}
171+
ArgumentNullException.ThrowIfNull(endpoints);
172+
ArgumentNullException.ThrowIfNull(pattern);
173+
ArgumentNullException.ThrowIfNull(filePath);
198174

199-
return endpoints.MapFallback(pattern, CreateRequestDelegate(endpoints, filePath, options));
175+
return endpoints
176+
.MapFallback(pattern, CreateRequestDelegate(endpoints, filePath, options))
177+
.WithMetadata(new HttpMethodMetadata(_supportedHttpMethods));
200178
}
201179

202180
private static RequestDelegate CreateRequestDelegate(

src/Mvc/test/Mvc.FunctionalTests/RoutingFallbackTest.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Net;
55
using System.Net.Http;
6+
using System.Text;
7+
using Microsoft.AspNetCore.Builder;
68
using Microsoft.AspNetCore.Hosting;
9+
using Microsoft.AspNetCore.Mvc.Testing;
710

811
namespace Microsoft.AspNetCore.Mvc.FunctionalTests;
912

@@ -102,7 +105,7 @@ public async Task Fallback_CanFallbackToControllerInAreaPost()
102105
public async Task Fallback_CanFallbackToPage()
103106
{
104107
// Arrange
105-
var url = "http://localhost/Foo";
108+
var url = "http://localhost/FallbackToPage/Foo/Bar";
106109
var request = new HttpRequestMessage(HttpMethod.Get, url);
107110

108111
// Act
@@ -113,4 +116,21 @@ public async Task Fallback_CanFallbackToPage()
113116
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
114117
Assert.Equal("Hello from fallback page: /FallbackPage", content);
115118
}
119+
120+
[Fact]
121+
public async Task Fallback_DoesNotFallbackToFile_WhenContentTypeDoesNotMatchConsumesAttribute()
122+
{
123+
// Arrange
124+
var url = "http://localhost/ConsumesAttribute/Json";
125+
var request = new HttpRequestMessage(HttpMethod.Post, url)
126+
{
127+
Content = new StringContent("some plaintext", Encoding.UTF8, "text/plain"),
128+
};
129+
130+
// Act
131+
var response = await Client.SendAsync(request);
132+
133+
// Assert
134+
Assert.Equal(HttpStatusCode.UnsupportedMediaType, response.StatusCode);
135+
}
116136
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.AspNetCore.Mvc;
5+
6+
namespace Mvc.RoutingWebSite.Controllers;
7+
8+
[Route("ConsumesAttribute/[action]")]
9+
public class ConsumesAttributeController : Controller
10+
{
11+
[HttpPost]
12+
[Consumes("application/json")]
13+
public IActionResult Json([FromBody] string json)
14+
{
15+
return Content($"Received json \"{json}\"");
16+
}
17+
}

src/Mvc/test/WebSites/RoutingWebSite/StartupForFallback.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.AspNetCore.Mvc;
5+
using Microsoft.AspNetCore.Mvc.Infrastructure;
6+
47
namespace RoutingWebSite;
58

69
// For by tests for fallback routing to pages/controllers
@@ -12,6 +15,9 @@ public void ConfigureServices(IServiceCollection services)
1215
.AddMvc()
1316
.AddNewtonsoftJson();
1417

18+
services.AddScoped<TestResponseGenerator>();
19+
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
20+
1521
// Used by some controllers defined in this project.
1622
services.Configure<RouteOptions>(options => options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer));
1723
}
@@ -22,7 +28,8 @@ public void Configure(IApplicationBuilder app)
2228
app.UseEndpoints(endpoints =>
2329
{
2430
endpoints.MapFallbackToAreaController("admin/{*path:nonfile}", "Index", "Fallback", "Admin");
25-
endpoints.MapFallbackToPage("/FallbackPage");
31+
endpoints.MapFallbackToPage("FallbackToPage/{*path:nonfile}", "/FallbackPage");
32+
endpoints.MapFallbackToFile("notfound.html");
2633

2734
endpoints.MapControllerRoute("admin", "link_generation/{area}/{controller}/{action}/{id?}");
2835
});

0 commit comments

Comments
 (0)