Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Startup Async Configure #14801

Merged
merged 8 commits into from
Dec 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/OrchardCore.Modules/OrchardCore.Localization/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -41,12 +42,12 @@ public override void ConfigureServices(IServiceCollection services)
}

/// <inheritdocs />
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<ILocalizationService>();

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<IOptions<RequestLocalizationOptions>>().Value;
var ignoreSystemSettings = serviceProvider.GetService<IOptions<CultureOptions>>().Value.IgnoreSystemSettings;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -97,6 +98,46 @@ public OrchardCoreBuilder Configure(Action<IApplicationBuilder> configure, int o
return Configure((app, routes, sp) => configure(app), order);
}

/// <summary>
/// This async action gets called for each tenant. Use this method to configure the tenant pipeline.
/// </summary>
/// <param name="configureAsync">The async action to execute when configuring the request's pipeline for a tenant.</param>
/// <param name="order">The order of the action to execute. Lower values will be executed first.</param>
public OrchardCoreBuilder Configure(Func<IApplicationBuilder, IEndpointRouteBuilder, IServiceProvider, ValueTask> configureAsync, int order = 0)
{
if (!_actions.TryGetValue(order, out var actions))
{
actions = _actions[order] = new StartupActions(order);

ApplicationServices.AddTransient<IStartup>(sp => new StartupActionsStartup(
sp.GetRequiredService<IServiceProvider>(), actions, order));
}

actions.AsyncConfigureActions.Add(configureAsync);

return this;
}

/// <summary>
/// This async action gets called for each tenant. Use this method to configure the tenant pipeline.
/// </summary>
/// <param name="configureAsync">The async action to execute when configuring the request's pipeline for a tenant.</param>
/// <param name="order">The order of the action to execute. Lower values will be executed first.</param>
public OrchardCoreBuilder Configure(Func<IApplicationBuilder, IEndpointRouteBuilder, ValueTask> configureAsync, int order = 0)
{
return Configure((app, routes, sp) => configureAsync(app, routes), order);
}

/// <summary>
/// This async action gets called for each tenant. Use this method to configure the tenant pipeline.
/// </summary>
/// <param name="configureAsync">The async action to execute when configuring the request's pipeline for a tenant.</param>
/// <param name="order">The order of the action to execute. Lower values will be executed first.</param>
public OrchardCoreBuilder Configure(Func<IApplicationBuilder, ValueTask> configureAsync, int order = 0)
{
return Configure((app, routes, sp) => configureAsync(app), order);
}

public OrchardCoreBuilder EnableFeature(string id)
{
return ConfigureServices(services =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;

Expand All @@ -14,8 +15,13 @@ public StartupActions(int order)

public int Order { get; }

public ICollection<Action<IServiceCollection, IServiceProvider>> ConfigureServicesActions { get; } = new List<Action<IServiceCollection, IServiceProvider>>();
public ICollection<Action<IServiceCollection, IServiceProvider>> ConfigureServicesActions { get; } =
new List<Action<IServiceCollection, IServiceProvider>>();

public ICollection<Action<IApplicationBuilder, IEndpointRouteBuilder, IServiceProvider>> ConfigureActions { get; } = new List<Action<IApplicationBuilder, IEndpointRouteBuilder, IServiceProvider>>();
public ICollection<Action<IApplicationBuilder, IEndpointRouteBuilder, IServiceProvider>> ConfigureActions { get; } =
new List<Action<IApplicationBuilder, IEndpointRouteBuilder, IServiceProvider>>();

public ICollection<Func<IApplicationBuilder, IEndpointRouteBuilder, IServiceProvider, ValueTask>> AsyncConfigureActions { get; } =
new List<Func<IApplicationBuilder, IEndpointRouteBuilder, IServiceProvider, ValueTask>>();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -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);
}
}
}
}
21 changes: 21 additions & 0 deletions src/OrchardCore/OrchardCore.Abstractions/Modules/IAsyncStartup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;

namespace OrchardCore.Modules
{
/// <summary>
/// An implementation of this interface allows to configure asynchronously the tenant pipeline.
/// </summary>
public interface IAsyncStartup
{
/// <summary>
/// This method gets called by the runtime. Use this method to configure the tenant pipeline.
/// </summary>
/// <param name="builder"></param>
/// <param name="routes"></param>
/// <param name="serviceProvider"></param>
ValueTask ConfigureAsync(IApplicationBuilder builder, IEndpointRouteBuilder routes, IServiceProvider serviceProvider);
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <inheritdoc />
public virtual int Order { get; } = 0;
Expand All @@ -22,5 +23,8 @@ public virtual void ConfigureServices(IServiceCollection services)
public virtual void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)
{
}

/// <inheritdoc />
public virtual ValueTask ConfigureAsync(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider) => default;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,6 +18,8 @@ namespace OrchardCore.Modules;

public static class ShellPipelineExtensions
{
private const string EndpointRouteBuilder = "__EndpointRouteBuilder";

private static readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphores = new();

/// <summary>
Expand All @@ -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
Expand All @@ -43,22 +46,20 @@ public static async Task BuildPipelineAsync(this ShellContext context)
/// <summary>
/// Builds the tenant pipeline.
/// </summary>
private static IShellPipeline BuildPipeline(this ShellContext context)
private static async ValueTask<IShellPipeline> BuildPipelineInternalAsync(this ShellContext context)
{
var features = context.ServiceProvider.GetService<IServer>()?.Features;
var builder = new ApplicationBuilder(context.ServiceProvider, features ?? new FeatureCollection());
var startupFilters = builder.ApplicationServices.GetService<IEnumerable<IStartupFilter>>();

Action<IApplicationBuilder> configure = builder =>
{
ConfigurePipeline(builder);
};

Action<IApplicationBuilder> configure = builder => { };
foreach (var filter in startupFilters.Reverse())
{
configure = filter.Configure(configure);
}

await ConfigurePipelineAsync(builder);

configure(builder);

var shellPipeline = new ShellRequestPipeline
Expand All @@ -72,19 +73,35 @@ private static IShellPipeline BuildPipeline(this ShellContext context)
/// <summary>
/// Configures the tenant pipeline.
/// </summary>
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<IStartup>().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 => { });
}
}