diff --git a/extensions/Worker.Extensions.Rpc/release_notes.md b/extensions/Worker.Extensions.Rpc/release_notes.md index fdf74f389..b6f4eae92 100644 --- a/extensions/Worker.Extensions.Rpc/release_notes.md +++ b/extensions/Worker.Extensions.Rpc/release_notes.md @@ -4,12 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.Rpc 1.0.0-preview1 +### Microsoft.Azure.Functions.Worker.Extensions.Rpc 1.0.0-preview2 -- Add `IHttpClientBuilder.ConfigureForFunctionsHostGrpc` extension method. (#1616) - - Used along with `IServiceCollection.AddGrpcClient()` to configure for Functions host communication. - - **NOTE**: when using Grpc.Net.ClientFactory <= 2.54.0-pre1 use `IServiceCollection.AddGrpcClient(_ => { })` - - See [grpc/grpc-dotnet/issues/2158](https://github.com/grpc/grpc-dotnet/issues/2158) - - Available in `>=net6.0` only. -- Add `FunctionsGrpcOptions`, which can be used to get a built `CallInvoker` for gRPC communication with the functions host. (#1637) - - Available in all TFMs. \ No newline at end of file +- Replace auto-registration via `WorkerRpcStartup` with manual call `RpcServiceCollectionExtensions.AddWorkerRpc` \ No newline at end of file diff --git a/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetApp.cs b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetApp.cs new file mode 100644 index 000000000..ff0780c38 --- /dev/null +++ b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetApp.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#if !NETSTANDARD +using System; +using Grpc.Core; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.Azure.Functions.Worker.Extensions.Rpc +{ + /// + /// extensions for RPC. + /// + public static partial class RpcServiceCollectionExtensions + { + private static void ConfigureCallInvoker(IServiceCollection services) + { + // Instead of building the GrpcChannel/CallInvoker ourselves, we use Grpc.Net.ClientFactory to + // construct and configure the CallInvoker for us, then we attach that to our options. + services.AddGrpcClient(_ => { }) + .ConfigureForFunctionsHostGrpc(); + services.TryAddEnumerable( + ServiceDescriptor.Transient, ConfigureOptions>()); + } + + // Used as a roundabout way of getting the configured CallInvoker from Grpc.Net.ClientFactory. + private class CallInvokerExtractor + { + public CallInvokerExtractor(CallInvoker callInvoker) + { + CallInvoker = callInvoker ?? throw new ArgumentNullException(nameof(callInvoker)); + } + + public CallInvoker CallInvoker { get; } + } + + private class ConfigureOptions : IConfigureOptions + { + private readonly CallInvokerExtractor _extractor; + + public ConfigureOptions(CallInvokerExtractor extractor) + { + _extractor = extractor; + } + + public void Configure(FunctionsGrpcOptions options) + { + options.CallInvoker = _extractor.CallInvoker; + } + } + } +} +#endif diff --git a/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetStandard.cs b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetStandard.cs new file mode 100644 index 000000000..743473bc2 --- /dev/null +++ b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.NetStandard.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#if NETSTANDARD +using System; +using Grpc.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.Azure.Functions.Worker.Extensions.Rpc +{ + /// + /// extensions for RPC. + /// + public static partial class RpcServiceCollectionExtensions + { + private static void ConfigureCallInvoker(IServiceCollection services) + { + services.TryAddEnumerable( + ServiceDescriptor.Transient, ConfigureOptions>()); + } + + private class ConfigureOptions : IConfigureOptions + { + private readonly IConfiguration _configuration; + + public ConfigureOptions(IConfiguration configuration) + { + _configuration = configuration; + } + + public void Configure(FunctionsGrpcOptions options) + { + Uri address = _configuration.GetFunctionsHostGrpcUri(); + Channel c = new Channel(address.Host, address.Port, ChannelCredentials.Insecure); + options.CallInvoker = c.CreateCallInvoker(); + } + } + } +} +#endif diff --git a/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.cs b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.cs new file mode 100644 index 000000000..9aaab718b --- /dev/null +++ b/extensions/Worker.Extensions.Rpc/src/RpcServiceCollectionExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.Azure.Functions.Worker.Extensions.Rpc +{ + /// + /// extensions for RPC. + /// + public static partial class RpcServiceCollectionExtensions + { + /// + /// Adds requires types for Functions host and worker RPC communication. Extensions that want to use RPC are + /// expected to call this before configuring their RPC clients. + /// + /// The service collection. + /// The original service collection for call chaining. + public static IServiceCollection AddWorkerRpc(this IServiceCollection services) + { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + + services.AddOptions() + .Validate(options => options.CallInvoker is not null, "gRPC CallInvoker must not be null."); + ConfigureCallInvoker(services); + return services; + } + } +} diff --git a/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj b/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj index 6dd028423..e2db7d52a 100644 --- a/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj +++ b/extensions/Worker.Extensions.Rpc/src/Worker.Extensions.Rpc.csproj @@ -6,7 +6,7 @@ Microsoft.Azure.Functions.Worker.Extensions.Rpc Contains types to facilitate RPC communication between a worker extension and the functions host. 1.0.0 - -preview1 + -preview2 README.md diff --git a/extensions/Worker.Extensions.Rpc/src/WorkerRpcStartup.cs b/extensions/Worker.Extensions.Rpc/src/WorkerRpcStartup.cs deleted file mode 100644 index df159e365..000000000 --- a/extensions/Worker.Extensions.Rpc/src/WorkerRpcStartup.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -using System; -using Grpc.Core; -using Microsoft.Azure.Functions.Worker.Core; -using Microsoft.Azure.Functions.Worker.Extensions.Rpc; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -#if NETSTANDARD -using Microsoft.Extensions.Configuration; -#endif - -[assembly: WorkerExtensionStartup(typeof(WorkerRpcStartup))] - -namespace Microsoft.Azure.Functions.Worker.Extensions.Rpc -{ - - /// - /// Startup code for extensions RPC. - /// - public sealed class WorkerRpcStartup : WorkerExtensionStartup - { - /// - public override void Configure(IFunctionsWorkerApplicationBuilder applicationBuilder) - { - if (applicationBuilder is null) - { - throw new ArgumentNullException(nameof(applicationBuilder)); - } - - OptionsBuilder builder = applicationBuilder.Services - .AddOptions(); - - ConfigureCallInvoker(builder); - builder.Validate(options => options.CallInvoker is not null, "gRPC CallInvoker must not be null."); - } - - private void ConfigureCallInvoker(OptionsBuilder builder) - { -#if NETSTANDARD - builder.Configure((options, config) => - { - Uri address = config.GetFunctionsHostGrpcUri(); - Channel c = new Channel(address.Host, address.Port, ChannelCredentials.Insecure); - options.CallInvoker = c.CreateCallInvoker(); - }); - -#else - // Instead of building the GrpcChannel/CallInvoker ourselves, we use Grpc.Net.ClientFactory to - // construct and configure the CallInvoker for us, then we attach that to our options. - builder.Services.AddGrpcClient(_ => { }) - .ConfigureForFunctionsHostGrpc(); - - builder.Configure((options, extractor) => - { - options.CallInvoker = extractor.CallInvoker; - }); -#endif - } - -#if !NETSTANDARD - // Used as a roundabout way of getting the configured CallInvoker from Grpc.Net.ClientFactory. - private class CallInvokerExtractor - { - public CallInvokerExtractor(CallInvoker callInvoker) - { - CallInvoker = callInvoker ?? throw new ArgumentNullException(nameof(callInvoker)); - } - - public CallInvoker CallInvoker { get; } - } -#endif - } -} diff --git a/test/Worker.Extensions.Rpc.Tests/WorkerRpcStartupTests.cs b/test/Worker.Extensions.Rpc.Tests/RpcServiceCollectionExtensionsTests.cs similarity index 80% rename from test/Worker.Extensions.Rpc.Tests/WorkerRpcStartupTests.cs rename to test/Worker.Extensions.Rpc.Tests/RpcServiceCollectionExtensionsTests.cs index 46fcd1b83..3984ff07e 100644 --- a/test/Worker.Extensions.Rpc.Tests/WorkerRpcStartupTests.cs +++ b/test/Worker.Extensions.Rpc.Tests/RpcServiceCollectionExtensionsTests.cs @@ -4,12 +4,11 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Moq; using Xunit; namespace Microsoft.Azure.Functions.Worker.Extensions.Rpc.Tests { - public class WorkerRpcStartupTests + public class RpcServiceCollectionExtensionsTests { [Fact] public void Configure_AddsFunctionsGrpcOptions() @@ -24,11 +23,7 @@ public void Configure_AddsFunctionsGrpcOptions() ServiceCollection services = new(); services.AddSingleton((IConfiguration)configBuilder.Build()); - IFunctionsWorkerApplicationBuilder builder = Mock.Of( - m => m.Services == services); - - WorkerRpcStartup startup = new(); - startup.Configure(builder); + services.AddWorkerRpc(); IServiceProvider sp = services.BuildServiceProvider(); IOptions options = sp.GetService>(); diff --git a/test/Worker.Extensions.Rpc.Tests/Worker.Extensions.Rpc.Tests.csproj b/test/Worker.Extensions.Rpc.Tests/Worker.Extensions.Rpc.Tests.csproj index ad5edae31..70a469e5b 100644 --- a/test/Worker.Extensions.Rpc.Tests/Worker.Extensions.Rpc.Tests.csproj +++ b/test/Worker.Extensions.Rpc.Tests/Worker.Extensions.Rpc.Tests.csproj @@ -1,7 +1,7 @@  - net7.0 + net48;net7.0 false true true