diff --git a/all.sln b/all.sln
index ae51012e0..4464ddb03 100644
--- a/all.sln
+++ b/all.sln
@@ -169,6 +169,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Actors.Analyzers.Test"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Analyzers.Common", "test\Dapr.Analyzers.Common\Dapr.Analyzers.Common.csproj", "{7E23E229-6823-4D84-AF3A-AE14CEAEF52A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Bindings", "src\Dapr.Bindings\Dapr.Bindings.csproj", "{ACE88DA3-AFDC-478D-B3D9-B06460C64D2F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Bindings.Test", "test\Dapr.Bindings.Test\Dapr.Bindings.Test.csproj", "{A8A37DBD-8EB1-4346-9A0D-235403564355}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -445,6 +449,14 @@ Global
{7E23E229-6823-4D84-AF3A-AE14CEAEF52A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E23E229-6823-4D84-AF3A-AE14CEAEF52A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E23E229-6823-4D84-AF3A-AE14CEAEF52A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ACE88DA3-AFDC-478D-B3D9-B06460C64D2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ACE88DA3-AFDC-478D-B3D9-B06460C64D2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ACE88DA3-AFDC-478D-B3D9-B06460C64D2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ACE88DA3-AFDC-478D-B3D9-B06460C64D2F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A8A37DBD-8EB1-4346-9A0D-235403564355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A8A37DBD-8EB1-4346-9A0D-235403564355}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A8A37DBD-8EB1-4346-9A0D-235403564355}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A8A37DBD-8EB1-4346-9A0D-235403564355}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -525,6 +537,8 @@ Global
{E49C822C-E921-48DF-897B-3E603CA596D2} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{A2C0F203-11FF-4B7F-A94F-B9FD873573FE} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{7E23E229-6823-4D84-AF3A-AE14CEAEF52A} = {DD020B34-460F-455F-8D17-CF4A949F100B}
+ {ACE88DA3-AFDC-478D-B3D9-B06460C64D2F} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
+ {A8A37DBD-8EB1-4346-9A0D-235403564355} = {DD020B34-460F-455F-8D17-CF4A949F100B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40}
diff --git a/src/Dapr.Bindings/Dapr.Bindings.csproj b/src/Dapr.Bindings/Dapr.Bindings.csproj
new file mode 100644
index 000000000..f4d55867c
--- /dev/null
+++ b/src/Dapr.Bindings/Dapr.Bindings.csproj
@@ -0,0 +1,38 @@
+
+
+
+ enable
+ enable
+ Dapr.Bindings
+ Dapr Bindings SDK
+ Used to engage with the Dapr bindings building block.
+ alpha
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.14\Microsoft.Extensions.Configuration.Abstractions.dll
+
+
+ C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.14\Microsoft.Extensions.DependencyInjection.Abstractions.dll
+
+
+
+
diff --git a/src/Dapr.Bindings/DaprBindingsClient.cs b/src/Dapr.Bindings/DaprBindingsClient.cs
new file mode 100644
index 000000000..1ab2896ad
--- /dev/null
+++ b/src/Dapr.Bindings/DaprBindingsClient.cs
@@ -0,0 +1,146 @@
+// ------------------------------------------------------------------------
+// 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 Dapr.Bindings.Models;
+using Dapr.Common;
+using Autogenerated = Dapr.Client.Autogen.Grpc.v1.Dapr;
+
+namespace Dapr.Bindings;
+
+///
+///
+/// Defines client operations for managing Dapr jobs.
+/// Register the for use via dependency injection with
+/// DaprJobsServiceCollectionExtensions.AddDaprJobsClient.
+///
+///
+/// Implementations of implement because the
+/// client accesses network resources. For best performance, create a single long-lived client instance
+/// and share it for the lifetime of the application. This is done for you if created via the DI extensions. Avoid
+/// creating a disposing a client instance for each operation that the application performs - this can lead to socket
+/// exhaustion and other problems.
+///
+///
+public abstract class DaprBindingsClient(
+ Autogenerated.DaprClient client,
+ HttpClient httpClient,
+ JsonSerializerOptions jsonSerializerOptions,
+ string? daprApiToken = null) : IDaprClient
+{
+ private bool disposed;
+
+ ///
+ /// The HTTP client used by the client for calling the Dapr runtime.
+ ///
+ ///
+ /// Property exposed for testing purposes.
+ ///
+ internal readonly HttpClient HttpClient = httpClient;
+
+ ///
+ /// The Dapr API token value.
+ ///
+ ///
+ /// Property exposed for testing purposes.
+ ///
+ internal readonly string? DaprApiToken = daprApiToken;
+
+ ///
+ /// The autogenerated Dapr client.
+ ///
+ ///
+ /// Property exposed for testing purposes.
+ ///
+ internal Autogenerated.DaprClient Client { get; } = client;
+
+ ///
+ /// The JSON serializer options.
+ ///
+ ///
+ /// Property exposed for testing purposes.
+ ///
+ internal JsonSerializerOptions JsonSerializerOptions { get; } = jsonSerializerOptions;
+
+ ///
+ /// Invokes an output binding.
+ ///
+ /// The type of the data that will be JSON serialized and provided as the binding payload.
+ /// The name of the binding to sent the event to.
+ /// The type of operation to perform on the binding.
+ /// The data that will be JSON serialized and provided as the binding payload.
+ /// A collection of metadata key-value pairs that will be provided to the binding. The valid metadata keys and values are determined by the type of binding used.
+ /// A that can be used to cancel the operation.
+ /// A that will complete when the operation has completed.
+ public abstract Task InvokeBindingAsync(
+ string bindingName,
+ string operation,
+ TRequest data,
+ IReadOnlyDictionary? metadata = null,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// Invokes an output binding.
+ ///
+ /// The type of the data that will be JSON serialized and provided as the binding payload.
+ /// The type of the data that will be JSON deserialized from the binding response.
+ /// The name of the binding to sent the event to.
+ /// The type of operation to perform on the binding.
+ /// The data that will be JSON serialized and provided as the binding payload.
+ /// A collection of metadata key-value pairs that will be provided to the binding. The valid metadata keys and values are determined by the type of binding used.
+ /// A that can be used to cancel the operation.
+ /// A that will complete when the operation has completed.
+ public abstract Task InvokeBindingAsync(
+ string bindingName,
+ string operation,
+ TRequest data,
+ IReadOnlyDictionary? metadata = null,
+ CancellationToken cancellationToken = default);
+
+ ///
+ /// Invokes a binding with the provided . This method allows for control of the binding
+ /// input and output using raw bytes.
+ ///
+ /// The to send.
+ /// A that can be used to cancel the operation.
+ /// A that will complete when the operation has completed.
+ public abstract Task InvokeBindingAsync(DaprBindingRequest request, CancellationToken cancellationToken = default);
+
+ internal static KeyValuePair? GetDaprApiTokenHeader(string apiToken)
+ {
+ if (string.IsNullOrWhiteSpace(apiToken))
+ {
+ return null;
+ }
+
+ return new KeyValuePair("dapr-api-token", apiToken);
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (!this.disposed)
+ {
+ Dispose(disposing: true);
+ this.disposed = true;
+ }
+ }
+
+ ///
+ /// Disposes the resources associated with the object.
+ ///
+ /// true if called by a call to the Dispose method; otherwise false.
+ protected virtual void Dispose(bool disposing)
+ {
+ }
+}
diff --git a/src/Dapr.Bindings/DaprBindingsClientBuilder.cs b/src/Dapr.Bindings/DaprBindingsClientBuilder.cs
new file mode 100644
index 000000000..23248529a
--- /dev/null
+++ b/src/Dapr.Bindings/DaprBindingsClientBuilder.cs
@@ -0,0 +1,39 @@
+// ------------------------------------------------------------------------
+// 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 Dapr.Common;
+using Microsoft.Extensions.Configuration;
+using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
+
+namespace Dapr.Bindings;
+
+///
+/// Builds a .
+///
+/// An optional instance of .
+public sealed class DaprBindingsClientBuilder(IConfiguration? configuration = null) : DaprGenericClientBuilder(configuration)
+{
+ ///
+ /// Builds the client instance from the properties of the builder.
+ ///
+ /// The Dapr client instance.
+ ///
+ /// Builds the client instance from the properties of the builder.
+ ///
+ public override DaprBindingsClient Build()
+ {
+ var daprClientDependencies = this.BuildDaprClientDependencies(typeof(DaprBindingsClient).Assembly);
+ var client = new Autogenerated.Dapr.DaprClient(daprClientDependencies.channel);
+ return new DaprBindingsGrpcClient(client, daprClientDependencies.httpClient, this.JsonSerializerOptions, daprClientDependencies.daprApiToken);
+ }
+}
diff --git a/src/Dapr.Bindings/DaprBindingsGrpcClient.cs b/src/Dapr.Bindings/DaprBindingsGrpcClient.cs
new file mode 100644
index 000000000..ade4dfd69
--- /dev/null
+++ b/src/Dapr.Bindings/DaprBindingsGrpcClient.cs
@@ -0,0 +1,144 @@
+// ------------------------------------------------------------------------
+// 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 Dapr.Bindings.Models;
+using Dapr.Common;
+using Google.Protobuf;
+using Grpc.Core;
+using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
+
+namespace Dapr.Bindings;
+
+///
+/// A client for interacting with the Dapr Bindings endpoints.
+///
+internal sealed class DaprBindingsGrpcClient(
+ Autogenerated.Dapr.DaprClient client,
+ HttpClient httpClient,
+ JsonSerializerOptions jsonSerializerOptions,
+ string? daprApiToken = null) : DaprBindingsClient(client, httpClient, jsonSerializerOptions, daprApiToken)
+{
+ ///
+ /// Invokes an output binding.
+ ///
+ /// The type of the data that will be JSON serialized and provided as the binding payload.
+ /// The name of the binding to sent the event to.
+ /// The type of operation to perform on the binding.
+ /// The data that will be JSON serialized and provided as the binding payload.
+ /// A collection of metadata key-value pairs that will be provided to the binding. The valid metadata keys and values are determined by the type of binding used.
+ /// A that can be used to cancel the operation.
+ /// A that will complete when the operation has completed.
+ public override async Task InvokeBindingAsync(
+ string bindingName,
+ string operation,
+ TRequest data,
+ IReadOnlyDictionary? metadata = null,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(bindingName, nameof(bindingName));
+ ArgumentException.ThrowIfNullOrEmpty(operation, nameof(operation));
+
+ var serializedBytes = JsonSerializer.SerializeToUtf8Bytes(data, JsonSerializerOptions);
+ var bytes = ByteString.CopyFrom(serializedBytes);
+ await MakeInvokeBindingRequestAsync(bindingName, operation, bytes, metadata, cancellationToken);
+ }
+
+ ///
+ /// Invokes an output binding.
+ ///
+ /// The type of the data that will be JSON serialized and provided as the binding payload.
+ /// The type of the data that will be JSON deserialized from the binding response.
+ /// The name of the binding to sent the event to.
+ /// The type of operation to perform on the binding.
+ /// The data that will be JSON serialized and provided as the binding payload.
+ /// A collection of metadata key-value pairs that will be provided to the binding. The valid metadata keys and values are determined by the type of binding used.
+ /// A that can be used to cancel the operation.
+ /// A that will complete when the operation has completed.
+ public override async Task InvokeBindingAsync(
+ string bindingName,
+ string operation,
+ TRequest data,
+ IReadOnlyDictionary? metadata = null,
+ CancellationToken cancellationToken = default) where TResponse : default
+ {
+ ArgumentException.ThrowIfNullOrEmpty(bindingName, nameof(bindingName));
+ ArgumentException.ThrowIfNullOrEmpty(operation, nameof(operation));
+
+ var serializedBytes = JsonSerializer.SerializeToUtf8Bytes(data, JsonSerializerOptions);
+ var bytes = ByteString.CopyFrom(serializedBytes);
+ var response = await MakeInvokeBindingRequestAsync(bindingName, operation, bytes, metadata, cancellationToken);
+
+ try
+ {
+ return response.Data.Length == 0
+ ? default
+ : JsonSerializer.Deserialize(bytes.Span, JsonSerializerOptions);}
+ catch (JsonException ex)
+ {
+ throw new DaprException(
+ "Binding operation failed: the response payload could not be deserialized. See InnerException for details.",
+ ex);
+ }
+ }
+
+ ///
+ /// Invokes a binding with the provided . This method allows for control of the binding
+ /// input and output using raw bytes.
+ ///
+ /// The to send.
+ /// A that can be used to cancel the operation.
+ /// A that will complete when the operation has completed.
+ public override async Task InvokeBindingAsync(DaprBindingRequest request, CancellationToken cancellationToken = default)
+ {
+ var bytes = ByteString.CopyFrom(request.Data.Span);
+ var response = await MakeInvokeBindingRequestAsync(request.BindingName, request.Operation, bytes,
+ request.Metadata, cancellationToken);
+ return new DaprBindingResponse(request, response.Data.Memory, response.Metadata);
+ }
+
+ private async Task MakeInvokeBindingRequestAsync(
+ string name,
+ string operation,
+ ByteString? data = null,
+ IReadOnlyDictionary? metadata = null,
+ CancellationToken cancellationToken = default)
+ {
+ var envelope = new Autogenerated.InvokeBindingRequest { Name = name, Operation = operation };
+
+ if (data is not null)
+ {
+ envelope.Data = data;
+ }
+
+ if (metadata is not null)
+ {
+ foreach (var kvp in metadata)
+ {
+ envelope.Metadata.Add(kvp.Key, kvp.Value);
+ }
+ }
+
+ var grpcCallOptions = DaprClientUtilities.ConfigureGrpcCallOptions(typeof(DaprBindingsClient).Assembly,
+ this.DaprApiToken, cancellationToken);
+ try
+ {
+ return await Client.InvokeBindingAsync(envelope, grpcCallOptions);
+ }
+ catch (RpcException ex)
+ {
+ throw new DaprException(
+ "Binding operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
+ }
+ }
+}
diff --git a/src/Dapr.Bindings/Extensions/DaprBindingsBuilder.cs b/src/Dapr.Bindings/Extensions/DaprBindingsBuilder.cs
new file mode 100644
index 000000000..c04d8232a
--- /dev/null
+++ b/src/Dapr.Bindings/Extensions/DaprBindingsBuilder.cs
@@ -0,0 +1,27 @@
+// ------------------------------------------------------------------------
+// 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 Microsoft.Extensions.DependencyInjection;
+
+namespace Dapr.Bindings.Extensions;
+
+///
+/// Used by the fluent registration builder to configure a Dapr Bindings client.
+///
+public sealed class DaprBindingsBuilder(IServiceCollection services) : IDaprBindingsBuilder
+{
+ ///
+ /// The registered services on the builder.
+ ///
+ public IServiceCollection Services { get; } = services;
+}
diff --git a/src/Dapr.Bindings/Extensions/DaprBindingsServiceCollectionExtensions.cs b/src/Dapr.Bindings/Extensions/DaprBindingsServiceCollectionExtensions.cs
new file mode 100644
index 000000000..9161f593f
--- /dev/null
+++ b/src/Dapr.Bindings/Extensions/DaprBindingsServiceCollectionExtensions.cs
@@ -0,0 +1,38 @@
+// ------------------------------------------------------------------------
+// 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 Dapr.Common.Extensions;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Dapr.Bindings.Extensions;
+
+///
+/// Contains extension methods for using Dapr Bindings with dependency injection.
+///
+public static class DaprBindingsServiceCollectionExtensions
+{
+ ///
+ /// Adds Dapr Bindings client support to the service collection.
+ ///
+ /// The .
+ /// Optionally allows greater configuration of the using injected services.
+ /// The lifetime of the registered services.
+ ///
+ public static IDaprBindingsBuilder AddDaprBindingsClient(
+ this IServiceCollection services,
+ Action? configure = null,
+ ServiceLifetime lifetime = ServiceLifetime.Singleton) =>
+ services
+ .AddDaprClient(
+ configure, lifetime);
+}
diff --git a/src/Dapr.Bindings/IDaprBindingsBuilder.cs b/src/Dapr.Bindings/IDaprBindingsBuilder.cs
new file mode 100644
index 000000000..3d257c4d8
--- /dev/null
+++ b/src/Dapr.Bindings/IDaprBindingsBuilder.cs
@@ -0,0 +1,21 @@
+// ------------------------------------------------------------------------
+// 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 Dapr.Common;
+
+namespace Dapr.Bindings;
+
+///
+/// Responsible for registering Dapr Bindings service functionality.
+///
+public interface IDaprBindingsBuilder : IDaprServiceBuilder;
diff --git a/src/Dapr.Bindings/Models/DaprBindingRequest.cs b/src/Dapr.Bindings/Models/DaprBindingRequest.cs
new file mode 100644
index 000000000..edf7bd64a
--- /dev/null
+++ b/src/Dapr.Bindings/Models/DaprBindingRequest.cs
@@ -0,0 +1,33 @@
+// ------------------------------------------------------------------------
+// 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.
+// ------------------------------------------------------------------------
+
+namespace Dapr.Bindings.Models;
+
+///
+/// Represents the request used to invoke a binding.
+///
+/// The name of the binding.
+/// The type of the operation to perform on the binding.
+public sealed record DaprBindingRequest(string BindingName, string Operation )
+{
+ ///
+ /// The binding request payload.
+ ///
+ public ReadOnlyMemory Data { get; init; } = default;
+
+ ///
+ /// The collection of metadata key/value pairs that will be provided to the binding.
+ /// The valid metadata keys and values are determined by the type of binding used.
+ ///
+ public Dictionary Metadata { get; init; } = [];
+}
diff --git a/src/Dapr.Bindings/Models/DaprBindingResponse.cs b/src/Dapr.Bindings/Models/DaprBindingResponse.cs
new file mode 100644
index 000000000..6fb71ec8b
--- /dev/null
+++ b/src/Dapr.Bindings/Models/DaprBindingResponse.cs
@@ -0,0 +1,25 @@
+// ------------------------------------------------------------------------
+// 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.
+// ------------------------------------------------------------------------
+
+namespace Dapr.Bindings.Models;
+
+///
+/// Represents the response from invoking a binding.
+///
+/// The associated with this response.
+/// The response payload.
+/// The response metadata.
+public sealed record DaprBindingResponse(
+ DaprBindingRequest Request,
+ ReadOnlyMemory Data,
+ IReadOnlyDictionary Metadata);
diff --git a/src/Dapr.Common/AssemblyInfo.cs b/src/Dapr.Common/AssemblyInfo.cs
index 3037485a9..90fb59cd0 100644
--- a/src/Dapr.Common/AssemblyInfo.cs
+++ b/src/Dapr.Common/AssemblyInfo.cs
@@ -18,6 +18,7 @@
[assembly: InternalsVisibleTo("Dapr.Actors.AspNetCore, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.AI, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.AspNetCore, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
+[assembly: InternalsVisibleTo("Dapr.Bindings, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Client, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Jobs, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Messaging, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
@@ -33,6 +34,7 @@
[assembly: InternalsVisibleTo("Dapr.AspNetCore.IntegrationTest, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.AspNetCore.IntegrationTest.App, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.AspNetCore.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
+[assembly: InternalsVisibleTo("Dapr.Bindings.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Client.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Common.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.E2E.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
diff --git a/src/Dapr.Common/DaprGenericClientBuilder.cs b/src/Dapr.Common/DaprGenericClientBuilder.cs
index 8ff59490f..3e29a2eff 100644
--- a/src/Dapr.Common/DaprGenericClientBuilder.cs
+++ b/src/Dapr.Common/DaprGenericClientBuilder.cs
@@ -200,7 +200,7 @@ protected internal (GrpcChannel channel, HttpClient httpClient, Uri httpEndpoint
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
}
- var httpEndpoint = new Uri(this.HttpEndpoint);
+ var httpEndpoint = new Uri(this.HttpEndpoint);
if (httpEndpoint.Scheme != "http" && httpEndpoint.Scheme != "https")
{
throw new InvalidOperationException("The HTTP endpoint must use http or https.");
diff --git a/src/Dapr.Jobs/Extensions/DaprJobsBuilder.cs b/src/Dapr.Jobs/Extensions/DaprJobsBuilder.cs
index 51bdb8985..22b2d7d58 100644
--- a/src/Dapr.Jobs/Extensions/DaprJobsBuilder.cs
+++ b/src/Dapr.Jobs/Extensions/DaprJobsBuilder.cs
@@ -18,7 +18,6 @@ namespace Dapr.Jobs.Extensions;
///
/// Used by the fluent registration builder to configure a Dapr Jobs client.
///
-///
public sealed class DaprJobsBuilder(IServiceCollection services) : IDaprJobsBuilder
{
///
diff --git a/test/Dapr.Bindings.Test/Dapr.Bindings.Test.csproj b/test/Dapr.Bindings.Test/Dapr.Bindings.Test.csproj
new file mode 100644
index 000000000..7cb037c26
--- /dev/null
+++ b/test/Dapr.Bindings.Test/Dapr.Bindings.Test.csproj
@@ -0,0 +1,25 @@
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Dapr.Bindings.Test/DaprBindingsClientBuilderTests.cs b/test/Dapr.Bindings.Test/DaprBindingsClientBuilderTests.cs
new file mode 100644
index 000000000..d4b14dfea
--- /dev/null
+++ b/test/Dapr.Bindings.Test/DaprBindingsClientBuilderTests.cs
@@ -0,0 +1,121 @@
+// ------------------------------------------------------------------------
+// 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;
+using System.Text.Json;
+using Grpc.Net.Client;
+
+namespace Dapr.Bindings.Test;
+
+public class DaprBindingsClientBuilderTests
+{
+ [Fact]
+ public void DaprClientBuilder_UsesPropertyNameCaseHandlingInsensitiveByDefault()
+ {
+ DaprBindingsClientBuilder builder = new DaprBindingsClientBuilder();
+ Assert.True(builder.JsonSerializerOptions.PropertyNameCaseInsensitive);
+ }
+
+ [Fact]
+ public void DaprBindingsClientBuilder_UsesPropertyNameCaseHandlingAsSpecified()
+ {
+ var builder = new DaprBindingsClientBuilder();
+ builder.UseJsonSerializationOptions(new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = false
+ });
+ Assert.False(builder.JsonSerializerOptions.PropertyNameCaseInsensitive);
+ }
+
+ [Fact]
+ public void DaprBindingsClientBuilder_UsesThrowOperationCanceledOnCancellation_ByDefault()
+ {
+ var builder = new DaprBindingsClientBuilder();
+ var daprClient = builder.Build();
+ Assert.True(builder.GrpcChannelOptions.ThrowOperationCanceledOnCancellation);
+ }
+
+ [Fact]
+ public void DaprBindingsClientBuilder_DoesNotOverrideUserGrpcChannelOptions()
+ {
+ var builder = new DaprBindingsClientBuilder();
+ var daprClient = builder.UseGrpcChannelOptions(new GrpcChannelOptions()).Build();
+ Assert.False(builder.GrpcChannelOptions.ThrowOperationCanceledOnCancellation);
+ }
+
+ [Fact]
+ public void DaprBindingsClientBuilder_ValidatesGrpcEndpointScheme()
+ {
+ var builder = new DaprBindingsClientBuilder();
+ builder.UseGrpcEndpoint("ftp://example.com");
+
+ var ex = Assert.Throws(() => builder.Build());
+ Assert.Equal("The gRPC endpoint must use http or https.", ex.Message);
+ }
+
+ [Fact]
+ public void DaprBindingsClientBuilder_ValidatesHttpEndpointScheme()
+ {
+ var builder = new DaprBindingsClientBuilder();
+ builder.UseHttpEndpoint("ftp://example.com");
+
+ var ex = Assert.Throws(() => builder.Build());
+ Assert.Equal("The HTTP endpoint must use http or https.", ex.Message);
+ }
+
+ [Fact]
+ public void DaprBindingsClientBuilder_SetsApiToken()
+ {
+ var builder = new DaprBindingsClientBuilder();
+ builder.UseDaprApiToken("test_token");
+ builder.Build();
+ Assert.Equal("test_token", builder.DaprApiToken);
+ }
+
+ [Fact]
+ public void DaprBindingsClientBuilder_SetsNullApiToken()
+ {
+ var builder = new DaprBindingsClientBuilder();
+ builder.UseDaprApiToken(null);
+ builder.Build();
+ Assert.Null(builder.DaprApiToken);
+ }
+
+ [Fact]
+ public void DaprBindingsClientBuilder_ApiTokenSet_SetsApiTokenHeader()
+ {
+ var builder = new DaprBindingsClientBuilder();
+ builder.UseDaprApiToken("test_token");
+
+ var entry = DaprBindingsClient.GetDaprApiTokenHeader(builder.DaprApiToken);
+ Assert.NotNull(entry);
+ Assert.Equal("test_token", entry.Value.Value);
+ }
+
+ [Fact]
+ public void DaprBindingsClientBuilder_ApiTokenNotSet_EmptyApiTokenHeader()
+ {
+ var builder = new DaprBindingsClientBuilder();
+ var entry = DaprBindingsClient.GetDaprApiTokenHeader(builder.DaprApiToken);
+ Assert.Equal(default, entry);
+ }
+
+ [Fact]
+ public void DaprBindingsClientBuilder_SetsTimeout()
+ {
+ var builder = new DaprBindingsClientBuilder();
+ builder.UseTimeout(TimeSpan.FromSeconds(2));
+ builder.Build();
+ Assert.Equal(2, builder.Timeout.Seconds);
+ }
+}
diff --git a/test/Dapr.Bindings.Test/Extensions/DaprBindingsServiceCollectionExtensionsTest.cs b/test/Dapr.Bindings.Test/Extensions/DaprBindingsServiceCollectionExtensionsTest.cs
new file mode 100644
index 000000000..89f634658
--- /dev/null
+++ b/test/Dapr.Bindings.Test/Extensions/DaprBindingsServiceCollectionExtensionsTest.cs
@@ -0,0 +1,167 @@
+// ------------------------------------------------------------------------
+// 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.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Dapr.Bindings.Extensions;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Dapr.Bindings.Test.Extensions;
+
+public class DaprBindingsServiceCollectionExtensionsTest
+{
+ [Fact]
+
+ public void AddDaprBindingsClient_FromIConfiguration()
+ {
+ const string apiToken = "abc123";
+ var configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary { { "DAPR_API_TOKEN", apiToken } })
+ .Build();
+
+ var services = new ServiceCollection();
+ services.AddSingleton(configuration);
+
+ services.AddDaprBindingsClient();
+
+ var app = services.BuildServiceProvider();
+
+ var jobsClient = app.GetRequiredService() as DaprBindingsGrpcClient;
+
+ Assert.NotNull(jobsClient!.DaprApiToken);
+ Assert.Equal(apiToken, jobsClient.DaprApiToken);
+ }
+
+ [Fact]
+ public void AddDaprBindingsClient_DaprClientRegistration_UseMostRecentVersion()
+ {
+ var services = new ServiceCollection();
+
+ services.AddDaprBindingsClient((_, builder) =>
+ {
+ //Sets the API token value
+ builder.UseDaprApiToken("abcd1234");
+ });
+ services.AddDaprBindingsClient(); //Sets a default API token value of an empty string
+
+ var serviceProvider = services.BuildServiceProvider();
+ var daprJobClient = serviceProvider.GetRequiredService() as DaprBindingsGrpcClient;
+
+ Assert.NotNull(daprJobClient!.HttpClient);
+ Assert.False(daprJobClient.HttpClient.DefaultRequestHeaders.TryGetValues("dapr-api-token", out var _));
+ }
+
+ [Fact]
+ public void AddDaprBindingsClient_RegistersIHttpClientFactory()
+ {
+ var services = new ServiceCollection();
+
+ services.AddDaprBindingsClient();
+
+ var serviceProvider = services.BuildServiceProvider();
+
+ var httpClientFactory = serviceProvider.GetService();
+ Assert.NotNull(httpClientFactory);
+
+ var daprBindingsClient = serviceProvider.GetService();
+ Assert.NotNull(daprBindingsClient);
+ }
+
+ [Fact]
+ public void AddDaprBindingsClient_RegistersUsingDependencyFromIServiceProvider()
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton();
+ services.AddDaprBindingsClient((provider, builder) =>
+ {
+ var configProvider = provider.GetRequiredService();
+ var apiToken = configProvider.GetApiTokenValue();
+ builder.UseDaprApiToken(apiToken);
+ });
+
+ var serviceProvider = services.BuildServiceProvider();
+ var client = serviceProvider.GetRequiredService() as DaprBindingsGrpcClient;
+
+ //Validate it's set on the GrpcClient - note that it doesn't get set on the HttpClient
+ Assert.NotNull(client);
+ Assert.NotNull(client.DaprApiToken);
+ Assert.Equal("abcdef", client.DaprApiToken);
+ Assert.NotNull(client.HttpClient);
+
+ if (!client.HttpClient.DefaultRequestHeaders.TryGetValues("dapr-api-token", out var daprApiToken))
+ {
+ Assert.Fail();
+ }
+ Assert.Equal("abcdef", daprApiToken.FirstOrDefault());
+ }
+
+ [Fact]
+ public void RegisterJobsClient_ShouldRegisterSingleton_WhenLifetimeIsSingleton()
+ {
+ var services = new ServiceCollection();
+
+ services.AddDaprBindingsClient((_, _) => { }, ServiceLifetime.Singleton);
+ var serviceProvider = services.BuildServiceProvider();
+
+ var DaprBindingsClient1 = serviceProvider.GetService();
+ var DaprBindingsClient2 = serviceProvider.GetService();
+
+ Assert.NotNull(DaprBindingsClient1);
+ Assert.NotNull(DaprBindingsClient2);
+
+ Assert.Same(DaprBindingsClient1, DaprBindingsClient2);
+ }
+
+ [Fact]
+ public async Task RegisterJobsClient_ShouldRegisterScoped_WhenLifetimeIsScoped()
+ {
+ var services = new ServiceCollection();
+
+ services.AddDaprBindingsClient((_, _) => { }, ServiceLifetime.Scoped);
+ var serviceProvider = services.BuildServiceProvider();
+
+ await using var scope1 = serviceProvider.CreateAsyncScope();
+ var DaprBindingsClient1 = scope1.ServiceProvider.GetService();
+
+ await using var scope2 = serviceProvider.CreateAsyncScope();
+ var DaprBindingsClient2 = scope2.ServiceProvider.GetService();
+
+ Assert.NotNull(DaprBindingsClient1);
+ Assert.NotNull(DaprBindingsClient2);
+ Assert.NotSame(DaprBindingsClient1, DaprBindingsClient2);
+ }
+
+ [Fact]
+ public void RegisterJobsClient_ShouldRegisterTransient_WhenLifetimeIsTransient()
+ {
+ var services = new ServiceCollection();
+
+ services.AddDaprBindingsClient((_, _) => { }, ServiceLifetime.Transient);
+ var serviceProvider = services.BuildServiceProvider();
+
+ var DaprBindingsClient1 = serviceProvider.GetService();
+ var DaprBindingsClient2 = serviceProvider.GetService();
+
+ Assert.NotNull(DaprBindingsClient1);
+ Assert.NotNull(DaprBindingsClient2);
+ Assert.NotSame(DaprBindingsClient1, DaprBindingsClient2);
+ }
+
+ private class TestSecretRetriever
+ {
+ public string GetApiTokenValue() => "abcdef";
+ }
+}
diff --git a/test/Dapr.Jobs.Test/Extensions/DaprJobsServiceCollectionExtensionsTests.cs b/test/Dapr.Jobs.Test/Extensions/DaprJobsServiceCollectionExtensionsTests.cs
index 814e52794..dd40287f9 100644
--- a/test/Dapr.Jobs.Test/Extensions/DaprJobsServiceCollectionExtensionsTests.cs
+++ b/test/Dapr.Jobs.Test/Extensions/DaprJobsServiceCollectionExtensionsTests.cs
@@ -11,7 +11,6 @@
// limitations under the License.
// ------------------------------------------------------------------------
-using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;