diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index 240f229f1..f91d277c2 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -144,6 +144,50 @@ public static CallInvoker CreateInvocationInvoker(string appId, string? daprEndp return channel.Intercept(new InvocationInterceptor(appId, daprApiToken ?? DaprDefaults.GetDefaultDaprApiToken(null))); } + /// + /// + /// Creates an that can be used to perform locally defined gRPC calls + /// using the Dapr sidecar as a proxy. + /// + /// + /// The created is used to intercept a with an + /// . The interceptor inserts the and, if present, + /// the into the request's metadata. + /// + /// + /// + /// The appId that is targetted by Dapr for gRPC invocations. + /// + /// + /// The options to use when creating the . + /// + /// + /// Optional gRPC endpoint for calling Dapr, defaults to . + /// + /// + /// Optional token to be attached to all requests, defaults to . + /// + /// An to be used for proxied gRPC calls through Dapr. + /// + /// + /// As the will remain constant, a single instance of the + /// can be used throughout the lifetime of the application. + /// + /// + public static CallInvoker CreateInvocationInvoker( + string appId, + GrpcChannelOptions grpcChannelOptions, + string? daprEndpoint = null, + string? daprApiToken = null) + { + ArgumentNullException.ThrowIfNull(grpcChannelOptions); + + var channel = GrpcChannel.ForAddress( + daprEndpoint ?? DaprDefaults.GetDefaultGrpcEndpoint(), + grpcChannelOptions); + return channel.Intercept(new InvocationInterceptor(appId, daprApiToken ?? DaprDefaults.GetDefaultDaprApiToken(null))); + } + internal static KeyValuePair? GetDaprApiTokenHeader(string apiToken) { return string.IsNullOrWhiteSpace(apiToken) diff --git a/test/Dapr.Client.Test/DaprClientTest.cs b/test/Dapr.Client.Test/DaprClientTest.cs index b825375b8..4a65fefe6 100644 --- a/test/Dapr.Client.Test/DaprClientTest.cs +++ b/test/Dapr.Client.Test/DaprClientTest.cs @@ -12,7 +12,12 @@ // ------------------------------------------------------------------------ using System; +using System.Linq; +using System.Net.Http; using System.Threading.Tasks; +using Dapr; +using Grpc.Core; +using Grpc.Net.Client; using Xunit; namespace Dapr.Client; @@ -68,6 +73,36 @@ public void CreateInvokeHttpClient_InvalidDaprEndpoint_InvalidScheme() Assert.Contains("The URI scheme of the Dapr endpoint must be http or https.", ex.Message); } + [Fact] + public async Task CreateInvocationInvoker_UsesGrpcChannelOptions() + { + await using var testClient = TestClient.CreateForMessageHandler(); + using var httpClient = new HttpClient(testClient.InnerClient); + var invoker = DaprClient.CreateInvocationInvoker( + appId: "bank", + grpcChannelOptions: new GrpcChannelOptions { HttpClient = httpClient }, + daprEndpoint: "http://localhost"); + var method = new Method( + MethodType.Unary, + "test.Service", + "TestMethod", + Marshallers.StringMarshaller, + Marshallers.StringMarshaller); + + var request = await testClient.CaptureGrpcRequestAsync( + _ => invoker.AsyncUnaryCall(method, null, new CallOptions(), "request").ResponseAsync); + + Assert.Equal("bank", request.Request.Headers.GetValues("dapr-app-id").Single()); + request.Dismiss(); + } + + [Fact] + public void CreateInvocationInvoker_WithNullGrpcChannelOptions_Throws() + { + Assert.Throws( + () => DaprClient.CreateInvocationInvoker(grpcChannelOptions: null!, appId: "bank")); + } + [Fact] public void GetDaprApiTokenHeader_ApiTokenSet_SetsApiTokenHeader() {