diff --git a/src/Dapr.Common/DaprGenericClientBuilder.cs b/src/Dapr.Common/DaprGenericClientBuilder.cs
index 3e29a2eff..bbe247ac6 100644
--- a/src/Dapr.Common/DaprGenericClientBuilder.cs
+++ b/src/Dapr.Common/DaprGenericClientBuilder.cs
@@ -45,17 +45,17 @@ protected DaprGenericClientBuilder(IConfiguration? configuration = null)
///
/// Property exposed for testing purposes.
///
- internal string GrpcEndpoint { get; private set; }
+ protected internal string GrpcEndpoint { get; private set; }
///
/// Property exposed for testing purposes.
///
- internal string HttpEndpoint { get; private set; }
+ protected internal string HttpEndpoint { get; private set; }
///
/// Property exposed for testing purposes.
///
- internal Func? HttpClientFactory { get; set; }
+ protected internal Func? HttpClientFactory { get; set; }
///
/// Property exposed for testing purposes.
@@ -65,7 +65,7 @@ protected DaprGenericClientBuilder(IConfiguration? configuration = null)
///
/// Property exposed for testing purposes.
///
- internal GrpcChannelOptions GrpcChannelOptions { get; private set; }
+ protected internal GrpcChannelOptions GrpcChannelOptions { get; private set; }
///
/// Property exposed for testing purposes.
@@ -75,7 +75,7 @@ protected DaprGenericClientBuilder(IConfiguration? configuration = null)
///
/// Property exposed for testing purposes.
///
- internal TimeSpan Timeout { get; private set; }
+ protected internal TimeSpan Timeout { get; private set; }
///
/// Overrides the HTTP endpoint used by the Dapr client for communicating with the Dapr runtime.
@@ -185,8 +185,10 @@ public DaprGenericClientBuilder UseTimeout(TimeSpan timeout)
/// runtime gRPC client used by the consuming package.
///
/// The assembly the dependencies are being built for.
+ ///
///
- protected internal (GrpcChannel channel, HttpClient httpClient, Uri httpEndpoint, string daprApiToken) BuildDaprClientDependencies(Assembly assembly)
+ protected internal (GrpcChannel channel, HttpClient httpClient, Uri httpEndpoint, string daprApiToken)
+ BuildDaprClientDependencies(Assembly assembly, HttpClient? providedHttpClient = null)
{
var grpcEndpoint = new Uri(this.GrpcEndpoint);
if (grpcEndpoint.Scheme != "http" && grpcEndpoint.Scheme != "https")
@@ -205,10 +207,13 @@ protected internal (GrpcChannel channel, HttpClient httpClient, Uri httpEndpoint
{
throw new InvalidOperationException("The HTTP endpoint must use http or https.");
}
-
- //Configure the HTTP client
- var httpClient = ConfigureHttpClient(assembly);
- this.GrpcChannelOptions.HttpClient = httpClient;
+
+ // If provided with an HttpClient, use it directory - this supports the one registered in DI otherwise
+ var httpClient = providedHttpClient ?? ConfigureHttpClient(assembly);
+
+ // Apply the current GprcChannelOptions
+ var options = this.GrpcChannelOptions;
+ options.HttpClient = httpClient;
var channel = GrpcChannel.ForAddress(this.GrpcEndpoint, this.GrpcChannelOptions);
return (channel, httpClient, httpEndpoint, this.DaprApiToken);
diff --git a/src/Dapr.Common/DaprHttpClientOptions.cs b/src/Dapr.Common/DaprHttpClientOptions.cs
new file mode 100644
index 000000000..92face4d4
--- /dev/null
+++ b/src/Dapr.Common/DaprHttpClientOptions.cs
@@ -0,0 +1,56 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr 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.
+// ------------------------------------------------------------------------
+
+using System.Text.Json;
+using Grpc.Net.Client;
+
+namespace Dapr.Common;
+
+///
+/// Provides for a collection of options used to configure the Dapr instance(s).
+///
+public class DaprHttpClientOptions
+{
+ ///
+ /// Gets or sets the HTTP endpoint used by the Dapr client.
+ ///
+ public string? HttpEndpoint { get; set; }
+
+ ///
+ /// Gets or sets the gRPC endpoint used by the Dapr client.
+ ///
+ public string? GrpcEndpoint { get; set; }
+
+ ///
+ /// Gets or sets the JSON serialization options.
+ ///
+ public JsonSerializerOptions? JsonSerializerOptions { get; set; }
+
+ ///
+ /// Gets or sets the gRPC channel options.
+ ///
+ public GrpcChannelOptions? GrpcChannelOptions { get; set; } = new GrpcChannelOptions
+ {
+ ThrowOperationCanceledOnCancellation = true
+ };
+
+ ///
+ /// Gets or sets the API token used for Dapr authentication.
+ ///
+ public string? DaprApiToken { get; set; }
+
+ ///
+ /// Gets or sets the timeout for HTTP requests.
+ ///
+ public TimeSpan Timeout { get; set; } = TimeSpan.Zero;
+}
diff --git a/src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs b/src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs
index 1070133c2..7fb097d92 100644
--- a/src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs
+++ b/src/Dapr.Common/Extensions/DaprClientBuilderExtensions.cs
@@ -14,6 +14,7 @@
using System.Reflection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1.Dapr;
namespace Dapr.Common.Extensions;
@@ -59,20 +60,70 @@ internal static TServiceBuilder AddDaprClient(provider =>
{
var configuration = provider.GetService();
+ var options = provider.GetRequiredService>().Value;
+ var httpClientFactory = provider.GetRequiredService();
+
+ // Create the builder with the configuration to ensure it has access to all configuration values
+ // for defaults
var builder = (TClientBuilder)Activator.CreateInstance(typeof(TClientBuilder), configuration)!;
-
- builder.UseDaprApiToken(DaprDefaults.GetDefaultDaprApiToken(configuration));
- configure?.Invoke(provider, builder);
- var (channel, httpClient, _, daprApiToken) =
- builder.BuildDaprClientDependencies(Assembly.GetExecutingAssembly());
- var daprClient = new Autogenerated.DaprClient(channel);
- return (TClient)Activator.CreateInstance(typeof(TConcreteClient), daprClient, httpClient, daprApiToken)!;
+
+ // Apply options from DI, but only if they're explicitly set
+ // Otherwise, let the builder use its defaults from DaprDefaults
+ if (!string.IsNullOrEmpty(options.HttpEndpoint))
+ {
+ builder.UseHttpEndpoint(options.HttpEndpoint);
+ }
+
+ if (!string.IsNullOrEmpty(options.GrpcEndpoint))
+ {
+ builder.UseGrpcEndpoint(options.GrpcEndpoint);
+ }
+
+ if (options.JsonSerializerOptions != null)
+ {
+ builder.UseJsonSerializationOptions(options.JsonSerializerOptions);
+ }
+
+ if (options.GrpcChannelOptions is not null)
+ {
+ builder.UseGrpcChannelOptions(options.GrpcChannelOptions);
+ }
+
+ if (!string.IsNullOrEmpty(options.DaprApiToken))
+ {
+ builder.UseDaprApiToken(options.DaprApiToken);
+ }
+ else
+ {
+ //Use DaprDefaults to get the token if not set in options
+ builder.UseDaprApiToken(DaprDefaults.GetDefaultDaprApiToken(configuration));
+ }
+
+ if (options.Timeout > TimeSpan.Zero)
+ {
+ builder.UseTimeout(options.Timeout);
+ }
+
+ // Get an HttpClient from the factory
+ var httpClient = httpClientFactory.CreateClient(DaprServiceCollectionExtensions.DaprHttpClientName);
+
+ // Allow additional configuration
+ configure?.Invoke(provider, builder);
+
+ // Build dependencies using our pre-configured HttpClient
+ var (channel, _, _, _) = builder.BuildDaprClientDependencies(Assembly.GetExecutingAssembly(), httpClient);
+
+ //Create the Dapr client
+ var daprClient = new Autogenerated.DaprClient(channel);
+ return (TClient)Activator.CreateInstance(typeof(TConcreteClient), daprClient, httpClient, builder.DaprApiToken)!;
});
services.Add(new ServiceDescriptor(typeof(TClient), registration, lifetime));
diff --git a/src/Dapr.Common/Extensions/DaprServiceCollectionExtensions.cs b/src/Dapr.Common/Extensions/DaprServiceCollectionExtensions.cs
new file mode 100644
index 000000000..9ef5ea716
--- /dev/null
+++ b/src/Dapr.Common/Extensions/DaprServiceCollectionExtensions.cs
@@ -0,0 +1,94 @@
+// ------------------------------------------------------------------------
+// Copyright 2025 The Dapr 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.
+// ------------------------------------------------------------------------
+
+using System.Reflection;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+
+namespace Dapr.Common.Extensions;
+
+///
+/// Provides extension method implementations against an .
+///
+public static class DaprServiceCollectionExtensions
+{
+ ///
+ /// The name of the Dapr HTTP client.
+ ///
+ public const string DaprHttpClientName = "DaprClient";
+
+ ///
+ /// Adds and configures a Dapr HTTP client to the service collection.
+ ///
+ public static IServiceCollection AddDaprHttpClient(
+ this IServiceCollection services,
+ Action? configureOptions = null)
+ {
+ //Register the options with defaults from DaprDefaults
+ services.AddOptions()
+ .Configure((options, configuration) =>
+ {
+ //Only set these values from DaprDefaults if they haven't been explicitly configured already
+ options.HttpEndpoint ??= DaprDefaults.GetDefaultHttpEndpoint();
+ options.GrpcEndpoint ??= DaprDefaults.GetDefaultGrpcEndpoint();
+ options.DaprApiToken ??= DaprDefaults.GetDefaultDaprApiToken(configuration);
+ });
+
+ if (configureOptions != null)
+ {
+ services.Configure(configureOptions);
+ }
+
+ // Add the HttpClient with configuration from the options and DaprDefaults
+ services.AddHttpClient(name: DaprHttpClientName, (sp, client) =>
+ {
+ var options = sp.GetRequiredService>().Value;
+ var configuration = sp.GetRequiredService();
+
+ //Configure the timeout
+ if (options.Timeout > TimeSpan.Zero)
+ {
+ client.Timeout = options.Timeout;
+ }
+
+ //Add user agent
+ var userAgent = DaprClientUtilities.GetUserAgent(Assembly.GetExecutingAssembly());
+ client.DefaultRequestHeaders.Add("User-Agent", userAgent.ToString());
+
+ //Add API token if needed - use options first, then fall back to DaprDefaults
+ var apiToken = options.DaprApiToken ?? DaprDefaults.GetDefaultDaprApiToken(configuration);
+ var apiTokenHeader = DaprClientUtilities.GetDaprApiTokenHeader(apiToken);
+ if (apiTokenHeader is not null)
+ {
+ client.DefaultRequestHeaders.Add(apiTokenHeader.Value.Key, apiTokenHeader.Value.Value);
+ }
+ });
+
+ return services;
+ }
+
+ ///
+ /// Extension method to use a configured from an
+ /// in a .
+ ///
+ public static TClientBuilder UseDaprHttpClientFactory(
+ this TClientBuilder builder,
+ IHttpClientFactory httpClientFactory,
+ IOptions options)
+ where TClientBuilder : DaprGenericClientBuilder
+ {
+ builder.UseHttpClientFactory(() => httpClientFactory.CreateClient(DaprHttpClientName));
+ return builder;
+ }
+}