diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/Microsoft.Extensions.HostFactoryResolver.slnx b/src/libraries/Microsoft.Extensions.HostFactoryResolver/Microsoft.Extensions.HostFactoryResolver.slnx index e5924abe732345..5c7297b37293cd 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/Microsoft.Extensions.HostFactoryResolver.slnx +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/Microsoft.Extensions.HostFactoryResolver.slnx @@ -1279,6 +1279,14 @@ + + + + + + + + diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs index cc8a86d84e1a56..7886081c262545 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs @@ -63,7 +63,8 @@ private static TimeSpan SetupDefaultTimeout() TimeSpan? waitTimeout = null, bool stopApplication = true, Action? configureHostBuilder = null, - Action? entrypointCompleted = null) + Action? entrypointCompleted = null, + IDictionary>? arbitraryActions = null) { if (assembly.EntryPoint is null) { @@ -89,7 +90,7 @@ private static TimeSpan SetupDefaultTimeout() return null; } - return args => new HostingListener(args, assembly.EntryPoint, waitTimeout ?? s_defaultWaitTimeout, stopApplication, configureHostBuilder, entrypointCompleted).CreateHost(); + return args => new HostingListener(args, assembly.EntryPoint, waitTimeout ?? s_defaultWaitTimeout, stopApplication, configureHostBuilder, entrypointCompleted, arbitraryActions).CreateHost(); } private static Func? ResolveFactory(Assembly assembly, string name) @@ -204,9 +205,17 @@ private sealed class HostingListener : IObserver, IObserver< private IDisposable? _disposable; private readonly Action? _configure; private readonly Action? _entrypointCompleted; + private readonly IDictionary>? _arbitraryActions; private static readonly AsyncLocal _currentListener = new(); - public HostingListener(string[] args, MethodInfo entryPoint, TimeSpan waitTimeout, bool stopApplication, Action? configure, Action? entrypointCompleted) + public HostingListener( + string[] args, + MethodInfo entryPoint, + TimeSpan waitTimeout, + bool stopApplication, + Action? configure, + Action? entrypointCompleted, + IDictionary>? arbitraryActions) { _args = args; _entryPoint = entryPoint; @@ -214,6 +223,7 @@ public HostingListener(string[] args, MethodInfo entryPoint, TimeSpan waitTimeou _stopApplication = stopApplication; _configure = configure; _entrypointCompleted = entrypointCompleted; + _arbitraryActions = arbitraryActions; } public object CreateHost() @@ -332,8 +342,7 @@ public void OnNext(KeyValuePair value) { _configure?.Invoke(value.Value!); } - - if (value.Key == "HostBuilt") + else if (value.Key == "HostBuilt") { _hostTcs.TrySetResult(value.Value!); @@ -343,6 +352,10 @@ public void OnNext(KeyValuePair value) ThrowHostAborted(); } } + else if (_arbitraryActions?.TryGetValue(value.Key, out var arbitraryAction) == true) + { + arbitraryAction.Invoke(value.Value); + } } // HostFactoryResolver is used by tools that explicitly don't want to reference Microsoft.Extensions.Hosting assemblies. diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/ArbitraryDiagnosticEventPatternTestSite/ArbitraryDiagnosticEventPatternTestSite.csproj b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/ArbitraryDiagnosticEventPatternTestSite/ArbitraryDiagnosticEventPatternTestSite.csproj new file mode 100644 index 00000000000000..0900603677a73a --- /dev/null +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/ArbitraryDiagnosticEventPatternTestSite/ArbitraryDiagnosticEventPatternTestSite.csproj @@ -0,0 +1,13 @@ + + + + $(NetCoreAppCurrent);$(NetFrameworkCurrent) + true + Exe + + + + + + + diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/ArbitraryDiagnosticEventPatternTestSite/Program.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/ArbitraryDiagnosticEventPatternTestSite/Program.cs new file mode 100644 index 00000000000000..26259a65f8177d --- /dev/null +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/ArbitraryDiagnosticEventPatternTestSite/Program.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using Microsoft.Extensions.Hosting; + +namespace ArbitraryDiagnosticEventPatternTestSite +{ + public class Program + { + public static void Main(string[] args) + { + using var listener = new DiagnosticListener("Microsoft.Extensions.Hosting"); + listener.Write("CustomEvent", 42); + + new HostBuilder().Build().Dispose(); + } + } +} diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs index de12ae7da78465..507cd398f6246e 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Configuration; using MockHostTypes; using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Threading; @@ -163,6 +164,57 @@ void ConfigureHostBuilder(object hostBuilder) Assert.True(called); } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsMultithreadingSupported))] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ArbitraryDiagnosticEventPatternTestSite.Program))] + [InlineData(true)] + [InlineData(false)] + public void ArbitraryActionsCustomEventCallbackIsCalled(bool stopApplication) + { + using var callbackCalled = new ManualResetEventSlim(false); + object? callbackValue = null; + void CustomCallback(object? value) + { + callbackValue = value; + callbackCalled.Set(); + } + + var arbitraryActions = new Dictionary> + { + ["CustomEvent"] = CustomCallback + }; + + var factory = HostFactoryResolver.ResolveHostFactory( + typeof(ArbitraryDiagnosticEventPatternTestSite.Program).Assembly, + waitTimeout: s_WaitTimeout, + stopApplication: stopApplication, + arbitraryActions: arbitraryActions); + + Assert.NotNull(factory); + using var host = Assert.IsAssignableFrom(factory(Array.Empty())); + Assert.True(callbackCalled.Wait(s_WaitTimeout)); + Assert.Equal(42, callbackValue); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsMultithreadingSupported))] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ArbitraryDiagnosticEventPatternTestSite.Program))] + public void ArbitraryActionsExceptionIsPropagated() + { + var arbitraryActions = new Dictionary> + { + ["CustomEvent"] = _ => throw new InvalidOperationException("arbitrary action failed") + }; + + var factory = HostFactoryResolver.ResolveHostFactory( + typeof(ArbitraryDiagnosticEventPatternTestSite.Program).Assembly, + waitTimeout: s_WaitTimeout, + stopApplication: true, + arbitraryActions: arbitraryActions); + + Assert.NotNull(factory); + var exception = Assert.Throws(() => factory(Array.Empty())); + Assert.Equal("arbitrary action failed", exception.Message); + } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsMultithreadingSupported))] [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(NoSpecialEntryPointPattern.Program))] public void NoSpecialEntryPointPatternBuildsThenThrowsCallsEntryPointCompletedCallback() diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj index 753f6940f13029..71f03b3386ebd7 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/Microsoft.Extensions.HostFactoryResolver.Tests.csproj @@ -25,6 +25,7 @@ +