From 4cf8804b9bd9680da3b63df0ee134f82cbf850e5 Mon Sep 17 00:00:00 2001 From: Youssef Fahmy Date: Mon, 4 May 2026 13:54:12 +0200 Subject: [PATCH 1/7] Allow HostFactoryResolver to listen on arbitrary events --- .../src/HostFactoryResolver.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs index cc8a86d84e1a56..5f0c94bf964d97 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; namespace Microsoft.Extensions.Hosting { @@ -63,6 +64,7 @@ private static TimeSpan SetupDefaultTimeout() TimeSpan? waitTimeout = null, bool stopApplication = true, Action? configureHostBuilder = null, + Dictionary>? arbitraryActions = null, Action? entrypointCompleted = null) { if (assembly.EntryPoint is null) @@ -89,7 +91,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, arbitraryActions, entrypointCompleted).CreateHost(); } private static Func? ResolveFactory(Assembly assembly, string name) @@ -203,16 +205,18 @@ private sealed class HostingListener : IObserver, IObserver< private readonly TaskCompletionSource _hostTcs = new(); private IDisposable? _disposable; private readonly Action? _configure; + private readonly Dictionary>? _arbitraryActions; private readonly Action? _entrypointCompleted; 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, Dictionary>? arbitraryActions, Action? entrypointCompleted) { _args = args; _entryPoint = entryPoint; _waitTimeout = waitTimeout; _stopApplication = stopApplication; _configure = configure; + _arbitraryActions = arbitraryActions; _entrypointCompleted = entrypointCompleted; } @@ -332,8 +336,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 +346,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. From 935186becb528199c38be9519d3560419f81b268 Mon Sep 17 00:00:00 2001 From: Youssef Fahmy Date: Mon, 4 May 2026 14:27:19 +0200 Subject: [PATCH 2/7] Update HostFactoryResolver.cs --- .../src/HostFactoryResolver.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs index 5f0c94bf964d97..468759d14fe973 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs @@ -8,7 +8,6 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Configuration; namespace Microsoft.Extensions.Hosting { From 68a5222bb0324260db2e9fe6fb6a004ad0403e62 Mon Sep 17 00:00:00 2001 From: Youssef Fahmy Date: Mon, 4 May 2026 16:04:25 +0200 Subject: [PATCH 3/7] Update HostFactoryResolver.cs --- .../src/HostFactoryResolver.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs index 468759d14fe973..9f0bd39a83d829 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs @@ -63,8 +63,8 @@ private static TimeSpan SetupDefaultTimeout() TimeSpan? waitTimeout = null, bool stopApplication = true, Action? configureHostBuilder = null, - Dictionary>? arbitraryActions = null, - Action? entrypointCompleted = null) + Action? entrypointCompleted = null, + Dictionary>? arbitraryActions = null) { if (assembly.EntryPoint is null) { @@ -90,7 +90,7 @@ private static TimeSpan SetupDefaultTimeout() return null; } - return args => new HostingListener(args, assembly.EntryPoint, waitTimeout ?? s_defaultWaitTimeout, stopApplication, configureHostBuilder, arbitraryActions, 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,19 +204,26 @@ private sealed class HostingListener : IObserver, IObserver< private readonly TaskCompletionSource _hostTcs = new(); private IDisposable? _disposable; private readonly Action? _configure; - private readonly Dictionary>? _arbitraryActions; private readonly Action? _entrypointCompleted; + private readonly Dictionary>? _arbitraryActions; private static readonly AsyncLocal _currentListener = new(); - public HostingListener(string[] args, MethodInfo entryPoint, TimeSpan waitTimeout, bool stopApplication, Action? configure, Dictionary>? arbitraryActions, Action? entrypointCompleted) + public HostingListener( + string[] args, + MethodInfo entryPoint, + TimeSpan waitTimeout, + bool stopApplication, + Action? configure, + Action? entrypointCompleted, + Dictionary>? arbitraryActions) { _args = args; _entryPoint = entryPoint; _waitTimeout = waitTimeout; _stopApplication = stopApplication; _configure = configure; - _arbitraryActions = arbitraryActions; _entrypointCompleted = entrypointCompleted; + _arbitraryActions = arbitraryActions; } public object CreateHost() From 989a47eeded9a4e3b7078411e00442a678b6a2d5 Mon Sep 17 00:00:00 2001 From: Youssef Fahmy Date: Fri, 15 May 2026 14:52:05 +0200 Subject: [PATCH 4/7] Use IDictionary --- .../src/HostFactoryResolver.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs index 9f0bd39a83d829..7886081c262545 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/src/HostFactoryResolver.cs @@ -64,7 +64,7 @@ private static TimeSpan SetupDefaultTimeout() bool stopApplication = true, Action? configureHostBuilder = null, Action? entrypointCompleted = null, - Dictionary>? arbitraryActions = null) + IDictionary>? arbitraryActions = null) { if (assembly.EntryPoint is null) { @@ -205,7 +205,7 @@ private sealed class HostingListener : IObserver, IObserver< private IDisposable? _disposable; private readonly Action? _configure; private readonly Action? _entrypointCompleted; - private readonly Dictionary>? _arbitraryActions; + private readonly IDictionary>? _arbitraryActions; private static readonly AsyncLocal _currentListener = new(); public HostingListener( @@ -215,7 +215,7 @@ public HostingListener( bool stopApplication, Action? configure, Action? entrypointCompleted, - Dictionary>? arbitraryActions) + IDictionary>? arbitraryActions) { _args = args; _entryPoint = entryPoint; From 0f2154f69195eb12e4e85238422e4743f4a0c021 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 13:24:18 +0000 Subject: [PATCH 5/7] Add HostFactoryResolver arbitraryActions tests Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/c2f33a8c-2e5f-41cc-87fb-11636c3780e9 Co-authored-by: Youssef1313 <31348972+Youssef1313@users.noreply.github.com> --- ...traryDiagnosticEventPatternTestSite.csproj | 13 +++++ .../Program.cs | 19 +++++++ .../tests/HostFactoryResolverTests.cs | 52 +++++++++++++++++++ ...xtensions.HostFactoryResolver.Tests.csproj | 1 + 4 files changed, 85 insertions(+) create mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/ArbitraryDiagnosticEventPatternTestSite/ArbitraryDiagnosticEventPatternTestSite.csproj create mode 100644 src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/ArbitraryDiagnosticEventPatternTestSite/Program.cs 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..ce98d8685f8a39 --- /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) + { + var listener = new DiagnosticListener("Microsoft.Extensions.Hosting"); + listener.Write("CustomEvent", 42); + + var host = new HostBuilder().Build(); + } + } +} diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs index de12ae7da78465..89b79123640bcf 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) + { + 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); + 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 @@ + From 08df3ae575f5792162a12d9432edc37e995b6bcd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 13:31:01 +0000 Subject: [PATCH 6/7] Add arbitraryActions coverage and exception propagation tests Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/c2f33a8c-2e5f-41cc-87fb-11636c3780e9 Co-authored-by: Youssef1313 <31348972+Youssef1313@users.noreply.github.com> --- .../tests/ArbitraryDiagnosticEventPatternTestSite/Program.cs | 4 ++-- .../tests/HostFactoryResolverTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/ArbitraryDiagnosticEventPatternTestSite/Program.cs b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/ArbitraryDiagnosticEventPatternTestSite/Program.cs index ce98d8685f8a39..26259a65f8177d 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/ArbitraryDiagnosticEventPatternTestSite/Program.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/ArbitraryDiagnosticEventPatternTestSite/Program.cs @@ -10,10 +10,10 @@ public class Program { public static void Main(string[] args) { - var listener = new DiagnosticListener("Microsoft.Extensions.Hosting"); + using var listener = new DiagnosticListener("Microsoft.Extensions.Hosting"); listener.Write("CustomEvent", 42); - var host = new HostBuilder().Build(); + 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 89b79123640bcf..507cd398f6246e 100644 --- a/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs +++ b/src/libraries/Microsoft.Extensions.HostFactoryResolver/tests/HostFactoryResolverTests.cs @@ -170,7 +170,7 @@ void ConfigureHostBuilder(object hostBuilder) [InlineData(false)] public void ArbitraryActionsCustomEventCallbackIsCalled(bool stopApplication) { - var callbackCalled = new ManualResetEventSlim(false); + using var callbackCalled = new ManualResetEventSlim(false); object? callbackValue = null; void CustomCallback(object? value) { @@ -190,7 +190,7 @@ void CustomCallback(object? value) arbitraryActions: arbitraryActions); Assert.NotNull(factory); - Assert.IsAssignableFrom(factory(Array.Empty())); + using var host = Assert.IsAssignableFrom(factory(Array.Empty())); Assert.True(callbackCalled.Wait(s_WaitTimeout)); Assert.Equal(42, callbackValue); } From eee17b8101ce7b5b0f393231aeb3483ba0ba9d19 Mon Sep 17 00:00:00 2001 From: Petr Onderka Date: Tue, 19 May 2026 15:57:36 +0200 Subject: [PATCH 7/7] Added new project file to solution --- .../Microsoft.Extensions.HostFactoryResolver.slnx | 8 ++++++++ 1 file changed, 8 insertions(+) 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 @@ + + + + + + + +