diff --git a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/GrpcJsonTranscodingServiceExtensions.cs b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/GrpcJsonTranscodingServiceExtensions.cs index ceb7a2c0eb82..f9e5a7070d7c 100644 --- a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/GrpcJsonTranscodingServiceExtensions.cs +++ b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/GrpcJsonTranscodingServiceExtensions.cs @@ -4,6 +4,7 @@ using Grpc.AspNetCore.Server; using Grpc.AspNetCore.Server.Model; using Grpc.Shared; +using Grpc.Shared.Server; using Microsoft.AspNetCore.Grpc.JsonTranscoding; using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Binding; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -28,6 +29,7 @@ public static IGrpcServerBuilder AddJsonTranscoding(this IGrpcServerBuilder buil builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IServiceMethodProvider<>), typeof(JsonTranscodingServiceMethodProvider<>))); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, GrpcJsonTranscodingOptionsSetup>()); builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); return builder; } diff --git a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Binding/JsonTranscodingProviderServiceBinder.cs b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Binding/JsonTranscodingProviderServiceBinder.cs index ff55facbdd73..81325c33a82c 100644 --- a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Binding/JsonTranscodingProviderServiceBinder.cs +++ b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Binding/JsonTranscodingProviderServiceBinder.cs @@ -34,6 +34,7 @@ private delegate (RequestDelegate RequestDelegate, List Metadata) Create private readonly GrpcServiceOptions _serviceOptions; private readonly IGrpcServiceActivator _serviceActivator; private readonly GrpcJsonTranscodingOptions _jsonTranscodingOptions; + private readonly InterceptorActivators _interceptorActivators; private readonly ILoggerFactory _loggerFactory; private readonly ILogger _logger; @@ -45,7 +46,8 @@ internal JsonTranscodingProviderServiceBinder( GrpcServiceOptions serviceOptions, ILoggerFactory loggerFactory, IGrpcServiceActivator serviceActivator, - GrpcJsonTranscodingOptions jsonTranscodingOptions) + GrpcJsonTranscodingOptions jsonTranscodingOptions, + InterceptorActivators interceptorActivators) { _context = context; _invokerResolver = invokerResolver; @@ -54,6 +56,7 @@ internal JsonTranscodingProviderServiceBinder( _serviceOptions = serviceOptions; _serviceActivator = serviceActivator; _jsonTranscodingOptions = jsonTranscodingOptions; + _interceptorActivators = interceptorActivators; _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger>(); } @@ -162,7 +165,7 @@ private void ProcessHttpRule( httpRule, methodDescriptor); - var methodInvoker = new UnaryServerMethodInvoker(invoker, method, methodOptions, _serviceActivator); + var methodInvoker = new UnaryServerMethodInvoker(invoker, method, methodOptions, _serviceActivator, _interceptorActivators); var callHandler = new UnaryServerCallHandler( methodInvoker, _loggerFactory, @@ -189,7 +192,7 @@ private void ProcessHttpRule( httpRule, methodDescriptor); - var methodInvoker = new ServerStreamingServerMethodInvoker(invoker, method, methodOptions, _serviceActivator); + var methodInvoker = new ServerStreamingServerMethodInvoker(invoker, method, methodOptions, _serviceActivator, _interceptorActivators); var callHandler = new ServerStreamingServerCallHandler( methodInvoker, _loggerFactory, diff --git a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Binding/JsonTranscodingServiceMethodProvider.cs b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Binding/JsonTranscodingServiceMethodProvider.cs index 66e03b25c1e5..4d46fb164b34 100644 --- a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Binding/JsonTranscodingServiceMethodProvider.cs +++ b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Internal/Binding/JsonTranscodingServiceMethodProvider.cs @@ -20,6 +20,7 @@ internal sealed partial class JsonTranscodingServiceMethodProvider : I private readonly ILoggerFactory _loggerFactory; private readonly IGrpcServiceActivator _serviceActivator; private readonly DescriptorRegistry _serviceDescriptorRegistry; + private readonly InterceptorActivators _interceptorActivators; public JsonTranscodingServiceMethodProvider( ILoggerFactory loggerFactory, @@ -27,7 +28,8 @@ public JsonTranscodingServiceMethodProvider( IOptions> serviceOptions, IGrpcServiceActivator serviceActivator, IOptions jsonTranscodingOptions, - DescriptorRegistry serviceDescriptorRegistry) + DescriptorRegistry serviceDescriptorRegistry, + InterceptorActivators interceptorActivators) { _logger = loggerFactory.CreateLogger>(); _globalOptions = globalOptions.Value; @@ -36,6 +38,7 @@ public JsonTranscodingServiceMethodProvider( _loggerFactory = loggerFactory; _serviceActivator = serviceActivator; _serviceDescriptorRegistry = serviceDescriptorRegistry; + _interceptorActivators = interceptorActivators; } public void OnServiceMethodDiscovery(ServiceMethodProviderContext context) @@ -70,7 +73,8 @@ public void OnServiceMethodDiscovery(ServiceMethodProviderContext cont _serviceOptions, _loggerFactory, _serviceActivator, - _jsonTranscodingOptions); + _jsonTranscodingOptions, + _interceptorActivators); try { diff --git a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Microsoft.AspNetCore.Grpc.JsonTranscoding.csproj b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Microsoft.AspNetCore.Grpc.JsonTranscoding.csproj index cc8d435f5eeb..3e3332639fbf 100644 --- a/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Microsoft.AspNetCore.Grpc.JsonTranscoding.csproj +++ b/src/Grpc/JsonTranscoding/src/Microsoft.AspNetCore.Grpc.JsonTranscoding/Microsoft.AspNetCore.Grpc.JsonTranscoding.csproj @@ -1,4 +1,4 @@ - + HTTP API for gRPC ASP.NET Core gRPC RPC HTTP/2 REST @@ -18,6 +18,8 @@ + + diff --git a/src/Grpc/JsonTranscoding/src/Shared/Server/BindMethodFinder.cs b/src/Grpc/JsonTranscoding/src/Shared/Server/BindMethodFinder.cs index 4dbc6b8a470f..d48fdb90d61f 100644 --- a/src/Grpc/JsonTranscoding/src/Shared/Server/BindMethodFinder.cs +++ b/src/Grpc/JsonTranscoding/src/Shared/Server/BindMethodFinder.cs @@ -16,6 +16,7 @@ #endregion +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Grpc.Core; @@ -64,6 +65,8 @@ internal static class BindMethodFinder return null; } + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern", + Justification = "Fallback doesn't have BindServiceMethodAttribute so can't be verified.")] internal static MethodInfo? GetBindMethodFallback(Type serviceType) { // Search for the generated service base class diff --git a/src/Grpc/JsonTranscoding/src/Shared/Server/ClientStreamingServerMethodInvoker.cs b/src/Grpc/JsonTranscoding/src/Shared/Server/ClientStreamingServerMethodInvoker.cs index 72377dc3b861..8e95adb7c3c1 100644 --- a/src/Grpc/JsonTranscoding/src/Shared/Server/ClientStreamingServerMethodInvoker.cs +++ b/src/Grpc/JsonTranscoding/src/Shared/Server/ClientStreamingServerMethodInvoker.cs @@ -16,6 +16,7 @@ #endregion +using System.Diagnostics.CodeAnalysis; using Grpc.AspNetCore.Server; using Grpc.AspNetCore.Server.Model; using Grpc.Core; @@ -29,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 : ServerMethodInvokerBase +internal sealed class ClientStreamingServerMethodInvoker<[DynamicallyAccessedMembers(ServerDynamicAccessConstants.ServiceAccessibility)] TService, TRequest, TResponse> : ServerMethodInvokerBase where TRequest : class where TResponse : class where TService : class @@ -44,18 +45,20 @@ internal sealed class ClientStreamingServerMethodInvokerThe 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); } } @@ -65,7 +68,7 @@ private async Task ResolvedInterceptorInvoker(IAsyncStreamReader serviceHandle = default; try { - serviceHandle = ServiceActivator.Create(resolvedContext.GetHttpContext().RequestServices); + serviceHandle = CreateServiceHandle(resolvedContext); return await _invoker( serviceHandle.Instance, requestStream, @@ -95,7 +98,7 @@ public async Task Invoke(HttpContext httpContext, ServerCallContext s GrpcActivatorHandle serviceHandle = default; try { - serviceHandle = ServiceActivator.Create(httpContext.RequestServices); + serviceHandle = CreateServiceHandle(httpContext); return await _invoker( serviceHandle.Instance, requestStream, diff --git a/src/Grpc/JsonTranscoding/src/Shared/Server/DuplexStreamingServerMethodInvoker.cs b/src/Grpc/JsonTranscoding/src/Shared/Server/DuplexStreamingServerMethodInvoker.cs index 8a76507f9cce..4153d3fc6eea 100644 --- a/src/Grpc/JsonTranscoding/src/Shared/Server/DuplexStreamingServerMethodInvoker.cs +++ b/src/Grpc/JsonTranscoding/src/Shared/Server/DuplexStreamingServerMethodInvoker.cs @@ -16,6 +16,7 @@ #endregion +using System.Diagnostics.CodeAnalysis; using Grpc.AspNetCore.Server; using Grpc.AspNetCore.Server.Model; using Grpc.Core; @@ -29,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 : ServerMethodInvokerBase +internal sealed class DuplexStreamingServerMethodInvoker<[DynamicallyAccessedMembers(ServerDynamicAccessConstants.ServiceAccessibility)] TService, TRequest, TResponse> : ServerMethodInvokerBase where TRequest : class where TResponse : class where TService : class @@ -44,18 +45,20 @@ internal sealed class DuplexStreamingServerMethodInvokerThe 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); } } @@ -65,7 +68,7 @@ private async Task ResolvedInterceptorInvoker(IAsyncStreamReader reque GrpcActivatorHandle serviceHandle = default; try { - serviceHandle = ServiceActivator.Create(resolvedContext.GetHttpContext().RequestServices); + serviceHandle = CreateServiceHandle(resolvedContext); await _invoker( serviceHandle.Instance, requestStream, @@ -88,7 +91,7 @@ await _invoker( /// The . /// The reader. /// The writer. - /// A that represents the asynchronous method. + /// A that represents the asynchronous method. public async Task Invoke(HttpContext httpContext, ServerCallContext serverCallContext, IAsyncStreamReader requestStream, IServerStreamWriter responseStream) { if (_pipelineInvoker == null) @@ -96,7 +99,7 @@ public async Task Invoke(HttpContext httpContext, ServerCallContext serverCallCo GrpcActivatorHandle serviceHandle = default; try { - serviceHandle = ServiceActivator.Create(httpContext.RequestServices); + serviceHandle = CreateServiceHandle(httpContext); await _invoker( serviceHandle.Instance, requestStream, diff --git a/src/Grpc/JsonTranscoding/src/Shared/Server/InterceptorActivators.cs b/src/Grpc/JsonTranscoding/src/Shared/Server/InterceptorActivators.cs new file mode 100644 index 000000000000..b5e7ff82af98 --- /dev/null +++ b/src/Grpc/JsonTranscoding/src/Shared/Server/InterceptorActivators.cs @@ -0,0 +1,49 @@ +#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; + +internal sealed class InterceptorActivators +{ + private readonly ConcurrentDictionary _cachedActivators = new(); + private readonly IServiceProvider _serviceProvider; + + public InterceptorActivators(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + 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/Grpc/JsonTranscoding/src/Shared/Server/InterceptorPipelineBuilder.cs b/src/Grpc/JsonTranscoding/src/Shared/Server/InterceptorPipelineBuilder.cs index ccea9f1ea554..4fb186f6dced 100644 --- a/src/Grpc/JsonTranscoding/src/Shared/Server/InterceptorPipelineBuilder.cs +++ b/src/Grpc/JsonTranscoding/src/Shared/Server/InterceptorPipelineBuilder.cs @@ -16,11 +16,9 @@ #endregion -using System.Linq; using Grpc.AspNetCore.Server; using Grpc.Core; using Grpc.Core.Interceptors; -using Microsoft.Extensions.DependencyInjection; namespace Grpc.Shared.Server; @@ -28,24 +26,26 @@ internal sealed class InterceptorPipelineBuilder where TRequest : class where TResponse : class { - private readonly List _interceptors; + private readonly IReadOnlyList _interceptors; + private readonly InterceptorActivators _interceptorActivators; - public InterceptorPipelineBuilder(IReadOnlyList interceptors) + public InterceptorPipelineBuilder(IReadOnlyList interceptors, InterceptorActivators interceptorActivators) { - _interceptors = interceptors.Select(i => new InterceptorActivatorHandle(i)).ToList(); + _interceptors = interceptors; + _interceptorActivators = interceptorActivators; } public ClientStreamingServerMethod ClientStreamingPipeline(ClientStreamingServerMethod innerInvoker) { return BuildPipeline(innerInvoker, BuildInvoker); - static ClientStreamingServerMethod BuildInvoker(InterceptorActivatorHandle interceptorActivatorHandle, ClientStreamingServerMethod next) + static ClientStreamingServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, InterceptorActivators interceptorActivators, ClientStreamingServerMethod next) { return async (requestStream, context) => { var serviceProvider = context.GetHttpContext().RequestServices; - var interceptorActivator = interceptorActivatorHandle.GetActivator(serviceProvider); - var interceptorHandle = CreateInterceptor(interceptorActivatorHandle, interceptorActivator, serviceProvider); + var interceptorActivator = interceptorActivators.GetInterceptorActivator(interceptorRegistration.Type); + var interceptorHandle = CreateInterceptor(interceptorRegistration, interceptorActivator, serviceProvider); try { @@ -63,13 +63,13 @@ internal DuplexStreamingServerMethod DuplexStreamingPipelin { return BuildPipeline(innerInvoker, BuildInvoker); - static DuplexStreamingServerMethod BuildInvoker(InterceptorActivatorHandle interceptorActivatorHandle, DuplexStreamingServerMethod next) + static DuplexStreamingServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, InterceptorActivators interceptorActivators, DuplexStreamingServerMethod next) { return async (requestStream, responseStream, context) => { var serviceProvider = context.GetHttpContext().RequestServices; - var interceptorActivator = interceptorActivatorHandle.GetActivator(serviceProvider); - var interceptorHandle = CreateInterceptor(interceptorActivatorHandle, interceptorActivator, serviceProvider); + var interceptorActivator = interceptorActivators.GetInterceptorActivator(interceptorRegistration.Type); + var interceptorHandle = CreateInterceptor(interceptorRegistration, interceptorActivator, serviceProvider); try { @@ -87,13 +87,18 @@ internal ServerStreamingServerMethod ServerStreamingPipelin { return BuildPipeline(innerInvoker, BuildInvoker); - static ServerStreamingServerMethod BuildInvoker(InterceptorActivatorHandle interceptorActivatorHandle, ServerStreamingServerMethod next) + static ServerStreamingServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, InterceptorActivators interceptorActivators, ServerStreamingServerMethod next) { return async (request, responseStream, context) => { var serviceProvider = context.GetHttpContext().RequestServices; - var interceptorActivator = interceptorActivatorHandle.GetActivator(serviceProvider); - var interceptorHandle = CreateInterceptor(interceptorActivatorHandle, interceptorActivator, serviceProvider); + var interceptorActivator = interceptorActivators.GetInterceptorActivator(interceptorRegistration.Type); + var interceptorHandle = CreateInterceptor(interceptorRegistration, interceptorActivator, serviceProvider); + + if (interceptorHandle.Instance == null) + { + throw new InvalidOperationException($"Could not construct Interceptor instance for type {interceptorRegistration.Type.FullName}"); + } try { @@ -111,13 +116,13 @@ internal UnaryServerMethod UnaryPipeline(UnaryServerMethod< { return BuildPipeline(innerInvoker, BuildInvoker); - static UnaryServerMethod BuildInvoker(InterceptorActivatorHandle interceptorActivatorHandle, UnaryServerMethod next) + static UnaryServerMethod BuildInvoker(InterceptorRegistration interceptorRegistration, InterceptorActivators interceptorActivators, UnaryServerMethod next) { return async (request, context) => { var serviceProvider = context.GetHttpContext().RequestServices; - var interceptorActivator = interceptorActivatorHandle.GetActivator(serviceProvider); - var interceptorHandle = CreateInterceptor(interceptorActivatorHandle, interceptorActivator, serviceProvider); + var interceptorActivator = interceptorActivators.GetInterceptorActivator(interceptorRegistration.Type); + var interceptorHandle = CreateInterceptor(interceptorRegistration, interceptorActivator, serviceProvider); try { @@ -131,7 +136,7 @@ static UnaryServerMethod BuildInvoker(InterceptorActivatorH } } - 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; @@ -139,48 +144,21 @@ private T BuildPipeline(T innerInvoker, Func= 0; i--) { - resolvedInvoker = wrapInvoker(_interceptors[i], resolvedInvoker); + resolvedInvoker = wrapInvoker(_interceptors[i], _interceptorActivators, resolvedInvoker); } return resolvedInvoker; } - private static GrpcActivatorHandle CreateInterceptor( - InterceptorActivatorHandle interceptorActivatorHandle, - IGrpcInterceptorActivator interceptorActivator, - IServiceProvider serviceProvider) + private static GrpcActivatorHandle CreateInterceptor(InterceptorRegistration interceptorRegistration, IGrpcInterceptorActivator interceptorActivator, IServiceProvider serviceProvider) { - var interceptorHandle = interceptorActivator.Create(serviceProvider, interceptorActivatorHandle.Registration); + var interceptorHandle = interceptorActivator.Create(serviceProvider, interceptorRegistration); if (interceptorHandle.Instance == null) { - throw new InvalidOperationException($"Could not construct Interceptor instance for type {interceptorActivatorHandle.Registration.Type.FullName}"); + throw new InvalidOperationException($"Could not construct Interceptor instance for type {interceptorRegistration.Type.FullName}"); } return interceptorHandle; } - - private sealed class InterceptorActivatorHandle - { - public InterceptorRegistration Registration { get; } - - private IGrpcInterceptorActivator? _interceptorActivator; - - public InterceptorActivatorHandle(InterceptorRegistration interceptorRegistration) - { - Registration = interceptorRegistration; - } - - public IGrpcInterceptorActivator GetActivator(IServiceProvider serviceProvider) - { - // Not thread safe. Side effect is resolving the service twice. - if (_interceptorActivator == null) - { - var activatorType = typeof(IGrpcInterceptorActivator<>).MakeGenericType(Registration.Type); - _interceptorActivator = (IGrpcInterceptorActivator)serviceProvider.GetRequiredService(activatorType); - } - - return _interceptorActivator; - } - } } diff --git a/src/Grpc/JsonTranscoding/src/Shared/Server/MethodOptions.cs b/src/Grpc/JsonTranscoding/src/Shared/Server/MethodOptions.cs index 1fdf601659e7..58b83208dd36 100644 --- a/src/Grpc/JsonTranscoding/src/Shared/Server/MethodOptions.cs +++ b/src/Grpc/JsonTranscoding/src/Shared/Server/MethodOptions.cs @@ -19,6 +19,7 @@ using System.IO.Compression; using System.Linq; using Grpc.AspNetCore.Server; +using Grpc.AspNetCore.Server.Internal; using Grpc.Net.Compression; namespace Grpc.Shared.Server; @@ -70,6 +71,8 @@ internal sealed class MethodOptions // Fast check for whether the service has any interceptors internal bool HasInterceptors { get; } + internal bool SuppressCreatingService { get; } + private MethodOptions( Dictionary compressionProviders, InterceptorCollection interceptors, @@ -77,7 +80,8 @@ private MethodOptions( int? maxReceiveMessageSize, bool? enableDetailedErrors, string? responseCompressionAlgorithm, - CompressionLevel? responseCompressionLevel) + CompressionLevel? responseCompressionLevel, + bool suppressCreatingService) { CompressionProviders = compressionProviders; Interceptors = interceptors; @@ -87,6 +91,7 @@ private MethodOptions( EnableDetailedErrors = enableDetailedErrors; ResponseCompressionAlgorithm = responseCompressionAlgorithm; ResponseCompressionLevel = responseCompressionLevel; + SuppressCreatingService = suppressCreatingService; if (ResponseCompressionAlgorithm != null) { @@ -115,8 +120,9 @@ public static MethodOptions Create(IEnumerable serviceOption bool? enableDetailedErrors = null; string? responseCompressionAlgorithm = null; CompressionLevel? responseCompressionLevel = null; + bool? suppressCreatingService = null; - foreach (var options in Enumerable.Reverse(serviceOptions)) + foreach (var options in serviceOptions.Reverse()) { AddCompressionProviders(resolvedCompressionProviders, options.CompressionProviders); tempInterceptors.InsertRange(0, options.Interceptors); @@ -128,9 +134,9 @@ public static MethodOptions Create(IEnumerable serviceOption } var interceptors = new InterceptorCollection(); - foreach (var interceptor in tempInterceptors) + foreach (var registration in tempInterceptors) { - interceptors.Add(interceptor); + interceptors.Add(registration); } return new MethodOptions @@ -141,7 +147,8 @@ public static MethodOptions Create(IEnumerable serviceOption maxReceiveMessageSize: maxReceiveMessageSize, enableDetailedErrors: enableDetailedErrors, responseCompressionAlgorithm: responseCompressionAlgorithm, - responseCompressionLevel: responseCompressionLevel + responseCompressionLevel: responseCompressionLevel, + suppressCreatingService: suppressCreatingService ?? false ); } @@ -151,10 +158,7 @@ private static void AddCompressionProviders(DictionaryService type for this method. /// Request message type for this method. /// Response message type for this method. -internal abstract class ServerMethodInvokerBase +internal abstract class ServerMethodInvokerBase<[DynamicallyAccessedMembers(ServerDynamicAccessConstants.ServiceAccessibility)] TService, TRequest, TResponse> where TRequest : class where TResponse : class where TService : class @@ -62,4 +64,19 @@ private protected ServerMethodInvokerBase( Options = options; ServiceActivator = serviceActivator; } + + protected GrpcActivatorHandle CreateServiceHandle(ServerCallContext context) + { + return CreateServiceHandle(context.GetHttpContext()); + } + + protected GrpcActivatorHandle CreateServiceHandle(HttpContext httpContext) + { + if (!Options.SuppressCreatingService) + { + return ServiceActivator.Create(httpContext.RequestServices); + } + + return default; + } } diff --git a/src/Grpc/JsonTranscoding/src/Shared/Server/ServerStreamingServerMethodInvoker.cs b/src/Grpc/JsonTranscoding/src/Shared/Server/ServerStreamingServerMethodInvoker.cs index 5b73c4b22309..a29aabfb6e80 100644 --- a/src/Grpc/JsonTranscoding/src/Shared/Server/ServerStreamingServerMethodInvoker.cs +++ b/src/Grpc/JsonTranscoding/src/Shared/Server/ServerStreamingServerMethodInvoker.cs @@ -16,6 +16,7 @@ #endregion +using System.Diagnostics.CodeAnalysis; using Grpc.AspNetCore.Server; using Grpc.AspNetCore.Server.Model; using Grpc.Core; @@ -29,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 : ServerMethodInvokerBase +internal sealed class ServerStreamingServerMethodInvoker<[DynamicallyAccessedMembers(ServerDynamicAccessConstants.ServiceAccessibility)] TService, TRequest, TResponse> : ServerMethodInvokerBase where TRequest : class where TResponse : class where TService : class @@ -44,18 +45,20 @@ internal sealed class ServerStreamingServerMethodInvokerThe 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); } } @@ -65,7 +68,7 @@ private async Task ResolvedInterceptorInvoker(TRequest request, IServerStreamWri GrpcActivatorHandle serviceHandle = default; try { - serviceHandle = ServiceActivator.Create(resolvedContext.GetHttpContext().RequestServices); + serviceHandle = CreateServiceHandle(resolvedContext); await _invoker( serviceHandle.Instance, request, @@ -96,7 +99,7 @@ public async Task Invoke(HttpContext httpContext, ServerCallContext serverCallCo GrpcActivatorHandle serviceHandle = default; try { - serviceHandle = ServiceActivator.Create(httpContext.RequestServices); + serviceHandle = CreateServiceHandle(httpContext); await _invoker( serviceHandle.Instance, request, diff --git a/src/Grpc/JsonTranscoding/src/Shared/Server/UnaryServerMethodInvoker.cs b/src/Grpc/JsonTranscoding/src/Shared/Server/UnaryServerMethodInvoker.cs index b29f08c94f5c..09398689ccb0 100644 --- a/src/Grpc/JsonTranscoding/src/Shared/Server/UnaryServerMethodInvoker.cs +++ b/src/Grpc/JsonTranscoding/src/Shared/Server/UnaryServerMethodInvoker.cs @@ -16,6 +16,8 @@ #endregion +using System.Diagnostics.CodeAnalysis; +using System.Runtime.ExceptionServices; using Grpc.AspNetCore.Server; using Grpc.AspNetCore.Server.Model; using Grpc.Core; @@ -29,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 : ServerMethodInvokerBase +internal sealed class UnaryServerMethodInvoker<[DynamicallyAccessedMembers(ServerDynamicAccessConstants.ServiceAccessibility)] TService, TRequest, TResponse> : ServerMethodInvokerBase where TRequest : class where TResponse : class where TService : class @@ -44,18 +46,20 @@ internal sealed class UnaryServerMethodInvoker : /// 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); } } @@ -65,7 +69,7 @@ private async Task ResolvedInterceptorInvoker(TRequest resolvedReques GrpcActivatorHandle serviceHandle = default; try { - serviceHandle = ServiceActivator.Create(resolvedContext.GetHttpContext().RequestServices); + serviceHandle = CreateServiceHandle(resolvedContext); return await _invoker(serviceHandle.Instance, resolvedRequest, resolvedContext); } finally @@ -85,32 +89,88 @@ private async Task ResolvedInterceptorInvoker(TRequest resolvedReques /// The message. /// A that represents the asynchronous method. The /// property returns the message. - public async Task Invoke(HttpContext httpContext, ServerCallContext serverCallContext, TRequest request) + public Task Invoke(HttpContext httpContext, ServerCallContext serverCallContext, TRequest request) { if (_pipelineInvoker == null) { GrpcActivatorHandle serviceHandle = default; + Task? invokerTask = null; try { - serviceHandle = ServiceActivator.Create(httpContext.RequestServices); - return await _invoker( + serviceHandle = CreateServiceHandle(httpContext); + invokerTask = _invoker( serviceHandle.Instance, request, serverCallContext); } - finally + catch (Exception ex) { + // Invoker calls user code. User code may throw an exception instead + // of a faulted task. We need to catch the exception, ensure cleanup + // runs and convert exception into a faulted task. if (serviceHandle.Instance != null) { - await ServiceActivator.ReleaseAsync(serviceHandle); + var releaseTask = ServiceActivator.ReleaseAsync(serviceHandle); + if (!releaseTask.IsCompletedSuccessfully) + { + // Capture the current exception state so we can rethrow it after awaiting + // with the same stack trace. + var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(ex); + return AwaitServiceReleaseAndThrow(releaseTask, exceptionDispatchInfo); + } } + + return Task.FromException(ex); + } + + if (invokerTask.IsCompletedSuccessfully && serviceHandle.Instance != null) + { + var releaseTask = ServiceActivator.ReleaseAsync(serviceHandle); + if (!releaseTask.IsCompletedSuccessfully) + { + return AwaitServiceReleaseAndReturn(invokerTask.Result, serviceHandle); + } + + return invokerTask; } + + return AwaitInvoker(invokerTask, serviceHandle); } else { - return await _pipelineInvoker( + return _pipelineInvoker( request, serverCallContext); } } + + private async Task AwaitInvoker(Task invokerTask, GrpcActivatorHandle serviceHandle) + { + try + { + return await invokerTask; + } + finally + { + if (serviceHandle.Instance != null) + { + await ServiceActivator.ReleaseAsync(serviceHandle); + } + } + } + + private static async Task AwaitServiceReleaseAndThrow(ValueTask releaseTask, ExceptionDispatchInfo ex) + { + await releaseTask; + ex.Throw(); + + // Should never reach here + return null; + } + + private async Task AwaitServiceReleaseAndReturn(TResponse invokerResult, GrpcActivatorHandle serviceHandle) + { + await ServiceActivator.ReleaseAsync(serviceHandle); + return invokerResult; + } } diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests/Infrastructure/DynamicGrpcServiceRegistry.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests/Infrastructure/DynamicGrpcServiceRegistry.cs index dc86e1303a5e..200977c57185 100644 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests/Infrastructure/DynamicGrpcServiceRegistry.cs +++ b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests/Infrastructure/DynamicGrpcServiceRegistry.cs @@ -8,6 +8,7 @@ using Grpc.AspNetCore.Server.Model; using Grpc.Core; using Grpc.Shared; +using Grpc.Shared.Server; using IntegrationTestsWebsite.Infrastructure; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Binding; @@ -151,7 +152,8 @@ private JsonTranscodingProviderServiceBinder CreateJsonTranscodi _serviceProvider.GetRequiredService>>().Value, _serviceProvider.GetRequiredService(), _serviceProvider.GetRequiredService>(), - JsonTranscodingOptions); + JsonTranscodingOptions, + new InterceptorActivators(_serviceProvider)); return binder; } diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/Infrastructure/TestHelpers.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/Infrastructure/TestHelpers.cs index 656420605d4e..7bdbfe24b6be 100644 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/Infrastructure/TestHelpers.cs +++ b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/Infrastructure/TestHelpers.cs @@ -19,10 +19,7 @@ internal static class TestHelpers { public static DefaultHttpContext CreateHttpContext(CancellationToken cancellationToken = default, Stream? bodyStream = null) { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(typeof(IGrpcInterceptorActivator<>), typeof(TestInterceptorActivator<>)); - var serviceProvider = serviceCollection.BuildServiceProvider(); + var serviceProvider = CreateServiceProvider(); var httpContext = new DefaultHttpContext(); httpContext.Request.Host = new HostString("localhost"); httpContext.RequestServices = serviceProvider; @@ -32,6 +29,15 @@ public static DefaultHttpContext CreateHttpContext(CancellationToken cancellatio return httpContext; } + public static IServiceProvider CreateServiceProvider() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(typeof(IGrpcInterceptorActivator<>), typeof(TestInterceptorActivator<>)); + var serviceProvider = serviceCollection.BuildServiceProvider(); + return serviceProvider; + } + internal static MessageDescriptor GetMessageDescriptor(Type typeToConvert) { var property = typeToConvert.GetProperty("Descriptor", BindingFlags.Static | BindingFlags.Public, binder: null, typeof(MessageDescriptor), Type.EmptyTypes, modifiers: null); diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ServerStreamingServerCallHandlerTests.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ServerStreamingServerCallHandlerTests.cs index f74675a6b9a4..ec1f3666d737 100644 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ServerStreamingServerCallHandlerTests.cs +++ b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/ServerStreamingServerCallHandlerTests.cs @@ -20,6 +20,7 @@ using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json; using Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.Infrastructure; using Microsoft.AspNetCore.InternalTesting; +using Microsoft.Extensions.DependencyInjection; using Transcoding; using Xunit.Abstractions; using MethodOptions = Grpc.Shared.Server.MethodOptions; @@ -339,7 +340,8 @@ private ServerStreamingServerCallHandler()); + new TestGrpcServiceActivator(), + new InterceptorActivators(TestHelpers.CreateServiceProvider())); var jsonSettings = jsonTranscodingOptions?.JsonSettings ?? new GrpcJsonSettings() { WriteIndented = false }; diff --git a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/UnaryServerCallHandlerTests.cs b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/UnaryServerCallHandlerTests.cs index 1d6ad6ba4c38..762483117ba4 100644 --- a/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/UnaryServerCallHandlerTests.cs +++ b/src/Grpc/JsonTranscoding/test/Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests/UnaryServerCallHandlerTests.cs @@ -23,6 +23,7 @@ using Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.Infrastructure; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.InternalTesting; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Primitives; using Transcoding; using Xunit.Abstractions; @@ -1842,7 +1843,8 @@ private UnaryServerCallHandler()); + new TestGrpcServiceActivator(), + new InterceptorActivators(TestHelpers.CreateServiceProvider())); var descriptorRegistry = new DescriptorRegistry(); descriptorRegistry.RegisterFileDescriptor(TestHelpers.GetMessageDescriptor(typeof(TRequest)).File);