I am writing a simple .NET 7 web application that will respond to any GET request by serving the same Razor page. This is my Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.MapFallbackToPage("/Index");
app.Run();
A request to https://localhost:7024/ returns my Razor page as expected. However, a request to https://localhost:7024/anythingelse throws an exception:
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints. Matches:
/Index
/Indexat Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ReportAmbiguity(CandidateState[] candidateState) at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.ProcessFinalCandidates(HttpContext httpContext, CandidateState[] candidateState) at Microsoft.AspNetCore.Routing.Matching.DefaultEndpointSelector.SelectAsync(HttpContext httpContext, CandidateSet candidateSet) at Microsoft.AspNetCore.Routing.Matching.DfaMatcher.SelectEndpointWithPoliciesAsync(HttpContext httpContext, IEndpointSelectorPolicy[] policies, CandidateSet candidateSet) at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.g__AwaitMatch|8_1(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task matchTask) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
My initial answer did not address your question: "Why do I get this...?" It just provided how to resolve the AmbiguousMatchException error. Here's an explanation of why this error occurs, along with a code sample that shows what's happening.
ASP.NET Core is creating multiple routes for /Index
: 1. for the Razor Page with the file name Index.cshtml
; and 2. for the root URL of the web app (or https://localhost:7024/
in your question).
Navigating to https://localhost:7024/
or https://localhost:7024/index
works because these two route patterns have a clear match.
When app.MapFallbackToPage("/Index");
is set in program.cs
, with the first parameter set to the page name: /Index
, the request is routed to the page endpoint /Index
. The ambiguous exception occurs because there are two routes that match the endpoint for /Index
.
Adding a catch-all route template (i.e. starting a route template with an asterik *
followed by any word) as @Mike added in his answer, removes ambiguity.
@page "{*anyword}"
The catch-all route pattern, the second list item in the image below, matches https://localhost:7024/anythingelse
.
Using a catch-all route pattern, however, in the @page
directive in the Index.cshtml
file in the root folder makes app.MapFallbackToPage("/Index");
redundant. All request paths will be processed by the catch-all route pattern.
Add "/Index" to the @page
directive in the main index page also removes ambiguity. Only one endpoint to /Index
is registered.
@page "/Index"
@model WebApplication1.Pages.IndexModel
@{
}
<h1>Welcome</h1>
The following code sample was used generate the image results above. Replace the first line in Index.cshtml
, that is, the @page
directive with any of the following to get the output of the images above, in order:
@page
@page "{*anyword}"
@page "/Index"
Index.cshtml
@page "/Index"
@model WebApplication1.Pages.IndexModel
<table class="table">
<thead>
<tr>
<th>Display Name</th>
<th>Route Pattern</th>
<th>Url</th>
</tr>
</thead>
<tbody>
@foreach (EndpointData endpointData in @Model.EndpointDataList)
{
<tr>
<td>@endpointData.DisplayName</td>
<td>@endpointData.RoutePattern</td>
<td>@endpointData.URL</td>
</tr>
}
</tbody>
</table>
Index.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace WebApplication1.Pages
{
public class IndexModel : PageModel
{
private readonly IEnumerable<EndpointDataSource> _endpointSources;
public IndexModel(
IEnumerable<EndpointDataSource> endpointDataSources)
{
_endpointSources = endpointDataSources;
}
public IEnumerable<RouteEndpoint> EndpointSources { get; set; }
public List<EndpointData> EndpointDataList { get; set; }
public void OnGet()
{
EndpointSources = _endpointSources
.SelectMany(es => es.Endpoints)
.OfType<RouteEndpoint>();
EndpointDataList = new List<EndpointData>();
foreach (RouteEndpoint routeEndpoint in EndpointSources)
{
// If the route pattern is an empty string then
// it represents the home page. The home page is
// represented by the route pattern '/Index'.
EndpointData pageData = new()
{
DisplayName = routeEndpoint.DisplayName,
RoutePattern = routeEndpoint.RoutePattern.RawText,
URL = routeEndpoint.ToString()
};
EndpointDataList.Add(pageData);
}
}
}
public class EndpointData
{
public string DisplayName { get; set; }
public string RoutePattern { get; set; }
public string URL { get; set; }
}
}
Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.MapFallbackToPage("/Index");
app.Run();