diff --git a/src/Orleans.Core/Hosting/GenericHostExtensions.cs b/src/Orleans.Core/Hosting/OrleansClientGenericHostExtensions.cs similarity index 59% rename from src/Orleans.Core/Hosting/GenericHostExtensions.cs rename to src/Orleans.Core/Hosting/OrleansClientGenericHostExtensions.cs index 90c5bc2164..55a0432467 100644 --- a/src/Orleans.Core/Hosting/GenericHostExtensions.cs +++ b/src/Orleans.Core/Hosting/OrleansClientGenericHostExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Microsoft.Extensions.DependencyInjection; using Orleans.Hosting; using Orleans.Runtime; @@ -10,6 +11,32 @@ namespace Microsoft.Extensions.Hosting /// public static class OrleansClientGenericHostExtensions { + private static readonly Type MarkerType = typeof(OrleansBuilderMarker); + + /// + /// Configures the host app builder to host an Orleans client. + /// + /// The host app builder. + /// The delegate used to configure the client. + /// The host builder. + /// + /// Calling this method multiple times on the same instance will result in one client being configured. + /// However, the effects of will be applied once for each call. + /// Note that this method shouldn't be used in conjunction with HostApplicationBuilder.UseOrleans, since UseOrleans includes a client automatically. + /// + /// was null or was null. + public static HostApplicationBuilder UseOrleansClient( + this HostApplicationBuilder hostAppBuilder, + Action configureDelegate) + { + ArgumentNullException.ThrowIfNull(hostAppBuilder); + ArgumentNullException.ThrowIfNull(configureDelegate); + + hostAppBuilder.Services.AddOrleansClient(configureDelegate); + + return hostAppBuilder; + } + /// /// Configures the host builder to host an Orleans client. /// @@ -22,8 +49,8 @@ public static class OrleansClientGenericHostExtensions /// Note that this method should not be used in conjunction with IHostBuilder.UseOrleans, since UseOrleans includes a client automatically. /// /// was null or was null. - public static IHostBuilder UseOrleansClient(this IHostBuilder hostBuilder, Action configureDelegate) - => hostBuilder.UseOrleansClient((_, clientBuilder) => configureDelegate(clientBuilder)); + public static IHostBuilder UseOrleansClient(this IHostBuilder hostBuilder, Action configureDelegate) => + hostBuilder.UseOrleansClient((_, clientBuilder) => configureDelegate(clientBuilder)); /// /// Configures the host builder to host an Orleans client. @@ -39,14 +66,16 @@ public static IHostBuilder UseOrleansClient(this IHostBuilder hostBuilder, Actio /// was null or was null. public static IHostBuilder UseOrleansClient(this IHostBuilder hostBuilder, Action configureDelegate) { - if (hostBuilder == null) throw new ArgumentNullException(nameof(hostBuilder)); - if (configureDelegate == null) throw new ArgumentNullException(nameof(configureDelegate)); + ArgumentNullException.ThrowIfNull(hostBuilder); + ArgumentNullException.ThrowIfNull(configureDelegate); + if (hostBuilder.Properties.ContainsKey("HasOrleansSiloBuilder")) { throw GetOrleansSiloAddedException(); } hostBuilder.Properties["HasOrleansClientBuilder"] = "true"; + return hostBuilder.ConfigureServices((ctx, services) => configureDelegate(ctx, AddOrleansClient(services))); } @@ -64,8 +93,10 @@ public static IHostBuilder UseOrleansClient(this IHostBuilder hostBuilder, Actio /// was null or was null. public static IServiceCollection AddOrleansClient(this IServiceCollection services, Action configureDelegate) { - if (configureDelegate == null) throw new ArgumentNullException(nameof(configureDelegate)); + ArgumentNullException.ThrowIfNull(configureDelegate); + var clientBuilder = AddOrleansClient(services); + configureDelegate(clientBuilder); return services; } @@ -73,45 +104,43 @@ public static IServiceCollection AddOrleansClient(this IServiceCollection servic private static IClientBuilder AddOrleansClient(IServiceCollection services) { IClientBuilder clientBuilder = default; - foreach (var descriptor in services) + foreach (var descriptor in services.Where(d => d.ServiceType.Equals(MarkerType))) { - if (descriptor.ServiceType.Equals(typeof(OrleansBuilderMarker))) + var instance = (OrleansBuilderMarker)descriptor.ImplementationInstance; + clientBuilder = instance.BuilderInstance switch { - var instance = (OrleansBuilderMarker)descriptor.ImplementationInstance; - clientBuilder = instance.Instance switch - { - IClientBuilder existingBuilder => existingBuilder, - _ => throw GetOrleansSiloAddedException() - }; - } + IClientBuilder existingBuilder => existingBuilder, + _ => throw GetOrleansSiloAddedException() + }; } if (clientBuilder is null) { clientBuilder = new ClientBuilder(services); - services.Add(new(typeof(OrleansBuilderMarker), new OrleansBuilderMarker(clientBuilder))); + services.AddSingleton(new OrleansBuilderMarker(clientBuilder)); } return clientBuilder; } - private static OrleansConfigurationException GetOrleansSiloAddedException() => new("Do not use UseOrleans with UseOrleansClient. If you want a client and server in the same process, only UseOrleans is necessary and the UseOrleansClient call can be removed."); + private static OrleansConfigurationException GetOrleansSiloAddedException() => + new("Do not use UseOrleans with UseOrleansClient. If you want a client and server in the same process, only UseOrleans is necessary and the UseOrleansClient call can be removed."); + } + /// + /// Marker type used for storing a builder in a service collection. + /// + internal sealed class OrleansBuilderMarker + { /// - /// Marker type used for storing a client builder in a service collection. + /// Initializes a new instance of the class. /// - internal class OrleansBuilderMarker - { - /// - /// Initializes a new instance of the class. - /// - /// The builder instance. - public OrleansBuilderMarker(object builderInstance) => Instance = builderInstance; - - /// - /// Gets the builder instance. - /// - public object Instance { get; } - } + /// The builder instance. + public OrleansBuilderMarker(object builderInstance) => BuilderInstance = builderInstance; + + /// + /// Gets the builder instance. + /// + public object BuilderInstance { get; } } } diff --git a/src/Orleans.Core/Orleans.Core.csproj b/src/Orleans.Core/Orleans.Core.csproj index 30f3c10dfe..e8b8e5e560 100644 --- a/src/Orleans.Core/Orleans.Core.csproj +++ b/src/Orleans.Core/Orleans.Core.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/Orleans.Runtime/Hosting/GenericHostExtensions.cs b/src/Orleans.Runtime/Hosting/OrleansSiloGenericHostExtensions.cs similarity index 58% rename from src/Orleans.Runtime/Hosting/GenericHostExtensions.cs rename to src/Orleans.Runtime/Hosting/OrleansSiloGenericHostExtensions.cs index acb6c728b2..e951a2991c 100644 --- a/src/Orleans.Runtime/Hosting/GenericHostExtensions.cs +++ b/src/Orleans.Runtime/Hosting/OrleansSiloGenericHostExtensions.cs @@ -1,17 +1,49 @@ using System; +using System.Linq; using Microsoft.Extensions.DependencyInjection; -using Orleans; using Orleans.Hosting; using Orleans.Runtime; -using static Microsoft.Extensions.Hosting.OrleansClientGenericHostExtensions; namespace Microsoft.Extensions.Hosting { /// /// Extension methods for . /// - public static class GenericHostExtensions + public static class OrleansSiloGenericHostExtensions { + private static readonly Type MarkerType = typeof(OrleansBuilderMarker); + + /// + /// Configures the host app builder to host an Orleans silo. + /// + /// The host app builder. + /// The host builder. + public static HostApplicationBuilder UseOrleans( + this HostApplicationBuilder hostAppBuilder) => + hostAppBuilder.UseOrleans(_ => { }); + + /// + /// Configures the host builder to host an Orleans silo. + /// + /// The host app builder. + /// The delegate used to configure the silo. + /// The host builder. + /// + /// Calling this method multiple times on the same instance will result in one silo being configured. + /// However, the effects of will be applied once for each call. + /// + public static HostApplicationBuilder UseOrleans( + this HostApplicationBuilder hostAppBuilder, + Action configureDelegate) + { + ArgumentNullException.ThrowIfNull(hostAppBuilder); + ArgumentNullException.ThrowIfNull(configureDelegate); + + hostAppBuilder.Services.AddOrleans(configureDelegate); + + return hostAppBuilder; + } + /// /// Configures the host builder to host an Orleans silo. /// @@ -40,8 +72,8 @@ public static IHostBuilder UseOrleans( this IHostBuilder hostBuilder, Action configureDelegate) { - if (hostBuilder is null) throw new ArgumentNullException(nameof(hostBuilder)); - if (configureDelegate == null) throw new ArgumentNullException(nameof(configureDelegate)); + ArgumentNullException.ThrowIfNull(hostBuilder); + ArgumentNullException.ThrowIfNull(configureDelegate); if (hostBuilder.Properties.ContainsKey("HasOrleansClientBuilder")) { @@ -67,39 +99,39 @@ public static IServiceCollection AddOrleans( this IServiceCollection services, Action configureDelegate) { - if (configureDelegate == null) throw new ArgumentNullException(nameof(configureDelegate)); + ArgumentNullException.ThrowIfNull(configureDelegate); + var builder = AddOrleans(services); configureDelegate(builder); + return services; } private static ISiloBuilder AddOrleans(IServiceCollection services) { ISiloBuilder builder = default; - foreach (var descriptor in services) + foreach (var descriptor in services.Where(d => d.ServiceType.Equals(MarkerType))) { - if (descriptor.ServiceType.Equals(typeof(OrleansBuilderMarker))) + var marker = (OrleansBuilderMarker)descriptor.ImplementationInstance; + builder = marker.BuilderInstance switch { - var instance = (OrleansBuilderMarker)descriptor.ImplementationInstance; - builder = instance.Instance switch - { - - ISiloBuilder existingBuilder => existingBuilder, - _ => throw GetOrleansClientAddedException() - }; - } + + ISiloBuilder existingBuilder => existingBuilder, + _ => throw GetOrleansClientAddedException() + }; } if (builder is null) { builder = new SiloBuilder(services); - services.Add(new(typeof(OrleansBuilderMarker), new OrleansBuilderMarker(builder))); + services.AddSingleton(new OrleansBuilderMarker(builder)); } return builder; } - private static OrleansConfigurationException GetOrleansClientAddedException() => new("Do not call both UseOrleansClient/AddOrleansClient with UseOrleans/AddOrleans. If you want a client and server in the same process, only UseOrleans/AddOrleans is necessary and the UseOrleansClient/AddOrleansClient call can be removed."); + private static OrleansConfigurationException GetOrleansClientAddedException() => + new("Do not call both UseOrleansClient/AddOrleansClient with UseOrleans/AddOrleans. If you want a client and server in the same process, only UseOrleans/AddOrleans is necessary and the UseOrleansClient/AddOrleansClient call can be removed."); } } \ No newline at end of file diff --git a/test/DefaultCluster.Tests/HostedClientTests.cs b/test/DefaultCluster.Tests/HostedClientTests.cs index 2589109439..24cde25268 100644 --- a/test/DefaultCluster.Tests/HostedClientTests.cs +++ b/test/DefaultCluster.Tests/HostedClientTests.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Orleans; using Orleans.Concurrency; using Orleans.Configuration; -using Orleans.Hosting; using Orleans.Providers; using Orleans.Runtime; using Orleans.Streams; @@ -39,8 +34,8 @@ public Fixture() public async Task InitializeAsync() { var (siloPort, gatewayPort) = portAllocator.AllocateConsecutivePortPairs(1); - Host = new HostBuilder() - .UseOrleans((ctx, siloBuilder) => + Host = Microsoft.Extensions.Hosting.Host.CreateApplicationBuilder() + .UseOrleans(siloBuilder => { siloBuilder .UseLocalhostClustering(siloPort, gatewayPort) diff --git a/test/NonSilo.Tests/ClientBuilderTests.cs b/test/NonSilo.Tests/ClientBuilderTests.cs index 46c76a1581..d2ac437b07 100644 --- a/test/NonSilo.Tests/ClientBuilderTests.cs +++ b/test/NonSilo.Tests/ClientBuilderTests.cs @@ -174,7 +174,7 @@ public void ClientBuilder_ServiceProviderTest() Assert.Throws(() => hostBuilder.ConfigureServices(null)); var registeredFirst = new int[1]; - + var one = new MyService { Id = 1 }; hostBuilder.ConfigureServices( services => @@ -196,7 +196,7 @@ public void ClientBuilder_ServiceProviderTest() var client = host.Services.GetRequiredService(); var services = client.ServiceProvider.GetServices()?.ToList(); Assert.NotNull(services); - + // Both services should be registered. Assert.Equal(2, services.Count); Assert.NotNull(services.FirstOrDefault(svc => svc.Id == 1)); @@ -226,6 +226,23 @@ public void ClientBuilderThrowsDuringStartupIfSiloBuildersAdded() }); } + [Fact] + public void ClientBuilderWithHotApplicationBuilderThrowsDuringStartupIfSiloBuildersAdded() + { + Assert.Throws(() => + { + _ = Host.CreateApplicationBuilder() + .UseOrleans(siloBuilder => + { + siloBuilder.UseLocalhostClustering(); + }) + .UseOrleansClient(clientBuilder => + { + clientBuilder.UseLocalhostClustering(); + }); + }); + } + private static void RemoveConfigValidators(IServiceCollection services) { var validators = services.Where(descriptor => descriptor.ServiceType == typeof(IConfigurationValidator)).ToList(); diff --git a/test/NonSilo.Tests/SiloBuilderTests.cs b/test/NonSilo.Tests/SiloBuilderTests.cs index 7a748b8a8d..b874de3810 100644 --- a/test/NonSilo.Tests/SiloBuilderTests.cs +++ b/test/NonSilo.Tests/SiloBuilderTests.cs @@ -201,6 +201,23 @@ public void SiloBuilderThrowsDuringStartupIfClientBuildersAdded() }); } + [Fact] + public void SiloBuilderWithHotApplicationBuilderThrowsDuringStartupIfClientBuildersAdded() + { + Assert.Throws(() => + { + _ = Host.CreateApplicationBuilder() + .UseOrleansClient(clientBuilder => + { + clientBuilder.UseLocalhostClustering(); + }) + .UseOrleans(siloBuilder => + { + siloBuilder.UseLocalhostClustering(); + }); + }); + } + private class FakeHostEnvironmentStatistics : IHostEnvironmentStatistics { public long? TotalPhysicalMemory => 0;