From bbdc3ee55a4e9d7269b5decf8f421f93e717fc5a Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 30 May 2024 11:42:41 +0800 Subject: [PATCH 1/7] Track factory primary handler without modifying the value --- .../GrpcClientServiceExtensions.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs b/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs index d7c13eaad..f7710e8d6 100644 --- a/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs +++ b/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs @@ -306,21 +306,21 @@ private static IHttpClientBuilder AddGrpcHttpClient< { ArgumentNullThrowHelper.ThrowIfNull(services); - services - .AddHttpClient(name) - .ConfigurePrimaryHttpMessageHandler(() => - { - // Set PrimaryHandler to null so we can track whether the user - // set a value or not. If they didn't set their own handler then - // one will be created by PostConfigure. - return null!; - }); + services.AddHttpClient(name); + + // Get PrimaryHandler o we can track whether the user set a value or not. + // This action comes before registered user actions so the primary handler here will be the one created by the factory. + HttpMessageHandler? initialPrimaryHandler = null; + services.Configure(name, options => + { + options.HttpMessageHandlerBuilderActions.Add(b => initialPrimaryHandler = b.PrimaryHandler); + }); services.PostConfigure(name, options => { options.HttpMessageHandlerBuilderActions.Add(builder => { - if (builder.PrimaryHandler == null) + if (builder.PrimaryHandler == initialPrimaryHandler) { // This will throw in .NET Standard 2.0 with a prompt that a user must set a handler. // Because it throws it should only be called in PostConfigure if no handler has been set. From 489a8ccb635697f6a82f6545fc09ac938d69a4dd Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 30 May 2024 11:46:23 +0800 Subject: [PATCH 2/7] Fix --- testassets/InteropTestsWebsite/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testassets/InteropTestsWebsite/Dockerfile b/testassets/InteropTestsWebsite/Dockerfile index c8f901a95..333c9bbc8 100644 --- a/testassets/InteropTestsWebsite/Dockerfile +++ b/testassets/InteropTestsWebsite/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/nightly/sdk:8.0 AS build-env +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env WORKDIR /app # Copy everything From a01b396419f1aaa37c0eb7b7208a676af808105c Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 30 May 2024 12:05:53 +0800 Subject: [PATCH 3/7] Update --- src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs b/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs index f7710e8d6..a0be13720 100644 --- a/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs +++ b/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs @@ -308,7 +308,7 @@ private static IHttpClientBuilder AddGrpcHttpClient< services.AddHttpClient(name); - // Get PrimaryHandler o we can track whether the user set a value or not. + // Get PrimaryHandler and store it to compare later. Used to track whether the user set a handler or not. // This action comes before registered user actions so the primary handler here will be the one created by the factory. HttpMessageHandler? initialPrimaryHandler = null; services.Configure(name, options => @@ -320,6 +320,8 @@ private static IHttpClientBuilder AddGrpcHttpClient< { options.HttpMessageHandlerBuilderActions.Add(builder => { + // If the primary handler is unchanged from what the factory created then replace the handler + // with one that has settings optimized for a gRPC client. if (builder.PrimaryHandler == initialPrimaryHandler) { // This will throw in .NET Standard 2.0 with a prompt that a user must set a handler. From 8a3adbdd4d0dc5371e6f805b1e65dcb426e19a00 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 2 Jun 2024 21:17:55 +0800 Subject: [PATCH 4/7] Refactor --- Directory.Packages.props | 3 +- src/Grpc.Net.Client/Grpc.Net.Client.csproj | 2 +- src/Grpc.Net.Client/GrpcChannel.cs | 5 +- .../Grpc.Net.ClientFactory.csproj | 2 +- .../GrpcClientServiceExtensions.cs | 75 ++++++++++--------- .../Internal/GrpcClientMappingRegistry.cs | 3 +- src/Shared/HttpHandlerFactory.cs | 22 ++++-- .../Grpc.Net.Client.Tests.csproj | 4 +- .../DefaultGrpcClientFactoryTests.cs | 75 +++++++++++++++++++ .../Grpc.Net.ClientFactory.Tests.csproj | 1 + 10 files changed, 140 insertions(+), 52 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c0b910e66..25dd91506 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,7 +9,7 @@ 1.8.1 1.8.0-beta.1 8.0.0 - 6.0.0 + 6.0.0 @@ -24,6 +24,7 @@ + diff --git a/src/Grpc.Net.Client/Grpc.Net.Client.csproj b/src/Grpc.Net.Client/Grpc.Net.Client.csproj index b97499188..a810fe41d 100644 --- a/src/Grpc.Net.Client/Grpc.Net.Client.csproj +++ b/src/Grpc.Net.Client/Grpc.Net.Client.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/Grpc.Net.Client/GrpcChannel.cs b/src/Grpc.Net.Client/GrpcChannel.cs index 0c4cea722..e84459040 100644 --- a/src/Grpc.Net.Client/GrpcChannel.cs +++ b/src/Grpc.Net.Client/GrpcChannel.cs @@ -489,7 +489,10 @@ private HttpMessageInvoker CreateInternalHttpInvoker(HttpMessageHandler? handler // Decision to dispose invoker is controlled by _shouldDisposeHttpClient. if (handler == null) { - handler = HttpHandlerFactory.CreatePrimaryHandler(); + if (!HttpHandlerFactory.TryCreatePrimaryHandler(out handler)) + { + throw HttpHandlerFactory.CreateUnsupportedHandlerException(); + } } else { diff --git a/src/Grpc.Net.ClientFactory/Grpc.Net.ClientFactory.csproj b/src/Grpc.Net.ClientFactory/Grpc.Net.ClientFactory.csproj index 0c77d633e..13d5b30d7 100644 --- a/src/Grpc.Net.ClientFactory/Grpc.Net.ClientFactory.csproj +++ b/src/Grpc.Net.ClientFactory/Grpc.Net.ClientFactory.csproj @@ -26,6 +26,6 @@ - + diff --git a/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs b/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs index a0be13720..e5ef8e484 100644 --- a/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs +++ b/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs @@ -16,6 +16,7 @@ #endregion +using System; using System.Diagnostics.CodeAnalysis; using Grpc.Net.ClientFactory; using Grpc.Net.ClientFactory.Internal; @@ -289,9 +290,7 @@ private static IHttpClientBuilder AddGrpcClientCore< // because we access it by reaching into the service collection. services.TryAddSingleton(new GrpcClientMappingRegistry()); - IHttpClientBuilder clientBuilder = services.AddGrpcHttpClient(name); - - return clientBuilder; + return services.AddGrpcHttpClient(name); } /// @@ -306,27 +305,22 @@ private static IHttpClientBuilder AddGrpcHttpClient< { ArgumentNullThrowHelper.ThrowIfNull(services); - services.AddHttpClient(name); + var builder = services.AddHttpClient(name); - // Get PrimaryHandler and store it to compare later. Used to track whether the user set a handler or not. - // This action comes before registered user actions so the primary handler here will be the one created by the factory. - HttpMessageHandler? initialPrimaryHandler = null; - services.Configure(name, options => + builder.Services.AddTransient(s => { - options.HttpMessageHandlerBuilderActions.Add(b => initialPrimaryHandler = b.PrimaryHandler); + var clientFactory = s.GetRequiredService(); + return clientFactory.CreateClient(builder.Name); }); - services.PostConfigure(name, options => + // Insert primary handler before other configuration so there is the opportunity to override it. + // This should run before ConfigureDefaultHttpClient so the handler can be overriden in defaults. + var configurePrimaryHandler = ServiceDescriptor.Singleton>(new ConfigureNamedOptions(name, options => { - options.HttpMessageHandlerBuilderActions.Add(builder => + options.HttpMessageHandlerBuilderActions.Add(b => { - // If the primary handler is unchanged from what the factory created then replace the handler - // with one that has settings optimized for a gRPC client. - if (builder.PrimaryHandler == initialPrimaryHandler) + if (HttpHandlerFactory.TryCreatePrimaryHandler(out var handler)) { - // This will throw in .NET Standard 2.0 with a prompt that a user must set a handler. - // Because it throws it should only be called in PostConfigure if no handler has been set. - var handler = HttpHandlerFactory.CreatePrimaryHandler(); #if NET5_0_OR_GREATER if (handler is SocketsHttpHandler socketsHttpHandler) { @@ -338,17 +332,27 @@ private static IHttpClientBuilder AddGrpcHttpClient< } #endif - builder.PrimaryHandler = handler; + b.PrimaryHandler = handler; + } + else + { + b.PrimaryHandler = UnsupportedHttpHandler.Instance; } }); - }); - - var builder = new DefaultHttpClientBuilder(services, name); + })); + services.Insert(0, configurePrimaryHandler); - builder.Services.AddTransient(s => + // Some platforms don't have a built-in handler that supports gRPC. + // Validate that a handler was set after all configuration has run. + services.PostConfigure(name, options => { - var clientFactory = s.GetRequiredService(); - return clientFactory.CreateClient(builder.Name); + options.HttpMessageHandlerBuilderActions.Add(builder => + { + if (builder.PrimaryHandler == UnsupportedHttpHandler.Instance) + { + throw HttpHandlerFactory.CreateUnsupportedHandlerException(); + } + }); }); ReserveClient(builder, typeof(TClient), name); @@ -356,19 +360,6 @@ private static IHttpClientBuilder AddGrpcHttpClient< return builder; } - private class DefaultHttpClientBuilder : IHttpClientBuilder - { - public DefaultHttpClientBuilder(IServiceCollection services, string name) - { - Services = services; - Name = name; - } - - public string Name { get; } - - public IServiceCollection Services { get; } - } - private static void ReserveClient(IHttpClientBuilder builder, Type type, string name) { var registry = (GrpcClientMappingRegistry?)builder.Services.Single(sd => sd.ServiceType == typeof(GrpcClientMappingRegistry)).ImplementationInstance; @@ -386,4 +377,14 @@ private static void ReserveClient(IHttpClientBuilder builder, Type type, string registry.NamedClientRegistrations[name] = type; } + + private sealed class UnsupportedHttpHandler : HttpMessageHandler + { + public static readonly UnsupportedHttpHandler Instance = new UnsupportedHttpHandler(); + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return Task.FromException(HttpHandlerFactory.CreateUnsupportedHandlerException()); + } + } } diff --git a/src/Grpc.Net.ClientFactory/Internal/GrpcClientMappingRegistry.cs b/src/Grpc.Net.ClientFactory/Internal/GrpcClientMappingRegistry.cs index f612cedc2..2dac10123 100644 --- a/src/Grpc.Net.ClientFactory/Internal/GrpcClientMappingRegistry.cs +++ b/src/Grpc.Net.ClientFactory/Internal/GrpcClientMappingRegistry.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -16,7 +16,6 @@ #endregion - namespace Grpc.Net.ClientFactory.Internal; internal class GrpcClientMappingRegistry diff --git a/src/Shared/HttpHandlerFactory.cs b/src/Shared/HttpHandlerFactory.cs index 6d0661c7c..fecd71857 100644 --- a/src/Shared/HttpHandlerFactory.cs +++ b/src/Shared/HttpHandlerFactory.cs @@ -16,13 +16,14 @@ #endregion +using System.Diagnostics.CodeAnalysis; using Grpc.Net.Client; namespace Grpc.Shared; internal static class HttpHandlerFactory { - public static HttpMessageHandler CreatePrimaryHandler() + public static bool TryCreatePrimaryHandler([NotNullWhen(true)] out HttpMessageHandler? primaryHandler) { #if NET5_0_OR_GREATER // If we're in .NET 5 and SocketsHttpHandler is supported (it's not in Blazor WebAssembly) @@ -30,29 +31,38 @@ public static HttpMessageHandler CreatePrimaryHandler() // allow a gRPC channel to create new connections if the maximum allow concurrency is exceeded. if (SocketsHttpHandler.IsSupported) { - return new SocketsHttpHandler + primaryHandler = new SocketsHttpHandler { EnableMultipleHttp2Connections = true }; + return true; } #endif #if NET462 // Create WinHttpHandler with EnableMultipleHttp2Connections set to true. That will // allow a gRPC channel to create new connections if the maximum allow concurrency is exceeded. - return new WinHttpHandler + primaryHandler = new WinHttpHandler { EnableMultipleHttp2Connections = true }; + return true; #elif !NETSTANDARD2_0 - return new HttpClientHandler(); + primaryHandler = new HttpClientHandler(); + return true; #else + primaryHandler = null; + return false; +#endif + } + + public static Exception CreateUnsupportedHandlerException() + { var message = $"gRPC requires extra configuration on .NET implementations that don't support gRPC over HTTP/2. " + $"An HTTP provider must be specified using {nameof(GrpcChannelOptions)}.{nameof(GrpcChannelOptions.HttpHandler)}." + $"The configured HTTP provider must either support HTTP/2 or be configured to use gRPC-Web. " + $"See https://aka.ms/aspnet/grpc/netstandard for details."; - throw new PlatformNotSupportedException(message); -#endif + return new PlatformNotSupportedException(message); } } diff --git a/test/Grpc.Net.Client.Tests/Grpc.Net.Client.Tests.csproj b/test/Grpc.Net.Client.Tests/Grpc.Net.Client.Tests.csproj index f612232dc..73a63531b 100644 --- a/test/Grpc.Net.Client.Tests/Grpc.Net.Client.Tests.csproj +++ b/test/Grpc.Net.Client.Tests/Grpc.Net.Client.Tests.csproj @@ -41,13 +41,11 @@ - + - - diff --git a/test/Grpc.Net.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs b/test/Grpc.Net.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs index d2a58d1c4..7920de17a 100644 --- a/test/Grpc.Net.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs +++ b/test/Grpc.Net.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs @@ -56,6 +56,33 @@ public void CreateClient_Default_DefaultInvokerSet() Assert.IsInstanceOf(typeof(HttpMessageInvoker), client.CallInvoker.Channel.HttpInvoker); } +#if NET6_0_OR_GREATER + [Test] + public void CreateClient_Default_PrimaryHandlerIsSocketsHttpHandler() + { + // Arrange + SocketsHttpHandler? socketsHttpHandler = null; + var services = new ServiceCollection(); + services + .AddGrpcClient(o => o.Address = new Uri("http://localhost")) + .ConfigurePrimaryHttpMessageHandler((primaryHandler, _) => + { + socketsHttpHandler = (SocketsHttpHandler)primaryHandler; + }); + + var serviceProvider = services.BuildServiceProvider(validateScopes: true); + + var clientFactory = CreateGrpcClientFactory(serviceProvider); + + // Act + var client = clientFactory.CreateClient(nameof(TestGreeterClient)); + + // Assert + Assert.NotNull(socketsHttpHandler); + Assert.IsTrue(socketsHttpHandler!.EnableMultipleHttp2Connections); + } +#endif + [Test] public void CreateClient_MatchingConfigurationBasedOnTypeName_ReturnConfiguration() { @@ -254,6 +281,54 @@ public void CreateClient_NoPrimaryHandlerNetStandard_ThrowError() // Assert Assert.AreEqual(@"gRPC requires extra configuration on .NET implementations that don't support gRPC over HTTP/2. An HTTP provider must be specified using GrpcChannelOptions.HttpHandler.The configured HTTP provider must either support HTTP/2 or be configured to use gRPC-Web. See https://aka.ms/aspnet/grpc/netstandard for details.", ex.Message); } + + [Test] + public void CreateClient_ConfigureDefaultAfter_Success() + { + // Arrange + var services = new ServiceCollection(); + services + .AddGrpcClient(o => o.Address = new Uri("https://localhost")); + + services.ConfigureHttpClientDefaults(builder => + { + builder.ConfigurePrimaryHttpMessageHandler(() => new NullHttpHandler()); + }); + + var serviceProvider = services.BuildServiceProvider(validateScopes: true); + + var clientFactory = CreateGrpcClientFactory(serviceProvider); + + // Act + var client = clientFactory.CreateClient(nameof(TestGreeterClient)); + + // Assert + Assert.IsNotNull(client); + } + + [Test] + public void CreateClient_ConfigureDefaultBefore_Success() + { + // Arrange + var services = new ServiceCollection(); + + services.ConfigureHttpClientDefaults(builder => + { + builder.ConfigurePrimaryHttpMessageHandler(() => new NullHttpHandler()); + }); + + services.AddGrpcClient(o => o.Address = new Uri("https://localhost")); + + var serviceProvider = services.BuildServiceProvider(validateScopes: true); + + var clientFactory = CreateGrpcClientFactory(serviceProvider); + + // Act + var client = clientFactory.CreateClient(nameof(TestGreeterClient)); + + // Assert + Assert.IsNotNull(client); + } #endif #if NET5_0_OR_GREATER diff --git a/test/Grpc.Net.ClientFactory.Tests/Grpc.Net.ClientFactory.Tests.csproj b/test/Grpc.Net.ClientFactory.Tests/Grpc.Net.ClientFactory.Tests.csproj index 50163ccb4..a78247ee4 100644 --- a/test/Grpc.Net.ClientFactory.Tests/Grpc.Net.ClientFactory.Tests.csproj +++ b/test/Grpc.Net.ClientFactory.Tests/Grpc.Net.ClientFactory.Tests.csproj @@ -29,6 +29,7 @@ + From a4e62a21c7db2aa3156d11c8c76d44caf7327708 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 4 Jun 2024 09:46:22 +0800 Subject: [PATCH 5/7] Feedback --- src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs b/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs index e5ef8e484..9e3b3bd00 100644 --- a/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs +++ b/src/Grpc.Net.ClientFactory/GrpcClientServiceExtensions.cs @@ -310,7 +310,7 @@ private static IHttpClientBuilder AddGrpcHttpClient< builder.Services.AddTransient(s => { var clientFactory = s.GetRequiredService(); - return clientFactory.CreateClient(builder.Name); + return clientFactory.CreateClient(name); }); // Insert primary handler before other configuration so there is the opportunity to override it. @@ -343,7 +343,7 @@ private static IHttpClientBuilder AddGrpcHttpClient< services.Insert(0, configurePrimaryHandler); // Some platforms don't have a built-in handler that supports gRPC. - // Validate that a handler was set after all configuration has run. + // Validate that a handler was set by the app to after all configuration has run. services.PostConfigure(name, options => { options.HttpMessageHandlerBuilderActions.Add(builder => From bc0cf8ebc77a652bbbe1853fa0a9758034376bfa Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 5 Jun 2024 08:56:10 +0800 Subject: [PATCH 6/7] Refactor test --- .../DefaultGrpcClientFactoryTests.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/Grpc.Net.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs b/test/Grpc.Net.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs index 7920de17a..dc473d6c4 100644 --- a/test/Grpc.Net.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs +++ b/test/Grpc.Net.ClientFactory.Tests/DefaultGrpcClientFactoryTests.cs @@ -61,13 +61,13 @@ public void CreateClient_Default_DefaultInvokerSet() public void CreateClient_Default_PrimaryHandlerIsSocketsHttpHandler() { // Arrange - SocketsHttpHandler? socketsHttpHandler = null; + HttpMessageHandler? clientPrimaryHandler = null; var services = new ServiceCollection(); services .AddGrpcClient(o => o.Address = new Uri("http://localhost")) .ConfigurePrimaryHttpMessageHandler((primaryHandler, _) => { - socketsHttpHandler = (SocketsHttpHandler)primaryHandler; + clientPrimaryHandler = primaryHandler; }); var serviceProvider = services.BuildServiceProvider(validateScopes: true); @@ -78,8 +78,9 @@ public void CreateClient_Default_PrimaryHandlerIsSocketsHttpHandler() var client = clientFactory.CreateClient(nameof(TestGreeterClient)); // Assert - Assert.NotNull(socketsHttpHandler); - Assert.IsTrue(socketsHttpHandler!.EnableMultipleHttp2Connections); + Assert.NotNull(clientPrimaryHandler); + Assert.IsInstanceOf(clientPrimaryHandler); + Assert.IsTrue(((SocketsHttpHandler)clientPrimaryHandler!).EnableMultipleHttp2Connections); } #endif From d0518d13efafe5534989624949e22e11841fd971 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 5 Jun 2024 09:43:24 +0800 Subject: [PATCH 7/7] Fix flaky test --- test/FunctionalTests/Linker/LinkerTests.cs | 13 ++++++++++++- testassets/LinkerTestsWebsite/Program.cs | 1 - 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/test/FunctionalTests/Linker/LinkerTests.cs b/test/FunctionalTests/Linker/LinkerTests.cs index 7a78c909a..8cb05d84c 100644 --- a/test/FunctionalTests/Linker/LinkerTests.cs +++ b/test/FunctionalTests/Linker/LinkerTests.cs @@ -19,6 +19,7 @@ // Skip running load running tests in debug configuration #if !DEBUG +using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; using Grpc.AspNetCore.FunctionalTests.Linker.Helpers; @@ -86,7 +87,17 @@ private async Task RunWebsiteAndCallWithClient(bool publishAot) websiteProcess.Start(BuildStartPath(linkerTestsWebsitePath, "LinkerTestsWebsite"), arguments: null); await websiteProcess.WaitForReadyAsync().TimeoutAfter(Timeout); - clientProcess.Start(BuildStartPath(linkerTestsClientPath, "LinkerTestsClient"), arguments: websiteProcess.ServerPort!.ToString()); + string? clientArguments = null; + if (websiteProcess.ServerPort is {} serverPort) + { + clientArguments = serverPort.ToString(CultureInfo.InvariantCulture); + } + else + { + throw new InvalidOperationException("Website server port not available."); + } + + clientProcess.Start(BuildStartPath(linkerTestsClientPath, "LinkerTestsClient"), arguments: clientArguments); await clientProcess.WaitForExitAsync().TimeoutAfter(Timeout); } finally diff --git a/testassets/LinkerTestsWebsite/Program.cs b/testassets/LinkerTestsWebsite/Program.cs index eb0c95644..5f026746f 100644 --- a/testassets/LinkerTestsWebsite/Program.cs +++ b/testassets/LinkerTestsWebsite/Program.cs @@ -35,5 +35,4 @@ app.MapGrpcService(); -app.Lifetime.ApplicationStarted.Register(() => Console.WriteLine("Application started. Press Ctrl+C to shut down.")); app.Run();