Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,18 @@ namespace Grpc.AspNetCore.ClientFactory
{
/// <summary>
/// Interceptor that will set the current request's cancellation token and deadline onto CallOptions.
/// This interceptor is registered with a singleton lifetime. The interceptor gets the request from
/// IHttpContextAccessor, which is also a singleton. IHttpContextAccessor uses an async local value.
/// The interceptor gets the request from IHttpContextAccessor, which is a singleton.
/// IHttpContextAccessor uses an async local value.
/// </summary>
internal class ContextPropagationInterceptor : Interceptor
{
private readonly GrpcContextPropagationOptions _options;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger _logger;

public ContextPropagationInterceptor(IOptions<GrpcContextPropagationOptions> options, IHttpContextAccessor httpContextAccessor, ILogger<ContextPropagationInterceptor> logger)
public ContextPropagationInterceptor(GrpcContextPropagationOptions options, IHttpContextAccessor httpContextAccessor, ILogger<ContextPropagationInterceptor> logger)
{
_options = options.Value;
_options = options;
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
using Grpc.AspNetCore.ClientFactory;
using Grpc.Core;
using Grpc.Net.ClientFactory;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.DependencyInjection
Expand All @@ -42,16 +43,7 @@ public static IHttpClientBuilder EnableCallContextPropagation(this IHttpClientBu
throw new ArgumentNullException(nameof(builder));
}

ValidateGrpcClient(builder);

builder.Services.TryAddSingleton<ContextPropagationInterceptor>();
builder.Services.AddHttpContextAccessor();
builder.Services.Configure<GrpcClientFactoryOptions>(builder.Name, options =>
{
options.InterceptorRegistrations.Add(new InterceptorRegistration(
InterceptorScope.Channel,
s => s.GetRequiredService<ContextPropagationInterceptor>()));
});
EnableCallContextPropagationCore(builder, new GrpcContextPropagationOptions());

return builder;
}
Expand All @@ -70,8 +62,34 @@ public static IHttpClientBuilder EnableCallContextPropagation(this IHttpClientBu
throw new ArgumentNullException(nameof(builder));
}

builder.Services.Configure(configureOptions);
return builder.EnableCallContextPropagation();
if (configureOptions == null)
{
throw new ArgumentNullException(nameof(configureOptions));
}

var options = new GrpcContextPropagationOptions();
configureOptions(options);
EnableCallContextPropagationCore(builder, options);

return builder;
}

private static void EnableCallContextPropagationCore(IHttpClientBuilder builder, GrpcContextPropagationOptions options)
{
ValidateGrpcClient(builder);

builder.Services.AddHttpContextAccessor();
builder.Services.Configure<GrpcClientFactoryOptions>(builder.Name, o =>
{
o.InterceptorRegistrations.Add(new InterceptorRegistration(
InterceptorScope.Channel,
s =>
{
var accessor = s.GetRequiredService<IHttpContextAccessor>();
var logger = s.GetRequiredService<ILogger<ContextPropagationInterceptor>>();
return new ContextPropagationInterceptor(options, accessor, logger);
}));
});
}

private static void ValidateGrpcClient(IHttpClientBuilder builder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,54 @@ public async Task CreateClient_NoHttpContextIgnoreError_Success()
Assert.AreEqual("Unable to propagate server context values to the call. Can't find the current HttpContext.", log.Message);
}

[Test]
public async Task CreateClient_MultipleConfiguration_ConfigurationAppliedPerClient()
{
// Arrange
var testSink = new TestSink();
var testProvider = new TestLoggerProvider(testSink);

var baseAddress = new Uri("http://localhost");

var services = new ServiceCollection();
services.AddLogging(o => o.AddProvider(testProvider).SetMinimumLevel(LogLevel.Debug));
services.AddOptions();
services.AddSingleton(CreateHttpContextAccessor(null));
services
.AddGrpcClient<Greeter.GreeterClient>(o =>
{
o.Address = baseAddress;
})
.EnableCallContextPropagation(o => o.SuppressContextNotFoundErrors = true)
.ConfigurePrimaryHttpMessageHandler(() => ClientTestHelpers.CreateTestMessageHandler(new HelloReply()));
services
.AddGrpcClient<SecondGreeter.SecondGreeterClient>(o =>
{
o.Address = baseAddress;
})
.EnableCallContextPropagation()
.ConfigurePrimaryHttpMessageHandler(() => ClientTestHelpers.CreateTestMessageHandler(new HelloReply()));

var serviceProvider = services.BuildServiceProvider(validateScopes: true);

var clientFactory = CreateGrpcClientFactory(serviceProvider);
var client1 = clientFactory.CreateClient<Greeter.GreeterClient>(nameof(Greeter.GreeterClient));
var client2 = clientFactory.CreateClient<SecondGreeter.SecondGreeterClient>(nameof(SecondGreeter.SecondGreeterClient));

// Act 1
await client1.SayHelloAsync(new HelloRequest(), new CallOptions()).ResponseAsync.DefaultTimeout();

// Assert 1
var log = testSink.Writes.Single(w => w.EventId.Name == "PropagateServerCallContextFailure");
Assert.AreEqual("Unable to propagate server context values to the call. Can't find the current HttpContext.", log.Message);

// Act 2
var ex = await ExceptionAssert.ThrowsAsync<InvalidOperationException>(() => client2.SayHelloAsync(new HelloRequest(), new CallOptions()).ResponseAsync).DefaultTimeout();

// Assert 2
Assert.AreEqual("Unable to propagate server context values to the call. Can't find the current HttpContext.", ex.Message);
}

[Test]
public async Task CreateClient_NoServerCallContextOnHttpContext_ThrowError()
{
Expand Down