Skip to content

Commit b88bc69

Browse files
Add ability to modify UseRouting/UseEndpoint behavior for WebApplicationBuilder (#35336)
1 parent c894584 commit b88bc69

File tree

5 files changed

+385
-53
lines changed

5 files changed

+385
-53
lines changed

src/DefaultBuilder/src/WebApplication.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@ public sealed class WebApplication : IHost, IApplicationBuilder, IEndpointRouteB
2323
private readonly IHost _host;
2424
private readonly List<EndpointDataSource> _dataSources = new();
2525

26+
internal static string GlobalEndpointRouteBuilderKey = "__GlobalEndpointRouteBuilder";
27+
2628
internal WebApplication(IHost host)
2729
{
2830
_host = host;
2931
ApplicationBuilder = new ApplicationBuilder(host.Services);
3032
Logger = host.Services.GetRequiredService<ILoggerFactory>().CreateLogger(Environment.ApplicationName);
33+
34+
Properties[GlobalEndpointRouteBuilderKey] = this;
3135
}
3236

3337
/// <summary>
@@ -170,15 +174,21 @@ public void Run(string? url = null)
170174
RequestDelegate IApplicationBuilder.Build() => BuildRequestDelegate();
171175

172176
// REVIEW: Should this be wrapping another type?
173-
IApplicationBuilder IApplicationBuilder.New() => ApplicationBuilder.New();
177+
IApplicationBuilder IApplicationBuilder.New()
178+
{
179+
var newBuilder = ApplicationBuilder.New();
180+
// Remove the route builder so branched pipelines have their own routing world
181+
newBuilder.Properties.Remove(GlobalEndpointRouteBuilderKey);
182+
return newBuilder;
183+
}
174184

175185
IApplicationBuilder IApplicationBuilder.Use(Func<RequestDelegate, RequestDelegate> middleware)
176186
{
177187
ApplicationBuilder.Use(middleware);
178188
return this;
179189
}
180190

181-
IApplicationBuilder IEndpointRouteBuilder.CreateApplicationBuilder() => ApplicationBuilder.New();
191+
IApplicationBuilder IEndpointRouteBuilder.CreateApplicationBuilder() => ((IApplicationBuilder)this).New();
182192

183193
private void Listen(string? url)
184194
{

src/DefaultBuilder/src/WebApplicationBuilder.cs

Lines changed: 32 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Diagnostics;
55
using System.Reflection;
66
using Microsoft.AspNetCore.Hosting;
7-
using Microsoft.AspNetCore.Routing;
87
using Microsoft.Extensions.Configuration;
98
using Microsoft.Extensions.DependencyInjection;
109
using Microsoft.Extensions.Hosting;
@@ -17,11 +16,10 @@ namespace Microsoft.AspNetCore.Builder
1716
/// </summary>
1817
public sealed class WebApplicationBuilder
1918
{
20-
private const string EndpointRouteBuilderKey = "__EndpointRouteBuilder";
21-
2219
private readonly HostBuilder _hostBuilder = new();
2320
private readonly BootstrapHostBuilder _bootstrapHostBuilder;
2421
private readonly WebApplicationServiceCollection _services = new();
22+
private const string EndpointRouteBuilderKey = "__EndpointRouteBuilder";
2523

2624
private WebApplication? _builtApplication;
2725

@@ -187,48 +185,39 @@ private void ConfigureApplication(WebHostBuilderContext context, IApplicationBui
187185
{
188186
Debug.Assert(_builtApplication is not null);
189187

188+
// UseRouting called before WebApplication such as in a StartupFilter
189+
// lets remove the property and reset it at the end so we don't mess with the routes in the filter
190+
if (app.Properties.TryGetValue(EndpointRouteBuilderKey, out var priorRouteBuilder))
191+
{
192+
app.Properties.Remove(EndpointRouteBuilderKey);
193+
}
194+
190195
if (context.HostingEnvironment.IsDevelopment())
191196
{
197+
// TODO: add test for this
192198
app.UseDeveloperExceptionPage();
193199
}
194200

195-
var implicitRouting = false;
201+
// Wrap the entire destination pipeline in UseRouting() and UseEndpoints(), essentially:
202+
// destination.UseRouting()
203+
// destination.Run(source)
204+
// destination.UseEndpoints()
205+
206+
// Set the route builder so that UseRouting will use the WebApplication as the IEndpointRouteBuilder for route matching
207+
app.Properties.Add(WebApplication.GlobalEndpointRouteBuilderKey, _builtApplication);
196208

197-
// The endpoints were already added on the outside
209+
// Only call UseRouting() if there are endpoints configured and UseRouting() wasn't called on the global route builder already
198210
if (_builtApplication.DataSources.Count > 0)
199211
{
200-
// The user did not register the routing middleware so wrap the entire
201-
// destination pipeline in UseRouting() and UseEndpoints(), essentially:
202-
// destination.UseRouting()
203-
// destination.Run(source)
204-
// destination.UseEndpoints()
205-
206-
// Copy endpoints to the IEndpointRouteBuilder created by an explicit call to UseRouting() if possible.
207-
var targetRouteBuilder = GetEndpointRouteBuilder(_builtApplication);
208-
209-
if (targetRouteBuilder is null)
212+
// If this is set, someone called UseRouting() when a global route builder was already set
213+
if (!_builtApplication.Properties.TryGetValue(EndpointRouteBuilderKey, out var localRouteBuilder))
210214
{
211-
// The app defined endpoints without calling UseRouting() explicitly, so call UseRouting() implicitly.
212215
app.UseRouting();
213-
214-
// An implicitly created IEndpointRouteBuilder was addeded to app.Properties by the UseRouting() call above.
215-
targetRouteBuilder = GetEndpointRouteBuilder(app)!;
216-
implicitRouting = true;
217216
}
218-
219-
// Copy the endpoints to the explicitly or implicitly created IEndopintRouteBuilder.
220-
foreach (var ds in _builtApplication.DataSources)
217+
else
221218
{
222-
targetRouteBuilder.DataSources.Add(ds);
223-
}
224-
225-
// UseEndpoints consumes the DataSources immediately to populate CompositeEndpointDataSource via RouteOptions,
226-
// so it must be called after we copy the endpoints.
227-
if (!implicitRouting)
228-
{
229-
// UseRouting() was called explicitely, but we may still need to call UseEndpoints() implicitely at
230-
// the end of the pipeline.
231-
_builtApplication.UseEndpoints(_ => { });
219+
// UseEndpoints will be looking for the RouteBuilder so make sure it's set
220+
app.Properties[EndpointRouteBuilderKey] = localRouteBuilder;
232221
}
233222
}
234223

@@ -239,11 +228,9 @@ private void ConfigureApplication(WebHostBuilderContext context, IApplicationBui
239228
return _builtApplication.BuildRequestDelegate();
240229
});
241230

242-
// Implicitly call UseEndpoints() at the end of the pipeline if UseRouting() was called implicitly.
243-
// We could add this to the end of _buildApplication instead if UseEndpoints() was not so picky about
244-
// being called with the same IApplicationBluilder instance as UseRouting().
245-
if (implicitRouting)
231+
if (_builtApplication.DataSources.Count > 0)
246232
{
233+
// We don't know if user code called UseEndpoints(), so we will call it just in case, UseEndpoints() will ignore duplicate DataSources
247234
app.UseEndpoints(_ => { });
248235
}
249236

@@ -252,12 +239,15 @@ private void ConfigureApplication(WebHostBuilderContext context, IApplicationBui
252239
{
253240
app.Properties[item.Key] = item.Value;
254241
}
255-
}
256242

257-
private static IEndpointRouteBuilder? GetEndpointRouteBuilder(IApplicationBuilder app)
258-
{
259-
app.Properties.TryGetValue(EndpointRouteBuilderKey, out var value);
260-
return (IEndpointRouteBuilder?)value;
243+
// Remove the route builder to clean up the properties, we're done adding routes to the pipeline
244+
app.Properties.Remove(WebApplication.GlobalEndpointRouteBuilderKey);
245+
246+
// reset route builder if it existed, this is needed for StartupFilters
247+
if (priorRouteBuilder is not null)
248+
{
249+
app.Properties[EndpointRouteBuilderKey] = priorRouteBuilder;
250+
}
261251
}
262252

263253
private class LoggingBuilder : ILoggingBuilder

0 commit comments

Comments
 (0)