diff --git a/src/OrchardCore.Modules/OrchardCore.Localization/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Localization/Startup.cs
index 8c825301c3d..73b06eab34c 100644
--- a/src/OrchardCore.Modules/OrchardCore.Localization/Startup.cs
+++ b/src/OrchardCore.Modules/OrchardCore.Localization/Startup.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
@@ -41,12 +42,12 @@ public override void ConfigureServices(IServiceCollection services)
}
///
- public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
+ public override async ValueTask ConfigureAsync(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
{
var localizationService = serviceProvider.GetService();
- var defaultCulture = localizationService.GetDefaultCultureAsync().GetAwaiter().GetResult();
- var supportedCultures = localizationService.GetSupportedCulturesAsync().GetAwaiter().GetResult();
+ var defaultCulture = await localizationService.GetDefaultCultureAsync();
+ var supportedCultures = await localizationService.GetSupportedCulturesAsync();
var localizationOptions = serviceProvider.GetService>().Value;
var ignoreSystemSettings = serviceProvider.GetService>().Value.IgnoreSystemSettings;
diff --git a/src/OrchardCore/OrchardCore.Abstractions/Modules/Builder/OrchardCoreBuilder.cs b/src/OrchardCore/OrchardCore.Abstractions/Modules/Builder/OrchardCoreBuilder.cs
index ba41209dfc6..b3c679717ea 100644
--- a/src/OrchardCore/OrchardCore.Abstractions/Modules/Builder/OrchardCoreBuilder.cs
+++ b/src/OrchardCore/OrchardCore.Abstractions/Modules/Builder/OrchardCoreBuilder.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using OrchardCore.Environment.Shell.Builders;
@@ -97,6 +98,46 @@ public OrchardCoreBuilder Configure(Action configure, int o
return Configure((app, routes, sp) => configure(app), order);
}
+ ///
+ /// This async action gets called for each tenant. Use this method to configure the tenant pipeline.
+ ///
+ /// The async action to execute when configuring the request's pipeline for a tenant.
+ /// The order of the action to execute. Lower values will be executed first.
+ public OrchardCoreBuilder Configure(Func configureAsync, int order = 0)
+ {
+ if (!_actions.TryGetValue(order, out var actions))
+ {
+ actions = _actions[order] = new StartupActions(order);
+
+ ApplicationServices.AddTransient(sp => new StartupActionsStartup(
+ sp.GetRequiredService(), actions, order));
+ }
+
+ actions.AsyncConfigureActions.Add(configureAsync);
+
+ return this;
+ }
+
+ ///
+ /// This async action gets called for each tenant. Use this method to configure the tenant pipeline.
+ ///
+ /// The async action to execute when configuring the request's pipeline for a tenant.
+ /// The order of the action to execute. Lower values will be executed first.
+ public OrchardCoreBuilder Configure(Func configureAsync, int order = 0)
+ {
+ return Configure((app, routes, sp) => configureAsync(app, routes), order);
+ }
+
+ ///
+ /// This async action gets called for each tenant. Use this method to configure the tenant pipeline.
+ ///
+ /// The async action to execute when configuring the request's pipeline for a tenant.
+ /// The order of the action to execute. Lower values will be executed first.
+ public OrchardCoreBuilder Configure(Func configureAsync, int order = 0)
+ {
+ return Configure((app, routes, sp) => configureAsync(app), order);
+ }
+
public OrchardCoreBuilder EnableFeature(string id)
{
return ConfigureServices(services =>
diff --git a/src/OrchardCore/OrchardCore.Abstractions/Modules/Builder/StartupActions.cs b/src/OrchardCore/OrchardCore.Abstractions/Modules/Builder/StartupActions.cs
index 7fd53da5a12..bb34dfafe2b 100644
--- a/src/OrchardCore/OrchardCore.Abstractions/Modules/Builder/StartupActions.cs
+++ b/src/OrchardCore/OrchardCore.Abstractions/Modules/Builder/StartupActions.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
@@ -14,8 +15,13 @@ public StartupActions(int order)
public int Order { get; }
- public ICollection> ConfigureServicesActions { get; } = new List>();
+ public ICollection> ConfigureServicesActions { get; } =
+ new List>();
- public ICollection> ConfigureActions { get; } = new List>();
+ public ICollection> ConfigureActions { get; } =
+ new List>();
+
+ public ICollection> AsyncConfigureActions { get; } =
+ new List>();
}
}
diff --git a/src/OrchardCore/OrchardCore.Abstractions/Modules/Builder/StartupActionsStartup.cs b/src/OrchardCore/OrchardCore.Abstractions/Modules/Builder/StartupActionsStartup.cs
index 603ecbe7e83..20273aaf0d9 100644
--- a/src/OrchardCore/OrchardCore.Abstractions/Modules/Builder/StartupActionsStartup.cs
+++ b/src/OrchardCore/OrchardCore.Abstractions/Modules/Builder/StartupActionsStartup.cs
@@ -1,4 +1,5 @@
using System;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
@@ -37,5 +38,18 @@ public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder ro
configure?.Invoke(app, routes, serviceProvider);
}
}
+
+ public override async ValueTask ConfigureAsync(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
+ {
+ foreach (var asyncConfigure in _actions.AsyncConfigureActions)
+ {
+ if (asyncConfigure is null)
+ {
+ continue;
+ }
+
+ await asyncConfigure(app, routes, serviceProvider);
+ }
+ }
}
}
diff --git a/src/OrchardCore/OrchardCore.Abstractions/Modules/IAsyncStartup.cs b/src/OrchardCore/OrchardCore.Abstractions/Modules/IAsyncStartup.cs
new file mode 100644
index 00000000000..24519cea75e
--- /dev/null
+++ b/src/OrchardCore/OrchardCore.Abstractions/Modules/IAsyncStartup.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Routing;
+
+namespace OrchardCore.Modules
+{
+ ///
+ /// An implementation of this interface allows to configure asynchronously the tenant pipeline.
+ ///
+ public interface IAsyncStartup
+ {
+ ///
+ /// This method gets called by the runtime. Use this method to configure the tenant pipeline.
+ ///
+ ///
+ ///
+ ///
+ ValueTask ConfigureAsync(IApplicationBuilder builder, IEndpointRouteBuilder routes, IServiceProvider serviceProvider);
+ }
+}
diff --git a/src/OrchardCore/OrchardCore.Abstractions/Modules/StartupBase.cs b/src/OrchardCore/OrchardCore.Abstractions/Modules/StartupBase.cs
index e24c6972224..97087c5dbbd 100644
--- a/src/OrchardCore/OrchardCore.Abstractions/Modules/StartupBase.cs
+++ b/src/OrchardCore/OrchardCore.Abstractions/Modules/StartupBase.cs
@@ -1,11 +1,12 @@
using System;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
namespace OrchardCore.Modules
{
- public abstract class StartupBase : IStartup
+ public abstract class StartupBase : IStartup, IAsyncStartup
{
///
public virtual int Order { get; } = 0;
@@ -22,5 +23,8 @@ public virtual void ConfigureServices(IServiceCollection services)
public virtual void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
{
}
+
+ ///
+ public virtual ValueTask ConfigureAsync(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) => default;
}
}
diff --git a/src/OrchardCore/OrchardCore/Modules/Extensions/ShellPipelineExtensions.cs b/src/OrchardCore/OrchardCore/Modules/Extensions/ShellPipelineExtensions.cs
index 26dc7b4b252..21a04fa9756 100644
--- a/src/OrchardCore/OrchardCore/Modules/Extensions/ShellPipelineExtensions.cs
+++ b/src/OrchardCore/OrchardCore/Modules/Extensions/ShellPipelineExtensions.cs
@@ -8,6 +8,7 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.Environment.Shell;
using OrchardCore.Environment.Shell.Builders;
@@ -17,6 +18,8 @@ namespace OrchardCore.Modules;
public static class ShellPipelineExtensions
{
+ private const string EndpointRouteBuilder = "__EndpointRouteBuilder";
+
private static readonly ConcurrentDictionary _semaphores = new();
///
@@ -31,7 +34,7 @@ public static async Task BuildPipelineAsync(this ShellContext context)
{
if (!context.HasPipeline())
{
- context.Pipeline = context.BuildPipeline();
+ context.Pipeline = await context.BuildPipelineInternalAsync();
}
}
finally
@@ -43,22 +46,20 @@ public static async Task BuildPipelineAsync(this ShellContext context)
///
/// Builds the tenant pipeline.
///
- private static IShellPipeline BuildPipeline(this ShellContext context)
+ private static async ValueTask BuildPipelineInternalAsync(this ShellContext context)
{
var features = context.ServiceProvider.GetService()?.Features;
var builder = new ApplicationBuilder(context.ServiceProvider, features ?? new FeatureCollection());
var startupFilters = builder.ApplicationServices.GetService>();
- Action configure = builder =>
- {
- ConfigurePipeline(builder);
- };
-
+ Action configure = builder => { };
foreach (var filter in startupFilters.Reverse())
{
configure = filter.Configure(configure);
}
+ await ConfigurePipelineAsync(builder);
+
configure(builder);
var shellPipeline = new ShellRequestPipeline
@@ -72,19 +73,35 @@ private static IShellPipeline BuildPipeline(this ShellContext context)
///
/// Configures the tenant pipeline.
///
- private static void ConfigurePipeline(IApplicationBuilder builder)
+ private static async ValueTask ConfigurePipelineAsync(IApplicationBuilder builder)
{
// 'IStartup' instances are ordered by module dependencies with a 'ConfigureOrder' of 0 by default.
// 'OrderBy' performs a stable sort, so the order is preserved among equal 'ConfigureOrder' values.
var startups = builder.ApplicationServices.GetServices().OrderBy(s => s.ConfigureOrder);
+ // Should be done first.
+ builder.UseRouting();
+
+ // Try to retrieve the current 'IEndpointRouteBuilder'.
+ if (!builder.Properties.TryGetValue(EndpointRouteBuilder, out var obj) ||
+ obj is not IEndpointRouteBuilder routes)
+ {
+ throw new InvalidOperationException("Failed to retrieve the current endpoint route builder.");
+ }
+
+ // Routes can be then configured outside 'UseEndpoints()'.
var services = ShellScope.Services;
- builder.UseRouting().UseEndpoints(routes =>
+ foreach (var startup in startups)
{
- foreach (var startup in startups)
+ if (startup is IAsyncStartup asyncStartup)
{
- startup.Configure(builder, routes, services);
+ await asyncStartup.ConfigureAsync(builder, routes, services);
}
- });
+
+ startup.Configure(builder, routes, services);
+ }
+
+ // Knowing that routes are already configured.
+ builder.UseEndpoints(routes => { });
}
}