From 64c8d6c8c685c6d6798f5f0df4cdd5808bcac953 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 13 Jun 2025 07:36:09 +0800 Subject: [PATCH 1/2] Fix shared code --- .../UnaryServerCallHandlerBenchmarkBase.cs | 5 +- .../Grpc.AspNetCore.Server.csproj | 18 +++--- .../GrpcServiceExtensions.cs | 2 + .../GrpcServiceOptions.cs | 47 ++++++++++++-- .../InterceptorRegistration.cs | 16 ----- .../Internal/GrpcProtocolConstants.cs | 3 +- .../Internal/GrpcServiceOptionsSetup.cs | 18 +++--- .../Internal/ServerCallHandlerFactory.cs | 13 ++-- src/Grpc.Core.Api/Properties/AssemblyInfo.cs | 3 +- .../ClientStreamingServerMethodInvoker.cs | 9 +-- .../DuplexStreamingServerMethodInvoker.cs | 9 +-- src/Shared/Server/InterceptorActivators.cs | 61 +++++++++++++++++++ .../Server/InterceptorPipelineBuilder.cs | 26 ++++---- src/Shared/Server/MethodOptions.cs | 12 ++-- .../Server/ServerDynamicAccessConstants.cs | 36 +++++++++++ src/Shared/Server/ServerMethodInvokerBase.cs | 3 +- .../ServerStreamingServerMethodInvoker.cs | 9 +-- src/Shared/Server/UnaryServerMethodInvoker.cs | 11 ++-- .../CallHandlerTests.cs | 13 ++-- .../DuplexStreamingServerCallHandlerTests.cs | 4 +- .../GrpcServicesExtensionsTests.cs | 3 +- .../Model/BinderServiceMethodProviderTests.cs | 7 ++- .../ServerCallHandlerFactoryTests.cs | 5 +- .../UnaryServerCallHandlerTests.cs | 15 +++-- 24 files changed, 250 insertions(+), 98 deletions(-) create mode 100644 src/Shared/Server/InterceptorActivators.cs create mode 100644 src/Shared/Server/ServerDynamicAccessConstants.cs diff --git a/perf/Grpc.AspNetCore.Microbenchmarks/Server/UnaryServerCallHandlerBenchmarkBase.cs b/perf/Grpc.AspNetCore.Microbenchmarks/Server/UnaryServerCallHandlerBenchmarkBase.cs index f25bb4a8c..3f6bcd7d4 100644 --- a/perf/Grpc.AspNetCore.Microbenchmarks/Server/UnaryServerCallHandlerBenchmarkBase.cs +++ b/perf/Grpc.AspNetCore.Microbenchmarks/Server/UnaryServerCallHandlerBenchmarkBase.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -83,7 +83,8 @@ public void GlobalSetup() compressionProviders: CompressionProviders, responseCompressionAlgorithm: ResponseCompressionAlgorithm, interceptors: Interceptors), - new TestGrpcServiceActivator(new TestService())), + new TestGrpcServiceActivator(new TestService()), + new InterceptorActivators(serviceProvider)), NullLoggerFactory.Instance); _trailers = new HeaderDictionary(); diff --git a/src/Grpc.AspNetCore.Server/Grpc.AspNetCore.Server.csproj b/src/Grpc.AspNetCore.Server/Grpc.AspNetCore.Server.csproj index 9ca7961ce..5c9434c7d 100644 --- a/src/Grpc.AspNetCore.Server/Grpc.AspNetCore.Server.csproj +++ b/src/Grpc.AspNetCore.Server/Grpc.AspNetCore.Server.csproj @@ -20,14 +20,16 @@ - - - - - - - - + + + + + + + + + + diff --git a/src/Grpc.AspNetCore.Server/GrpcServiceExtensions.cs b/src/Grpc.AspNetCore.Server/GrpcServiceExtensions.cs index a698b0591..460ff1b48 100644 --- a/src/Grpc.AspNetCore.Server/GrpcServiceExtensions.cs +++ b/src/Grpc.AspNetCore.Server/GrpcServiceExtensions.cs @@ -22,6 +22,7 @@ using Grpc.AspNetCore.Server.Model; using Grpc.AspNetCore.Server.Model.Internal; using Grpc.Shared; +using Grpc.Shared.Server; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -69,6 +70,7 @@ public static IGrpcServerBuilder AddGrpc(this IServiceCollection services) services.AddOptions(); services.TryAddSingleton(); services.TryAddSingleton(typeof(ServerCallHandlerFactory<>)); + services.TryAddSingleton(); services.TryAddSingleton(typeof(IGrpcServiceActivator<>), typeof(DefaultGrpcServiceActivator<>)); services.TryAddSingleton(typeof(IGrpcInterceptorActivator<>), typeof(DefaultGrpcInterceptorActivator<>)); services.TryAddEnumerable(ServiceDescriptor.Singleton, GrpcServiceOptionsSetup>()); diff --git a/src/Grpc.AspNetCore.Server/GrpcServiceOptions.cs b/src/Grpc.AspNetCore.Server/GrpcServiceOptions.cs index 9a6b53f04..bb79664d5 100644 --- a/src/Grpc.AspNetCore.Server/GrpcServiceOptions.cs +++ b/src/Grpc.AspNetCore.Server/GrpcServiceOptions.cs @@ -27,10 +27,10 @@ namespace Grpc.AspNetCore.Server; public class GrpcServiceOptions { internal IList? _compressionProviders; - internal bool _maxReceiveMessageSizeConfigured; internal int? _maxReceiveMessageSize; - internal bool _maxSendMessageSizeConfigured; internal int? _maxSendMessageSize; + internal bool _maxSendMessageSizeSpecified; + internal bool _maxReceiveMessageSizeSpecified; /// /// Gets or sets the maximum message size in bytes that can be sent from the server. @@ -45,7 +45,24 @@ public int? MaxSendMessageSize set { _maxSendMessageSize = value; - _maxSendMessageSizeConfigured = true; + MaxSendMessageSizeSpecified = true; + } + } + + /// + /// Gets or sets a flag indicating whether is specified. + /// This flag is automatically set to true when is configured. + /// + public bool MaxSendMessageSizeSpecified + { + get => _maxSendMessageSizeSpecified; + set + { + _maxSendMessageSizeSpecified = value; + if (!_maxSendMessageSizeSpecified) + { + _maxSendMessageSize = null; + } } } @@ -62,7 +79,24 @@ public int? MaxReceiveMessageSize set { _maxReceiveMessageSize = value; - _maxReceiveMessageSizeConfigured = true; + MaxReceiveMessageSizeSpecified = true; + } + } + + /// + /// Gets or sets a flag indicating whether is specified. + /// This flag is automatically set to true when is configured. + /// + public bool MaxReceiveMessageSizeSpecified + { + get => _maxReceiveMessageSizeSpecified; + set + { + _maxReceiveMessageSizeSpecified = value; + if (!_maxReceiveMessageSizeSpecified) + { + _maxReceiveMessageSize = null; + } } } @@ -113,7 +147,10 @@ public IList CompressionProviders /// public InterceptorCollection Interceptors { get; } = new InterceptorCollection(); - internal bool SuppressCreatingService { get; set; } + /// + /// Gets or sets a value indicating whether creating a service is suppressed when handling a gRPC call. + /// + public bool SuppressCreatingService { get; set; } } /// diff --git a/src/Grpc.AspNetCore.Server/InterceptorRegistration.cs b/src/Grpc.AspNetCore.Server/InterceptorRegistration.cs index b57f378b3..3ee68c5e3 100644 --- a/src/Grpc.AspNetCore.Server/InterceptorRegistration.cs +++ b/src/Grpc.AspNetCore.Server/InterceptorRegistration.cs @@ -60,24 +60,8 @@ internal InterceptorRegistration([DynamicallyAccessedMembers(InterceptorAccessib /// public IReadOnlyList Arguments => _args; - private IGrpcInterceptorActivator? _interceptorActivator; private ObjectFactory? _factory; - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", - Justification = "Type parameter members are preserved with DynamicallyAccessedMembers on InterceptorRegistration.Type property.")] - [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", - Justification = "Type definition is explicitly specified and type argument is always an Interceptor type.")] - internal IGrpcInterceptorActivator GetActivator(IServiceProvider serviceProvider) - { - // Not thread safe. Side effect is resolving the service twice. - if (_interceptorActivator == null) - { - _interceptorActivator = (IGrpcInterceptorActivator)serviceProvider.GetRequiredService(typeof(IGrpcInterceptorActivator<>).MakeGenericType(Type)); - } - - return _interceptorActivator; - } - internal ObjectFactory GetFactory() { // Not thread safe. Side effect is resolving the factory twice. diff --git a/src/Grpc.AspNetCore.Server/Internal/GrpcProtocolConstants.cs b/src/Grpc.AspNetCore.Server/Internal/GrpcProtocolConstants.cs index 269cf9a4c..b5097a053 100644 --- a/src/Grpc.AspNetCore.Server/Internal/GrpcProtocolConstants.cs +++ b/src/Grpc.AspNetCore.Server/Internal/GrpcProtocolConstants.cs @@ -18,6 +18,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using Grpc.Shared.Server; using Microsoft.AspNetCore.Http; using Microsoft.Net.Http.Headers; @@ -82,5 +83,5 @@ internal static int GetCancelErrorCode(string protocol) return IsHttp3(protocol) ? Http3ResetStreamCancel : Http2ResetStreamCancel; } - internal const DynamicallyAccessedMemberTypes ServiceAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods; + internal const DynamicallyAccessedMemberTypes ServiceAccessibility = ServerDynamicAccessConstants.ServiceAccessibility; } diff --git a/src/Grpc.AspNetCore.Server/Internal/GrpcServiceOptionsSetup.cs b/src/Grpc.AspNetCore.Server/Internal/GrpcServiceOptionsSetup.cs index f6a9193af..d57dd6301 100644 --- a/src/Grpc.AspNetCore.Server/Internal/GrpcServiceOptionsSetup.cs +++ b/src/Grpc.AspNetCore.Server/Internal/GrpcServiceOptionsSetup.cs @@ -18,23 +18,19 @@ using System.IO.Compression; using Grpc.Net.Compression; +using Grpc.Shared.Server; using Microsoft.Extensions.Options; namespace Grpc.AspNetCore.Server.Internal; internal sealed class GrpcServiceOptionsSetup : IConfigureOptions { - // Default to no send limit and 4mb receive limit. - // Matches the gRPC C impl defaults - // https://github.com/grpc/grpc/blob/977df7208a6e3f9a62a6369af5cd6e4b69b4fdec/include/grpc/impl/codegen/grpc_types.h#L413-L416 - internal const int DefaultReceiveMaxMessageSize = 4 * 1024 * 1024; - public void Configure(GrpcServiceOptions options) { - if (!options._maxReceiveMessageSizeConfigured) + if (!options.MaxReceiveMessageSizeSpecified) { // Only default MaxReceiveMessageSize if it was not configured - options._maxReceiveMessageSize = DefaultReceiveMaxMessageSize; + options._maxReceiveMessageSize = MethodOptions.DefaultReceiveMaxMessageSize; } if (options._compressionProviders == null || options._compressionProviders.Count == 0) { @@ -56,8 +52,12 @@ public GrpcServiceOptionsSetup(IOptions options) public void Configure(GrpcServiceOptions options) { - options.MaxReceiveMessageSize = _options.MaxReceiveMessageSize; - options.MaxSendMessageSize = _options.MaxSendMessageSize; + // Copy internal fields to avoid running logic in property setters. + options._maxReceiveMessageSize = _options._maxReceiveMessageSize; + options._maxReceiveMessageSizeSpecified = _options._maxReceiveMessageSizeSpecified; + options._maxSendMessageSize = _options._maxSendMessageSize; + options._maxSendMessageSizeSpecified = _options._maxSendMessageSizeSpecified; + options.EnableDetailedErrors = _options.EnableDetailedErrors; options.ResponseCompressionAlgorithm = _options.ResponseCompressionAlgorithm; options.ResponseCompressionLevel = _options.ResponseCompressionLevel; diff --git a/src/Grpc.AspNetCore.Server/Internal/ServerCallHandlerFactory.cs b/src/Grpc.AspNetCore.Server/Internal/ServerCallHandlerFactory.cs index 7da6ff722..1a366b830 100644 --- a/src/Grpc.AspNetCore.Server/Internal/ServerCallHandlerFactory.cs +++ b/src/Grpc.AspNetCore.Server/Internal/ServerCallHandlerFactory.cs @@ -35,6 +35,7 @@ internal sealed partial class ServerCallHandlerFactory<[DynamicallyAccessedMembe { private readonly ILoggerFactory _loggerFactory; private readonly IGrpcServiceActivator _serviceActivator; + private readonly InterceptorActivators _interceptorActivators; private readonly GrpcServiceOptions _globalOptions; private readonly GrpcServiceOptions _serviceOptions; @@ -42,10 +43,12 @@ public ServerCallHandlerFactory( ILoggerFactory loggerFactory, IOptions globalOptions, IOptions> serviceOptions, - IGrpcServiceActivator serviceActivator) + IGrpcServiceActivator serviceActivator, + InterceptorActivators interceptorActivators) { _loggerFactory = loggerFactory; _serviceActivator = serviceActivator; + _interceptorActivators = interceptorActivators; _serviceOptions = serviceOptions.Value; _globalOptions = globalOptions.Value; } @@ -61,7 +64,7 @@ public UnaryServerCallHandler CreateUnary(invoker, method, options, _serviceActivator); + var methodInvoker = new UnaryServerMethodInvoker(invoker, method, options, _serviceActivator, _interceptorActivators); return new UnaryServerCallHandler(methodInvoker, _loggerFactory); } @@ -71,7 +74,7 @@ public ClientStreamingServerCallHandler CreateCli where TResponse : class { var options = CreateMethodOptions(); - var methodInvoker = new ClientStreamingServerMethodInvoker(invoker, method, options, _serviceActivator); + var methodInvoker = new ClientStreamingServerMethodInvoker(invoker, method, options, _serviceActivator, _interceptorActivators); return new ClientStreamingServerCallHandler(methodInvoker, _loggerFactory); } @@ -81,7 +84,7 @@ public DuplexStreamingServerCallHandler CreateDup where TResponse : class { var options = CreateMethodOptions(); - var methodInvoker = new DuplexStreamingServerMethodInvoker(invoker, method, options, _serviceActivator); + var methodInvoker = new DuplexStreamingServerMethodInvoker(invoker, method, options, _serviceActivator, _interceptorActivators); return new DuplexStreamingServerCallHandler(methodInvoker, _loggerFactory); } @@ -91,7 +94,7 @@ public ServerStreamingServerCallHandler CreateSer where TResponse : class { var options = CreateMethodOptions(); - var methodInvoker = new ServerStreamingServerMethodInvoker(invoker, method, options, _serviceActivator); + var methodInvoker = new ServerStreamingServerMethodInvoker(invoker, method, options, _serviceActivator, _interceptorActivators); return new ServerStreamingServerCallHandler(methodInvoker, _loggerFactory); } diff --git a/src/Grpc.Core.Api/Properties/AssemblyInfo.cs b/src/Grpc.Core.Api/Properties/AssemblyInfo.cs index 260ac3f48..fb7104e70 100644 --- a/src/Grpc.Core.Api/Properties/AssemblyInfo.cs +++ b/src/Grpc.Core.Api/Properties/AssemblyInfo.cs @@ -16,7 +16,6 @@ #endregion -using System.Reflection; using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Grpc.Core,PublicKey=" + @@ -48,4 +47,4 @@ "00240000048000009400000006020000002400005253413100040000010001002f5797a92c6fcde81bd4098f43" + "0442bb8e12768722de0b0cb1b15e955b32a11352740ee59f2c94c48edc8e177d1052536b8ac651bce11ce5da3a" + "27fc95aff3dc604a6971417453f9483c7b5e836756d5b271bf8f2403fe186e31956148c03d804487cf642f8cc0" + - "71394ee9672dfe5b55ea0f95dfd5a7f77d22c962ccf51320d3")] \ No newline at end of file + "71394ee9672dfe5b55ea0f95dfd5a7f77d22c962ccf51320d3")] diff --git a/src/Shared/Server/ClientStreamingServerMethodInvoker.cs b/src/Shared/Server/ClientStreamingServerMethodInvoker.cs index 5a559145a..8e95adb7c 100644 --- a/src/Shared/Server/ClientStreamingServerMethodInvoker.cs +++ b/src/Shared/Server/ClientStreamingServerMethodInvoker.cs @@ -18,7 +18,6 @@ using System.Diagnostics.CodeAnalysis; using Grpc.AspNetCore.Server; -using Grpc.AspNetCore.Server.Internal; using Grpc.AspNetCore.Server.Model; using Grpc.Core; using Microsoft.AspNetCore.Http; @@ -31,7 +30,7 @@ namespace Grpc.Shared.Server; /// Service type for this method. /// Request message type for this method. /// Response message type for this method. -internal sealed class ClientStreamingServerMethodInvoker<[DynamicallyAccessedMembers(GrpcProtocolConstants.ServiceAccessibility)] TService, TRequest, TResponse> : ServerMethodInvokerBase +internal sealed class ClientStreamingServerMethodInvoker<[DynamicallyAccessedMembers(ServerDynamicAccessConstants.ServiceAccessibility)] TService, TRequest, TResponse> : ServerMethodInvokerBase where TRequest : class where TResponse : class where TService : class @@ -46,18 +45,20 @@ internal sealed class ClientStreamingServerMethodInvoker<[DynamicallyAccessedMem /// The description of the gRPC method. /// The options used to execute the method. /// The service activator used to create service instances. + /// The interceptor activators used to create interceptor instances. public ClientStreamingServerMethodInvoker( ClientStreamingServerMethod invoker, Method method, MethodOptions options, - IGrpcServiceActivator serviceActivator) + IGrpcServiceActivator serviceActivator, + InterceptorActivators interceptorActivators) : base(method, options, serviceActivator) { _invoker = invoker; if (Options.HasInterceptors) { - var interceptorPipeline = new InterceptorPipelineBuilder(Options.Interceptors); + var interceptorPipeline = new InterceptorPipelineBuilder(Options.Interceptors, interceptorActivators); _pipelineInvoker = interceptorPipeline.ClientStreamingPipeline(ResolvedInterceptorInvoker); } } diff --git a/src/Shared/Server/DuplexStreamingServerMethodInvoker.cs b/src/Shared/Server/DuplexStreamingServerMethodInvoker.cs index 7044be7fc..4153d3fc6 100644 --- a/src/Shared/Server/DuplexStreamingServerMethodInvoker.cs +++ b/src/Shared/Server/DuplexStreamingServerMethodInvoker.cs @@ -18,7 +18,6 @@ using System.Diagnostics.CodeAnalysis; using Grpc.AspNetCore.Server; -using Grpc.AspNetCore.Server.Internal; using Grpc.AspNetCore.Server.Model; using Grpc.Core; using Microsoft.AspNetCore.Http; @@ -31,7 +30,7 @@ namespace Grpc.Shared.Server; /// Service type for this method. /// Request message type for this method. /// Response message type for this method. -internal sealed class DuplexStreamingServerMethodInvoker<[DynamicallyAccessedMembers(GrpcProtocolConstants.ServiceAccessibility)] TService, TRequest, TResponse> : ServerMethodInvokerBase +internal sealed class DuplexStreamingServerMethodInvoker<[DynamicallyAccessedMembers(ServerDynamicAccessConstants.ServiceAccessibility)] TService, TRequest, TResponse> : ServerMethodInvokerBase where TRequest : class where TResponse : class where TService : class @@ -46,18 +45,20 @@ internal sealed class DuplexStreamingServerMethodInvoker<[DynamicallyAccessedMem /// The description of the gRPC method. /// The options used to execute the method. /// The service activator used to create service instances. + /// The interceptor activators used to create interceptor instances. public DuplexStreamingServerMethodInvoker( DuplexStreamingServerMethod invoker, Method method, MethodOptions options, - IGrpcServiceActivator serviceActivator) + IGrpcServiceActivator serviceActivator, + InterceptorActivators interceptorActivators) : base(method, options, serviceActivator) { _invoker = invoker; if (Options.HasInterceptors) { - var interceptorPipeline = new InterceptorPipelineBuilder(Options.Interceptors); + var interceptorPipeline = new InterceptorPipelineBuilder(Options.Interceptors, interceptorActivators); _pipelineInvoker = interceptorPipeline.DuplexStreamingPipeline(ResolvedInterceptorInvoker); } } diff --git a/src/Shared/Server/InterceptorActivators.cs b/src/Shared/Server/InterceptorActivators.cs new file mode 100644 index 000000000..2e1a2ffd7 --- /dev/null +++ b/src/Shared/Server/InterceptorActivators.cs @@ -0,0 +1,61 @@ +#region Copyright notice and license + +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using Grpc.AspNetCore.Server; +using Microsoft.Extensions.DependencyInjection; + +namespace Grpc.Shared.Server; + +/// +/// Provides caching and activation of gRPC interceptor activators. +/// +internal sealed class InterceptorActivators +{ + private readonly ConcurrentDictionary _cachedActivators = new(); + private readonly IServiceProvider _serviceProvider; + + /// + /// Initializes a new instance of the class. + /// + /// The service provider used to resolve activators. + public InterceptorActivators(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + /// + /// Gets the for the specified interceptor type. + /// + /// The interceptor type. + /// The activator for the specified interceptor type. + public IGrpcInterceptorActivator GetInterceptorActivator(Type type) + { + return _cachedActivators.GetOrAdd(type, CreateActivator, _serviceProvider); + } + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", + Justification = "Type parameter members are preserved with DynamicallyAccessedMembers on InterceptorRegistration.Type property.")] + [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", + Justification = "Type definition is explicitly specified and type argument is always an Interceptor type.")] + private static IGrpcInterceptorActivator CreateActivator(Type type, IServiceProvider serviceProvider) + { + return (IGrpcInterceptorActivator)serviceProvider.GetRequiredService(typeof(IGrpcInterceptorActivator<>).MakeGenericType(type)); + } +} diff --git a/src/Shared/Server/InterceptorPipelineBuilder.cs b/src/Shared/Server/InterceptorPipelineBuilder.cs index 71e5e9a1d..4fb186f6d 100644 --- a/src/Shared/Server/InterceptorPipelineBuilder.cs +++ b/src/Shared/Server/InterceptorPipelineBuilder.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -27,22 +27,24 @@ internal sealed class InterceptorPipelineBuilder where TResponse : class { private readonly IReadOnlyList _interceptors; + private readonly InterceptorActivators _interceptorActivators; - public InterceptorPipelineBuilder(IReadOnlyList interceptors) + public InterceptorPipelineBuilder(IReadOnlyList interceptors, InterceptorActivators interceptorActivators) { _interceptors = interceptors; + _interceptorActivators = interceptorActivators; } public ClientStreamingServerMethod ClientStreamingPipeline(ClientStreamingServerMethod innerInvoker) { return BuildPipeline(innerInvoker, BuildInvoker); - static ClientStreamingServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, ClientStreamingServerMethod next) + static ClientStreamingServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, InterceptorActivators interceptorActivators, ClientStreamingServerMethod next) { return async (requestStream, context) => { var serviceProvider = context.GetHttpContext().RequestServices; - var interceptorActivator = interceptorRegistration.GetActivator(serviceProvider); + var interceptorActivator = interceptorActivators.GetInterceptorActivator(interceptorRegistration.Type); var interceptorHandle = CreateInterceptor(interceptorRegistration, interceptorActivator, serviceProvider); try @@ -61,12 +63,12 @@ internal DuplexStreamingServerMethod DuplexStreamingPipelin { return BuildPipeline(innerInvoker, BuildInvoker); - static DuplexStreamingServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, DuplexStreamingServerMethod next) + static DuplexStreamingServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, InterceptorActivators interceptorActivators, DuplexStreamingServerMethod next) { return async (requestStream, responseStream, context) => { var serviceProvider = context.GetHttpContext().RequestServices; - var interceptorActivator = interceptorRegistration.GetActivator(serviceProvider); + var interceptorActivator = interceptorActivators.GetInterceptorActivator(interceptorRegistration.Type); var interceptorHandle = CreateInterceptor(interceptorRegistration, interceptorActivator, serviceProvider); try @@ -85,12 +87,12 @@ internal ServerStreamingServerMethod ServerStreamingPipelin { return BuildPipeline(innerInvoker, BuildInvoker); - static ServerStreamingServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, ServerStreamingServerMethod next) + static ServerStreamingServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, InterceptorActivators interceptorActivators, ServerStreamingServerMethod next) { return async (request, responseStream, context) => { var serviceProvider = context.GetHttpContext().RequestServices; - var interceptorActivator = interceptorRegistration.GetActivator(serviceProvider); + var interceptorActivator = interceptorActivators.GetInterceptorActivator(interceptorRegistration.Type); var interceptorHandle = CreateInterceptor(interceptorRegistration, interceptorActivator, serviceProvider); if (interceptorHandle.Instance == null) @@ -114,12 +116,12 @@ internal UnaryServerMethod UnaryPipeline(UnaryServerMethod< { return BuildPipeline(innerInvoker, BuildInvoker); - static UnaryServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, UnaryServerMethod next) + static UnaryServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, InterceptorActivators interceptorActivators, UnaryServerMethod next) { return async (request, context) => { var serviceProvider = context.GetHttpContext().RequestServices; - var interceptorActivator = interceptorRegistration.GetActivator(serviceProvider); + var interceptorActivator = interceptorActivators.GetInterceptorActivator(interceptorRegistration.Type); var interceptorHandle = CreateInterceptor(interceptorRegistration, interceptorActivator, serviceProvider); try @@ -134,7 +136,7 @@ static UnaryServerMethod BuildInvoker(InterceptorRegistrati } } - private T BuildPipeline(T innerInvoker, Func wrapInvoker) + private T BuildPipeline(T innerInvoker, Func wrapInvoker) { // The inner invoker will create the service instance and invoke the method var resolvedInvoker = innerInvoker; @@ -142,7 +144,7 @@ private T BuildPipeline(T innerInvoker, Func w // The list is reversed during construction so the first interceptor is built last and invoked first for (var i = _interceptors.Count - 1; i >= 0; i--) { - resolvedInvoker = wrapInvoker(_interceptors[i], resolvedInvoker); + resolvedInvoker = wrapInvoker(_interceptors[i], _interceptorActivators, resolvedInvoker); } return resolvedInvoker; diff --git a/src/Shared/Server/MethodOptions.cs b/src/Shared/Server/MethodOptions.cs index 02d51d367..b2d68f624 100644 --- a/src/Shared/Server/MethodOptions.cs +++ b/src/Shared/Server/MethodOptions.cs @@ -18,7 +18,6 @@ using System.IO.Compression; using Grpc.AspNetCore.Server; -using Grpc.AspNetCore.Server.Internal; using Grpc.Net.Compression; namespace Grpc.Shared.Server; @@ -28,6 +27,11 @@ namespace Grpc.Shared.Server; /// internal sealed class MethodOptions { + // Default to no send limit and 4mb receive limit. + // Matches the gRPC C impl defaults + // https://github.com/grpc/grpc/blob/977df7208a6e3f9a62a6369af5cd6e4b69b4fdec/include/grpc/impl/codegen/grpc_types.h#L413-L416 + internal const int DefaultReceiveMaxMessageSize = 4 * 1024 * 1024; + /// /// Gets the list of compression providers used to compress and decompress gRPC messages. /// @@ -116,7 +120,7 @@ public static MethodOptions Create(IEnumerable serviceOption var tempInterceptors = new List(); int? maxSendMessageSize = null; var maxSendMessageSizeConfigured = false; - int? maxReceiveMessageSize = GrpcServiceOptionsSetup.DefaultReceiveMaxMessageSize; + int? maxReceiveMessageSize = DefaultReceiveMaxMessageSize; var maxReceiveMessageSizeConfigured = false; bool? enableDetailedErrors = null; string? responseCompressionAlgorithm = null; @@ -127,12 +131,12 @@ public static MethodOptions Create(IEnumerable serviceOption { AddCompressionProviders(resolvedCompressionProviders, options.CompressionProviders); tempInterceptors.InsertRange(0, options.Interceptors); - if (!maxSendMessageSizeConfigured && options._maxSendMessageSizeConfigured) + if (!maxSendMessageSizeConfigured && options.MaxSendMessageSizeSpecified) { maxSendMessageSize = options.MaxSendMessageSize; maxSendMessageSizeConfigured = true; } - if (!maxReceiveMessageSizeConfigured && options._maxReceiveMessageSizeConfigured) + if (!maxReceiveMessageSizeConfigured && options.MaxReceiveMessageSizeSpecified) { maxReceiveMessageSize = options.MaxReceiveMessageSize; maxReceiveMessageSizeConfigured = true; diff --git a/src/Shared/Server/ServerDynamicAccessConstants.cs b/src/Shared/Server/ServerDynamicAccessConstants.cs new file mode 100644 index 000000000..e785a6afd --- /dev/null +++ b/src/Shared/Server/ServerDynamicAccessConstants.cs @@ -0,0 +1,36 @@ +#region Copyright notice and license + +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.Diagnostics.CodeAnalysis; + +namespace Grpc.Shared.Server; + +/// +/// Contains constants for specifying dynamically accessed member types for gRPC server services. +/// +internal static class ServerDynamicAccessConstants +{ + /// + /// Specifies the member types that should be dynamically accessed for gRPC services. + /// Includes public constructors, public methods, and non-public methods. + /// + internal const DynamicallyAccessedMemberTypes ServiceAccessibility = + DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.PublicMethods | + DynamicallyAccessedMemberTypes.NonPublicMethods; +} diff --git a/src/Shared/Server/ServerMethodInvokerBase.cs b/src/Shared/Server/ServerMethodInvokerBase.cs index c74bcf8a5..c599b6dad 100644 --- a/src/Shared/Server/ServerMethodInvokerBase.cs +++ b/src/Shared/Server/ServerMethodInvokerBase.cs @@ -18,7 +18,6 @@ using System.Diagnostics.CodeAnalysis; using Grpc.AspNetCore.Server; -using Grpc.AspNetCore.Server.Internal; using Grpc.Core; using Microsoft.AspNetCore.Http; @@ -30,7 +29,7 @@ namespace Grpc.Shared.Server; /// Service type for this method. /// Request message type for this method. /// Response message type for this method. -internal abstract class ServerMethodInvokerBase<[DynamicallyAccessedMembers(GrpcProtocolConstants.ServiceAccessibility)] TService, TRequest, TResponse> +internal abstract class ServerMethodInvokerBase<[DynamicallyAccessedMembers(ServerDynamicAccessConstants.ServiceAccessibility)] TService, TRequest, TResponse> where TRequest : class where TResponse : class where TService : class diff --git a/src/Shared/Server/ServerStreamingServerMethodInvoker.cs b/src/Shared/Server/ServerStreamingServerMethodInvoker.cs index 0995c4e6b..a29aabfb6 100644 --- a/src/Shared/Server/ServerStreamingServerMethodInvoker.cs +++ b/src/Shared/Server/ServerStreamingServerMethodInvoker.cs @@ -18,7 +18,6 @@ using System.Diagnostics.CodeAnalysis; using Grpc.AspNetCore.Server; -using Grpc.AspNetCore.Server.Internal; using Grpc.AspNetCore.Server.Model; using Grpc.Core; using Microsoft.AspNetCore.Http; @@ -31,7 +30,7 @@ namespace Grpc.Shared.Server; /// Service type for this method. /// Request message type for this method. /// Response message type for this method. -internal sealed class ServerStreamingServerMethodInvoker<[DynamicallyAccessedMembers(GrpcProtocolConstants.ServiceAccessibility)] TService, TRequest, TResponse> : ServerMethodInvokerBase +internal sealed class ServerStreamingServerMethodInvoker<[DynamicallyAccessedMembers(ServerDynamicAccessConstants.ServiceAccessibility)] TService, TRequest, TResponse> : ServerMethodInvokerBase where TRequest : class where TResponse : class where TService : class @@ -46,18 +45,20 @@ internal sealed class ServerStreamingServerMethodInvoker<[DynamicallyAccessedMem /// The description of the gRPC method. /// The options used to execute the method. /// The service activator used to create service instances. + /// The interceptor activators used to create interceptor instances. public ServerStreamingServerMethodInvoker( ServerStreamingServerMethod invoker, Method method, MethodOptions options, - IGrpcServiceActivator serviceActivator) + IGrpcServiceActivator serviceActivator, + InterceptorActivators interceptorActivators) : base(method, options, serviceActivator) { _invoker = invoker; if (Options.HasInterceptors) { - var interceptorPipeline = new InterceptorPipelineBuilder(Options.Interceptors); + var interceptorPipeline = new InterceptorPipelineBuilder(Options.Interceptors, interceptorActivators); _pipelineInvoker = interceptorPipeline.ServerStreamingPipeline(ResolvedInterceptorInvoker); } } diff --git a/src/Shared/Server/UnaryServerMethodInvoker.cs b/src/Shared/Server/UnaryServerMethodInvoker.cs index 565f1999c..09398689c 100644 --- a/src/Shared/Server/UnaryServerMethodInvoker.cs +++ b/src/Shared/Server/UnaryServerMethodInvoker.cs @@ -19,7 +19,6 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.ExceptionServices; using Grpc.AspNetCore.Server; -using Grpc.AspNetCore.Server.Internal; using Grpc.AspNetCore.Server.Model; using Grpc.Core; using Microsoft.AspNetCore.Http; @@ -32,7 +31,7 @@ namespace Grpc.Shared.Server; /// Service type for this method. /// Request message type for this method. /// Response message type for this method. -internal sealed class UnaryServerMethodInvoker<[DynamicallyAccessedMembers(GrpcProtocolConstants.ServiceAccessibility)] TService, TRequest, TResponse> : ServerMethodInvokerBase +internal sealed class UnaryServerMethodInvoker<[DynamicallyAccessedMembers(ServerDynamicAccessConstants.ServiceAccessibility)] TService, TRequest, TResponse> : ServerMethodInvokerBase where TRequest : class where TResponse : class where TService : class @@ -47,18 +46,20 @@ internal sealed class UnaryServerMethodInvoker<[DynamicallyAccessedMembers(GrpcP /// The description of the gRPC method. /// The options used to execute the method. /// The service activator used to create service instances. + /// The interceptor activators used to create interceptor instances. public UnaryServerMethodInvoker( UnaryServerMethod invoker, Method method, MethodOptions options, - IGrpcServiceActivator serviceActivator) + IGrpcServiceActivator serviceActivator, + InterceptorActivators interceptorActivators) : base(method, options, serviceActivator) { _invoker = invoker; if (Options.HasInterceptors) { - var interceptorPipeline = new InterceptorPipelineBuilder(Options.Interceptors); + var interceptorPipeline = new InterceptorPipelineBuilder(Options.Interceptors, interceptorActivators); _pipelineInvoker = interceptorPipeline.UnaryPipeline(ResolvedInterceptorInvoker); } } @@ -158,7 +159,7 @@ private async Task AwaitInvoker(Task invokerTask, GrpcActi } } - private async Task AwaitServiceReleaseAndThrow(ValueTask releaseTask, ExceptionDispatchInfo ex) + private static async Task AwaitServiceReleaseAndThrow(ValueTask releaseTask, ExceptionDispatchInfo ex) { await releaseTask; ex.Throw(); diff --git a/test/Grpc.AspNetCore.Server.Tests/CallHandlerTests.cs b/test/Grpc.AspNetCore.Server.Tests/CallHandlerTests.cs index 4f3af453a..e48245d4a 100644 --- a/test/Grpc.AspNetCore.Server.Tests/CallHandlerTests.cs +++ b/test/Grpc.AspNetCore.Server.Tests/CallHandlerTests.cs @@ -249,6 +249,7 @@ private static ServerCallHandlerBase Crea Func? handlerAction = null) { var method = new Method(methodType, "test", "test", _marshaller, _marshaller); + var interceptorActivators = new InterceptorActivators(new ServiceCollection().BuildServiceProvider()); switch (methodType) { @@ -262,7 +263,8 @@ private static ServerCallHandlerBase Crea }, method, HttpContextServerCallContextHelper.CreateMethodOptions(), - new TestGrpcServiceActivator()), + new TestGrpcServiceActivator(), + interceptorActivators), loggerFactory ?? NullLoggerFactory.Instance); case MethodType.ClientStreaming: return new ClientStreamingServerCallHandler( @@ -274,7 +276,8 @@ private static ServerCallHandlerBase Crea }, method, HttpContextServerCallContextHelper.CreateMethodOptions(), - new TestGrpcServiceActivator()), + new TestGrpcServiceActivator(), + interceptorActivators), loggerFactory ?? NullLoggerFactory.Instance); case MethodType.ServerStreaming: return new ServerStreamingServerCallHandler( @@ -285,7 +288,8 @@ private static ServerCallHandlerBase Crea }, method, HttpContextServerCallContextHelper.CreateMethodOptions(), - new TestGrpcServiceActivator()), + new TestGrpcServiceActivator(), + interceptorActivators), loggerFactory ?? NullLoggerFactory.Instance); case MethodType.DuplexStreaming: return new DuplexStreamingServerCallHandler( @@ -296,7 +300,8 @@ private static ServerCallHandlerBase Crea }, method, HttpContextServerCallContextHelper.CreateMethodOptions(), - new TestGrpcServiceActivator()), + new TestGrpcServiceActivator(), + interceptorActivators), loggerFactory ?? NullLoggerFactory.Instance); default: throw new ArgumentException("Unexpected method type: " + methodType); diff --git a/test/Grpc.AspNetCore.Server.Tests/DuplexStreamingServerCallHandlerTests.cs b/test/Grpc.AspNetCore.Server.Tests/DuplexStreamingServerCallHandlerTests.cs index c2bd64e12..c0b64cfe7 100644 --- a/test/Grpc.AspNetCore.Server.Tests/DuplexStreamingServerCallHandlerTests.cs +++ b/test/Grpc.AspNetCore.Server.Tests/DuplexStreamingServerCallHandlerTests.cs @@ -23,6 +23,7 @@ using Grpc.Tests.Shared; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; using NUnit.Framework; @@ -47,7 +48,8 @@ public async Task HandleCallAsync_ConcurrentReadAndWrite_Success() }, new Method(MethodType.DuplexStreaming, "test", "test", _marshaller, _marshaller), HttpContextServerCallContextHelper.CreateMethodOptions(), - new TestGrpcServiceActivator()); + new TestGrpcServiceActivator(), + new InterceptorActivators(new ServiceCollection().BuildServiceProvider())); var handler = new DuplexStreamingServerCallHandler(invoker, NullLoggerFactory.Instance); // Verify there isn't a race condition when reading/writing on seperate threads. diff --git a/test/Grpc.AspNetCore.Server.Tests/GrpcServicesExtensionsTests.cs b/test/Grpc.AspNetCore.Server.Tests/GrpcServicesExtensionsTests.cs index f5f756ca0..c3b9ed73b 100644 --- a/test/Grpc.AspNetCore.Server.Tests/GrpcServicesExtensionsTests.cs +++ b/test/Grpc.AspNetCore.Server.Tests/GrpcServicesExtensionsTests.cs @@ -17,6 +17,7 @@ #endregion using Grpc.AspNetCore.Server.Internal; +using Grpc.Shared.Server; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using NUnit.Framework; @@ -45,7 +46,7 @@ public void AddGrpc_ConfigureOptions_OptionsSet() // Assert Assert.AreEqual(true, options.EnableDetailedErrors); - Assert.AreEqual(GrpcServiceOptionsSetup.DefaultReceiveMaxMessageSize, options.MaxReceiveMessageSize); + Assert.AreEqual(MethodOptions.DefaultReceiveMaxMessageSize, options.MaxReceiveMessageSize); Assert.AreEqual(1, options.MaxSendMessageSize); Assert.AreEqual(2, options.CompressionProviders.Count); diff --git a/test/Grpc.AspNetCore.Server.Tests/Model/BinderServiceMethodProviderTests.cs b/test/Grpc.AspNetCore.Server.Tests/Model/BinderServiceMethodProviderTests.cs index 2d6b16484..98c23bace 100644 --- a/test/Grpc.AspNetCore.Server.Tests/Model/BinderServiceMethodProviderTests.cs +++ b/test/Grpc.AspNetCore.Server.Tests/Model/BinderServiceMethodProviderTests.cs @@ -21,6 +21,7 @@ using Grpc.AspNetCore.Server.Model; using Grpc.AspNetCore.Server.Model.Internal; using Grpc.AspNetCore.Server.Tests.TestObjects; +using Grpc.Shared.Server; using Grpc.Tests.Shared; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; @@ -38,18 +39,20 @@ public async Task OnServiceMethodDiscovery_ServiceWithDuplicateMethodNames_Succe // Arrange var services = new ServiceCollection(); services.AddSingleton(); + var serviceProvider = services.BuildServiceProvider(); var serverCallHandlerFactory = new ServerCallHandlerFactory( NullLoggerFactory.Instance, Options.Create(new GrpcServiceOptions()), Options.Create>(new GrpcServiceOptions()), - new TestGrpcServiceActivator()); + new TestGrpcServiceActivator(), + new InterceptorActivators(serviceProvider)); var provider = new BinderServiceMethodProvider(NullLoggerFactory.Instance); var context = new ServiceMethodProviderContext(serverCallHandlerFactory, argument: null); var httpContext = HttpContextHelpers.CreateContext(); - httpContext.RequestServices = services.BuildServiceProvider(); + httpContext.RequestServices = serviceProvider; // Act provider.OnServiceMethodDiscovery(context); diff --git a/test/Grpc.AspNetCore.Server.Tests/ServerCallHandlerFactoryTests.cs b/test/Grpc.AspNetCore.Server.Tests/ServerCallHandlerFactoryTests.cs index c67453e2f..28dba43a3 100644 --- a/test/Grpc.AspNetCore.Server.Tests/ServerCallHandlerFactoryTests.cs +++ b/test/Grpc.AspNetCore.Server.Tests/ServerCallHandlerFactoryTests.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -17,6 +17,7 @@ #endregion using Grpc.AspNetCore.Server.Internal; +using Grpc.Shared.Server; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; @@ -37,7 +38,7 @@ public void CreateMethodOptions_MaxReceiveMessageSizeUnset_DefaultValue() var options = factory.CreateMethodOptions(); // Assert - Assert.AreEqual(GrpcServiceOptionsSetup.DefaultReceiveMaxMessageSize, options.MaxReceiveMessageSize); + Assert.AreEqual(MethodOptions.DefaultReceiveMaxMessageSize, options.MaxReceiveMessageSize); } [Test] diff --git a/test/Grpc.AspNetCore.Server.Tests/UnaryServerCallHandlerTests.cs b/test/Grpc.AspNetCore.Server.Tests/UnaryServerCallHandlerTests.cs index c7db44c96..14e8d1d12 100644 --- a/test/Grpc.AspNetCore.Server.Tests/UnaryServerCallHandlerTests.cs +++ b/test/Grpc.AspNetCore.Server.Tests/UnaryServerCallHandlerTests.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -20,6 +20,7 @@ using Grpc.Core; using Grpc.Shared.Server; using Grpc.Tests.Shared; +using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; namespace Grpc.AspNetCore.Server.Tests; @@ -39,7 +40,8 @@ public void Invoke_ThrowException_ReleaseCalledAndErrorThrown() (service, reader, context) => throw ex, new Method(MethodType.Unary, "test", "test", _marshaller, _marshaller), HttpContextServerCallContextHelper.CreateMethodOptions(), - serviceActivator); + serviceActivator, + new InterceptorActivators(new ServiceCollection().BuildServiceProvider())); var httpContext = HttpContextHelpers.CreateContext(); // Act @@ -62,7 +64,8 @@ public async Task Invoke_ThrowExceptionAwaitedRelease_ReleaseCalledAndErrorThrow (service, reader, context) => throw thrownException, new Method(MethodType.Unary, "test", "test", _marshaller, _marshaller), HttpContextServerCallContextHelper.CreateMethodOptions(), - serviceActivator); + serviceActivator, + new InterceptorActivators(new ServiceCollection().BuildServiceProvider())); var httpContext = HttpContextHelpers.CreateContext(); // Act @@ -94,7 +97,8 @@ public async Task Invoke_SuccessAwaitedRelease_ReleaseCalled() (service, reader, context) => Task.FromResult(new TestMessage()), new Method(MethodType.Unary, "test", "test", _marshaller, _marshaller), HttpContextServerCallContextHelper.CreateMethodOptions(), - serviceActivator); + serviceActivator, + new InterceptorActivators(new ServiceCollection().BuildServiceProvider())); var httpContext = HttpContextHelpers.CreateContext(); // Act @@ -119,7 +123,8 @@ public async Task Invoke_AwaitedSuccess_ReleaseCalled() (service, reader, context) => methodTcs.Task, new Method(MethodType.Unary, "test", "test", _marshaller, _marshaller), HttpContextServerCallContextHelper.CreateMethodOptions(), - serviceActivator); + serviceActivator, + new InterceptorActivators(new ServiceCollection().BuildServiceProvider())); var httpContext = HttpContextHelpers.CreateContext(); // Act From 09c76ce9e9472d24e7c41b2215d0ce7fcfa727b5 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 24 Jun 2025 11:12:25 +0800 Subject: [PATCH 2/2] Tests --- .../GrpcServicesExtensionsTests.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/Grpc.AspNetCore.Server.Tests/GrpcServicesExtensionsTests.cs b/test/Grpc.AspNetCore.Server.Tests/GrpcServicesExtensionsTests.cs index c3b9ed73b..60bfd56a7 100644 --- a/test/Grpc.AspNetCore.Server.Tests/GrpcServicesExtensionsTests.cs +++ b/test/Grpc.AspNetCore.Server.Tests/GrpcServicesExtensionsTests.cs @@ -122,4 +122,56 @@ public void AddServiceOptions_MaxReceiveMessageSizeNull_NullOverrideGlobalOption // Assert Assert.AreEqual(null, options.MaxReceiveMessageSize); } + + [Test] + public void AddServiceOptions_ValuesSpecified_TrueWhenSet() + { + // Arrange + var services = new ServiceCollection(); + services + .AddGrpc() + .AddServiceOptions(o => + { + o.MaxReceiveMessageSize = null; + o.MaxSendMessageSize = null; + }); + + var serviceProvider = services.BuildServiceProvider(validateScopes: true); + + // Act + var options = serviceProvider.GetRequiredService>>().Value; + + // Assert + Assert.AreEqual(true, options.MaxReceiveMessageSizeSpecified); + Assert.AreEqual(true, options.MaxSendMessageSizeSpecified); + } + + [Test] + public void AddServiceOptions_ValuesNotSpecified_ResetToNull() + { + // Arrange + var services = new ServiceCollection(); + services + .AddGrpc() + .AddServiceOptions(o => + { + o.MaxReceiveMessageSize = 1; + o.MaxSendMessageSize = 1; + + o.MaxReceiveMessageSizeSpecified = false; + o.MaxSendMessageSizeSpecified = false; + }); + + var serviceProvider = services.BuildServiceProvider(validateScopes: true); + + // Act + var options = serviceProvider.GetRequiredService>>().Value; + + // Assert + Assert.AreEqual(false, options.MaxReceiveMessageSizeSpecified); + Assert.AreEqual(null, options.MaxReceiveMessageSize); + + Assert.AreEqual(false, options.MaxSendMessageSizeSpecified); + Assert.AreEqual(null, options.MaxSendMessageSize); + } }