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()
{