diff --git a/Directory.Packages.props b/Directory.Packages.props
index a98e9db58..efb48fcc4 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,52 +1,52 @@
-
- true
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/all.sln b/all.sln
index 1dab60475..9a163b1d9 100644
--- a/all.sln
+++ b/all.sln
@@ -119,6 +119,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Common", "src\Dapr.Com
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Common.Test", "test\Dapr.Common.Test\Dapr.Common.Test.csproj", "{CDB47863-BEBD-4841-A807-46D868962521}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.AI", "src\Dapr.AI\Dapr.AI.csproj", "{273F2527-1658-4CCF-8DC6-600E921188C5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.AI.Test", "test\Dapr.AI.Test\Dapr.AI.Test.csproj", "{2F3700EF-1CDA-4C15-AC88-360230000ECD}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AI", "AI", "{3046DBF4-C2FF-4F3A-9176-E1C01E0A90E5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConversationalAI", "examples\AI\ConversationalAI\ConversationalAI.csproj", "{11011FF8-77EA-4B25-96C0-29D4D486EF1C}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowExternalInteraction", "examples\Workflow\WorkflowExternalInteraction\WorkflowExternalInteraction.csproj", "{43CB06A9-7E88-4C5F-BFB8-947E072CBC9F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowMonitor", "examples\Workflow\WorkflowMonitor\WorkflowMonitor.csproj", "{7F73A3D8-FFC2-4E31-AA3D-A4840316A8C6}"
@@ -331,6 +339,18 @@ Global
{CDB47863-BEBD-4841-A807-46D868962521}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CDB47863-BEBD-4841-A807-46D868962521}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CDB47863-BEBD-4841-A807-46D868962521}.Release|Any CPU.Build.0 = Release|Any CPU
+ {273F2527-1658-4CCF-8DC6-600E921188C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {273F2527-1658-4CCF-8DC6-600E921188C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {273F2527-1658-4CCF-8DC6-600E921188C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {273F2527-1658-4CCF-8DC6-600E921188C5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2F3700EF-1CDA-4C15-AC88-360230000ECD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2F3700EF-1CDA-4C15-AC88-360230000ECD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2F3700EF-1CDA-4C15-AC88-360230000ECD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2F3700EF-1CDA-4C15-AC88-360230000ECD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {11011FF8-77EA-4B25-96C0-29D4D486EF1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {11011FF8-77EA-4B25-96C0-29D4D486EF1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {11011FF8-77EA-4B25-96C0-29D4D486EF1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {11011FF8-77EA-4B25-96C0-29D4D486EF1C}.Release|Any CPU.Build.0 = Release|Any CPU
{43CB06A9-7E88-4C5F-BFB8-947E072CBC9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{43CB06A9-7E88-4C5F-BFB8-947E072CBC9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{43CB06A9-7E88-4C5F-BFB8-947E072CBC9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -439,6 +459,10 @@ Global
{DFBABB04-50E9-42F6-B470-310E1B545638} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{B445B19C-A925-4873-8CB7-8317898B6970} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{CDB47863-BEBD-4841-A807-46D868962521} = {DD020B34-460F-455F-8D17-CF4A949F100B}
+ {273F2527-1658-4CCF-8DC6-600E921188C5} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
+ {2F3700EF-1CDA-4C15-AC88-360230000ECD} = {DD020B34-460F-455F-8D17-CF4A949F100B}
+ {3046DBF4-C2FF-4F3A-9176-E1C01E0A90E5} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}
+ {11011FF8-77EA-4B25-96C0-29D4D486EF1C} = {3046DBF4-C2FF-4F3A-9176-E1C01E0A90E5}
{43CB06A9-7E88-4C5F-BFB8-947E072CBC9F} = {BF3ED6BF-ADF3-4D25-8E89-02FB8D945CA9}
{7F73A3D8-FFC2-4E31-AA3D-A4840316A8C6} = {BF3ED6BF-ADF3-4D25-8E89-02FB8D945CA9}
{945DD3B7-94E5-435E-B3CB-796C20A652C7} = {BF3ED6BF-ADF3-4D25-8E89-02FB8D945CA9}
diff --git a/daprdocs/content/en/dotnet-sdk-docs/_index.md b/daprdocs/content/en/dotnet-sdk-docs/_index.md
index 82d16016d..ce80b3ea9 100644
--- a/daprdocs/content/en/dotnet-sdk-docs/_index.md
+++ b/daprdocs/content/en/dotnet-sdk-docs/_index.md
@@ -84,6 +84,13 @@ Put the Dapr .NET SDK to the test. Walk through the .NET quickstarts and tutoria
+
+
+
AI
+
Create and manage AI operations in .NET
+
+
+
## More information
diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/_index.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/_index.md
new file mode 100644
index 000000000..4374a8598
--- /dev/null
+++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/_index.md
@@ -0,0 +1,83 @@
+_index.md
+
+---
+type: docs
+title: "Getting started with the Dapr AI .NET SDK client"
+linkTitle: "AI"
+weight: 10000
+description: How to get up and running with the Dapr AI .NET SDK
+no_list: true
+---
+
+The Dapr AI client package allows you to interact with the AI capabilities provided by the Dapr sidecar.
+
+## Installation
+
+To get started with the Dapr AI .NET SDK client, install the following package from NuGet:
+```sh
+dotnet add package Dapr.AI
+```
+
+A `DaprConversationClient` holes access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar.
+
+### Dependency Injection
+
+The `AddDaprAiConversation()` method will register the Dapr client ASP.NET Core dependency injection and is the recommended approach
+for using this package. This method accepts an optional options delegate for configuring the `DaprConversationClient` and a
+`ServiceLifetime` argument, allowing you to specify a different lifetime for the registered services instead of the default `Singleton`
+value.
+
+The following example assumes all default values are acceptable and is sufficient to register the `DaprConversationClient`:
+
+```csharp
+services.AddDaprAiConversation();
+```
+
+The optional configuration delegate is used to configure the `DaprConversationClient` by specifying options on the
+`DaprConversationClientBuilder` as in the following example:
+```csharp
+services.AddSingleton();
+services.AddDaprAiConversation((serviceProvider, clientBuilder) => {
+ //Inject a service to source a value from
+ var optionsProvider = serviceProvider.GetRequiredService();
+ var standardTimeout = optionsProvider.GetStandardTimeout();
+
+ //Configure the value on the client builder
+ clientBuilder.UseTimeout(standardTimeout);
+});
+```
+
+### Manual Instantiation
+Rather than using dependency injection, a `DaprConversationClient` can also be built using the static client builder.
+
+For best performance, create a single long-lived instance of `DaprConversationClient` and provide access to that shared instance throughout
+your application. `DaprConversationClient` instances are thread-safe and intended to be shared.
+
+Avoid creating a `DaprConversationClient` per-operation.
+
+A `DaprConversationClient` can be configured by invoking methods on the `DaprConversationClientBuilder` class before calling `.Build()`
+to create the client. The settings for each `DaprConversationClient` are separate and cannot be changed after calling `.Build()`.
+
+```csharp
+var daprConversationClient = new DaprConversationClientBuilder()
+ .UseJsonSerializerSettings( ... ) //Configure JSON serializer
+ .Build();
+```
+
+See the .NET [documentation here]({{< ref dotnet-client >}}) for more information about the options available when configuring the Dapr client via the builder.
+
+## Try it out
+Put the Dapr AI .NET SDK to the test. Walk through the samples to see Dapr in action:
+
+| SDK Samples | Description |
+| ----------- | ----------- |
+| [SDK samples](https://github.com/dapr/dotnet-sdk/tree/master/examples) | Clone the SDK repo to try out some examples and get started. |
+
+## Building Blocks
+
+This part of the .NET SDK allows you to interface with the Conversations API to send and receive messages from
+large language models.
+
+### Send messages
+
+
diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-usage.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-usage.md
new file mode 100644
index 000000000..93700c383
--- /dev/null
+++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-usage.md
@@ -0,0 +1,7 @@
+---
+type: docs
+title: "Best practices with the Dapr AI .NET SDK client"
+linkTitle: "Best Practices"
+weight: 100000
+description: How to get up and running with the Dapr .NET SDK
+---
\ No newline at end of file
diff --git a/examples/AI/ConversationalAI/ConversationalAI.csproj b/examples/AI/ConversationalAI/ConversationalAI.csproj
new file mode 100644
index 000000000..976265a5c
--- /dev/null
+++ b/examples/AI/ConversationalAI/ConversationalAI.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/examples/AI/ConversationalAI/Program.cs b/examples/AI/ConversationalAI/Program.cs
new file mode 100644
index 000000000..bd3dc906a
--- /dev/null
+++ b/examples/AI/ConversationalAI/Program.cs
@@ -0,0 +1,23 @@
+using Dapr.AI.Conversation;
+using Dapr.AI.Conversation.Extensions;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddDaprAiConversation();
+
+var app = builder.Build();
+
+var conversationClient = app.Services.GetRequiredService();
+var response = await conversationClient.ConverseAsync("conversation",
+ new List
+ {
+ new DaprConversationInput(
+ "Please write a witty haiku about the Dapr distributed programming framework at dapr.io",
+ DaprConversationRole.Generic)
+ });
+
+Console.WriteLine("Received the following from the LLM:");
+foreach (var resp in response.Outputs)
+{
+ Console.WriteLine($"\t{resp.Result}");
+}
diff --git a/src/Dapr.AI/AssemblyInfo.cs b/src/Dapr.AI/AssemblyInfo.cs
new file mode 100644
index 000000000..8d96dcf56
--- /dev/null
+++ b/src/Dapr.AI/AssemblyInfo.cs
@@ -0,0 +1,4 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Dapr.AI.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
+
diff --git a/src/Dapr.AI/Conversation/ConversationOptions.cs b/src/Dapr.AI/Conversation/ConversationOptions.cs
new file mode 100644
index 000000000..87a49117a
--- /dev/null
+++ b/src/Dapr.AI/Conversation/ConversationOptions.cs
@@ -0,0 +1,40 @@
+// ------------------------------------------------------------------------
+// Copyright 2024 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 Google.Protobuf.WellKnownTypes;
+
+namespace Dapr.AI.Conversation;
+
+///
+/// Options used to configure the conversation operation.
+///
+/// The identifier of the conversation this is a continuation of.
+public sealed record ConversationOptions(string? ConversationId = null)
+{
+ ///
+ /// Temperature for the LLM to optimize for creativity or predictability.
+ ///
+ public double Temperature { get; init; } = default;
+ ///
+ /// Flag that indicates whether data that comes back from the LLM should be scrubbed of PII data.
+ ///
+ public bool ScrubPII { get; init; } = default;
+ ///
+ /// The metadata passing to the conversation components.
+ ///
+ public IReadOnlyDictionary Metadata { get; init; } = new Dictionary();
+ ///
+ /// Parameters for all custom fields.
+ ///
+ public IReadOnlyDictionary Parameters { get; init; } = new Dictionary();
+}
diff --git a/src/Dapr.AI/Conversation/DaprConversationClient.cs b/src/Dapr.AI/Conversation/DaprConversationClient.cs
new file mode 100644
index 000000000..2335197bc
--- /dev/null
+++ b/src/Dapr.AI/Conversation/DaprConversationClient.cs
@@ -0,0 +1,116 @@
+// ------------------------------------------------------------------------
+// Copyright 2024 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 Dapr.Common.Extensions;
+using P = Dapr.Client.Autogen.Grpc.v1;
+
+namespace Dapr.AI.Conversation;
+
+///
+/// Used to interact with the Dapr conversation building block.
+///
+public sealed class DaprConversationClient : DaprAIClient
+{
+ ///
+ /// The HTTP client used by the client for calling the Dapr runtime.
+ ///
+ ///
+ /// Property exposed for testing purposes.
+ ///
+ internal readonly HttpClient HttpClient;
+ ///
+ /// The Dapr API token value.
+ ///
+ ///
+ /// Property exposed for testing purposes.
+ ///
+ internal readonly string? DaprApiToken;
+ ///
+ /// The autogenerated Dapr client.
+ ///
+ ///
+ /// Property exposed for testing purposes.
+ ///
+ internal P.Dapr.DaprClient Client { get; }
+
+ ///
+ /// Used to initialize a new instance of a .
+ ///
+ /// The Dapr client.
+ /// The HTTP client used by the client for calling the Dapr runtime.
+ /// An optional token required to send requests to the Dapr sidecar.
+ public DaprConversationClient(P.Dapr.DaprClient client,
+ HttpClient httpClient,
+ string? daprApiToken = null)
+ {
+ this.Client = client;
+ this.HttpClient = httpClient;
+ this.DaprApiToken = daprApiToken;
+ }
+
+ ///
+ /// Sends various inputs to the large language model via the Conversational building block on the Dapr sidecar.
+ ///
+ /// The name of the Dapr conversation component.
+ /// The input values to send.
+ /// Optional options used to configure the conversation.
+ /// Cancellation token.
+ /// The response(s) provided by the LLM provider.
+ public override async Task ConverseAsync(string daprConversationComponentName, IReadOnlyList inputs, ConversationOptions? options = null,
+ CancellationToken cancellationToken = default)
+ {
+ var request = new P.ConversationRequest
+ {
+ Name = daprConversationComponentName
+ };
+
+ if (options is not null)
+ {
+ request.ContextID = options.ConversationId;
+ request.ScrubPII = options.ScrubPII;
+
+ foreach (var (key, value) in options.Metadata)
+ {
+ request.Metadata.Add(key, value);
+ }
+
+ foreach (var (key, value) in options.Parameters)
+ {
+ request.Parameters.Add(key, value);
+ }
+ }
+
+ foreach (var input in inputs)
+ {
+ request.Inputs.Add(new P.ConversationInput
+ {
+ ScrubPII = input.ScrubPII,
+ Message = input.Message,
+ Role = input.Role.GetValueFromEnumMember()
+ });
+ }
+
+ var grpCCallOptions =
+ DaprClientUtilities.ConfigureGrpcCallOptions(typeof(DaprConversationClient).Assembly, this.DaprApiToken,
+ cancellationToken);
+
+ var result = await Client.ConverseAlpha1Async(request, grpCCallOptions).ConfigureAwait(false);
+ var outputs = result.Outputs.Select(output => new DaprConversationResult(output.Result)
+ {
+ Parameters = output.Parameters.ToDictionary(kvp => kvp.Key, parameter => parameter.Value)
+ }).ToList();
+
+ return new DaprConversationResponse(outputs);
+ }
+}
diff --git a/src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs b/src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs
new file mode 100644
index 000000000..5e0a0825d
--- /dev/null
+++ b/src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs
@@ -0,0 +1,46 @@
+// ------------------------------------------------------------------------
+// Copyright 2024 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.Dapr;
+
+namespace Dapr.AI.Conversation;
+
+///
+/// Used to create a new instance of a .
+///
+public sealed class DaprConversationClientBuilder : DaprGenericClientBuilder
+{
+ ///
+ /// Used to initialize a new instance of the .
+ ///
+ ///
+ public DaprConversationClientBuilder(IConfiguration? configuration = null) : base(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 DaprConversationClient Build()
+ {
+ var daprClientDependencies = BuildDaprClientDependencies(typeof(DaprConversationClient).Assembly);
+ var client = new Autogenerated.DaprClient(daprClientDependencies.channel);
+ return new DaprConversationClient(client, daprClientDependencies.httpClient, daprClientDependencies.daprApiToken);
+ }
+}
diff --git a/src/Dapr.AI/Conversation/DaprConversationInput.cs b/src/Dapr.AI/Conversation/DaprConversationInput.cs
new file mode 100644
index 000000000..3485849c8
--- /dev/null
+++ b/src/Dapr.AI/Conversation/DaprConversationInput.cs
@@ -0,0 +1,22 @@
+// ------------------------------------------------------------------------
+// Copyright 2024 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.AI.Conversation;
+
+///
+/// Represents an input for the Dapr Conversational API.
+///
+/// The message to send to the LLM.
+/// The role indicating the entity providing the message.
+/// If true, scrubs the data that goes into the LLM.
+public sealed record DaprConversationInput(string Message, DaprConversationRole Role, bool ScrubPII = false);
diff --git a/src/Dapr.AI/Conversation/DaprConversationResponse.cs b/src/Dapr.AI/Conversation/DaprConversationResponse.cs
new file mode 100644
index 000000000..36de7fd6e
--- /dev/null
+++ b/src/Dapr.AI/Conversation/DaprConversationResponse.cs
@@ -0,0 +1,21 @@
+// ------------------------------------------------------------------------
+// Copyright 2024 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.AI.Conversation;
+
+///
+/// The response for a conversation.
+///
+/// The collection of conversation results.
+/// The identifier of an existing or newly created conversation.
+public record DaprConversationResponse(IReadOnlyList Outputs, string? ConversationId = null);
diff --git a/src/Dapr.AI/Conversation/DaprConversationResult.cs b/src/Dapr.AI/Conversation/DaprConversationResult.cs
new file mode 100644
index 000000000..700cc8730
--- /dev/null
+++ b/src/Dapr.AI/Conversation/DaprConversationResult.cs
@@ -0,0 +1,28 @@
+// ------------------------------------------------------------------------
+// Copyright 2024 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 Google.Protobuf.WellKnownTypes;
+
+namespace Dapr.AI.Conversation;
+
+///
+/// The result for a single conversational input.
+///
+/// The result for one conversation input.
+public record DaprConversationResult(string Result)
+{
+ ///
+ /// Parameters for all custom fields.
+ ///
+ public IReadOnlyDictionary Parameters { get; init; } = new Dictionary();
+}
diff --git a/src/Dapr.AI/Conversation/DaprConversationRole.cs b/src/Dapr.AI/Conversation/DaprConversationRole.cs
new file mode 100644
index 000000000..3e48a41c1
--- /dev/null
+++ b/src/Dapr.AI/Conversation/DaprConversationRole.cs
@@ -0,0 +1,42 @@
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+using Dapr.Common.JsonConverters;
+
+namespace Dapr.AI.Conversation;
+
+///
+/// Represents who
+///
+public enum DaprConversationRole
+{
+ ///
+ /// Represents a message sent by an AI.
+ ///
+ [EnumMember(Value="ai")]
+ AI,
+ ///
+ /// Represents a message sent by a human.
+ ///
+ [EnumMember(Value="human")]
+ Human,
+ ///
+ /// Represents a message sent by the system.
+ ///
+ [EnumMember(Value="system")]
+ System,
+ ///
+ /// Represents a message sent by a generic user.
+ ///
+ [EnumMember(Value="generic")]
+ Generic,
+ ///
+ /// Represents a message sent by a function.
+ ///
+ [EnumMember(Value="function")]
+ Function,
+ ///
+ /// Represents a message sent by a tool.
+ ///
+ [EnumMember(Value="tool")]
+ Tool
+}
diff --git a/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilder.cs b/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilder.cs
new file mode 100644
index 000000000..876d223b1
--- /dev/null
+++ b/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilder.cs
@@ -0,0 +1,35 @@
+// ------------------------------------------------------------------------
+// Copyright 2024 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.AI.Conversation.Extensions;
+
+///
+/// Used by the fluent registration builder to configure a Dapr AI conversational manager.
+///
+public sealed class DaprAiConversationBuilder : IDaprAiConversationBuilder
+{
+ ///
+ /// The registered services on the builder.
+ ///
+ public IServiceCollection Services { get; }
+
+ ///
+ /// Used to initialize a new .
+ ///
+ public DaprAiConversationBuilder(IServiceCollection services)
+ {
+ Services = services;
+ }
+}
diff --git a/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilderExtensions.cs b/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilderExtensions.cs
new file mode 100644
index 000000000..902fd82a3
--- /dev/null
+++ b/src/Dapr.AI/Conversation/Extensions/DaprAiConversationBuilderExtensions.cs
@@ -0,0 +1,64 @@
+// ------------------------------------------------------------------------
+// Copyright 2024 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.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Dapr.AI.Conversation.Extensions;
+
+///
+/// Contains the dependency injection registration extensions for the Dapr AI Conversation operations.
+///
+public static class DaprAiConversationBuilderExtensions
+{
+ ///
+ /// Registers the necessary functionality for the Dapr AI conversation functionality.
+ ///
+ ///
+ public static IDaprAiConversationBuilder AddDaprAiConversation(this IServiceCollection services, Action? configure = null, ServiceLifetime lifetime = ServiceLifetime.Singleton)
+ {
+ ArgumentNullException.ThrowIfNull(services, nameof(services));
+
+ services.AddHttpClient();
+
+ var registration = new Func(provider =>
+ {
+ var configuration = provider.GetService();
+ var builder = new DaprConversationClientBuilder(configuration);
+
+ var httpClientFactory = provider.GetRequiredService();
+ builder.UseHttpClientFactory(httpClientFactory);
+
+ configure?.Invoke(provider, builder);
+
+ return builder.Build();
+ });
+
+ switch (lifetime)
+ {
+ case ServiceLifetime.Scoped:
+ services.TryAddScoped(registration);
+ break;
+ case ServiceLifetime.Transient:
+ services.TryAddTransient(registration);
+ break;
+ case ServiceLifetime.Singleton:
+ default:
+ services.TryAddSingleton(registration);
+ break;
+ }
+
+ return new DaprAiConversationBuilder(services);
+ }
+}
diff --git a/src/Dapr.AI/Conversation/Extensions/IDaprAiConversationBuilder.cs b/src/Dapr.AI/Conversation/Extensions/IDaprAiConversationBuilder.cs
new file mode 100644
index 000000000..30d3822d4
--- /dev/null
+++ b/src/Dapr.AI/Conversation/Extensions/IDaprAiConversationBuilder.cs
@@ -0,0 +1,23 @@
+// ------------------------------------------------------------------------
+// Copyright 2024 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.AI.Extensions;
+
+namespace Dapr.AI.Conversation.Extensions;
+
+///
+/// Provides a root builder for the Dapr AI conversational functionality facilitating a more fluent-style registration.
+///
+public interface IDaprAiConversationBuilder : IDaprAiServiceBuilder
+{
+}
diff --git a/src/Dapr.AI/Dapr.AI.csproj b/src/Dapr.AI/Dapr.AI.csproj
new file mode 100644
index 000000000..8220c5c4d
--- /dev/null
+++ b/src/Dapr.AI/Dapr.AI.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net6;net8
+ enable
+ enable
+ Dapr.AI
+ Dapr AI SDK
+ Dapr AI SDK for performing operations associated with artificial intelligence.
+ alpha
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Dapr.AI/DaprAIClient.cs b/src/Dapr.AI/DaprAIClient.cs
new file mode 100644
index 000000000..a2fd2255f
--- /dev/null
+++ b/src/Dapr.AI/DaprAIClient.cs
@@ -0,0 +1,34 @@
+// ------------------------------------------------------------------------
+// Copyright 2024 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.AI.Conversation;
+
+namespace Dapr.AI;
+
+///
+/// The base implementation of a Dapr AI client.
+///
+public abstract class DaprAIClient
+{
+ ///
+ /// Sends various inputs to the large language model via the Conversational building block on the Dapr sidecar.
+ ///
+ /// The name of the Dapr conversation component.
+ /// The input values to send.
+ /// Optional options used to configure the conversation.
+ /// Cancellation token.
+ /// The response(s) provided by the LLM provider.
+ public abstract Task ConverseAsync(string daprConversationComponentName,
+ IReadOnlyList inputs, ConversationOptions? options = null,
+ CancellationToken cancellationToken = default);
+}
diff --git a/src/Dapr.AI/Extensions/IDaprAiServiceBuilder.cs b/src/Dapr.AI/Extensions/IDaprAiServiceBuilder.cs
new file mode 100644
index 000000000..8a0a80c2c
--- /dev/null
+++ b/src/Dapr.AI/Extensions/IDaprAiServiceBuilder.cs
@@ -0,0 +1,27 @@
+// ------------------------------------------------------------------------
+// Copyright 2024 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.AI.Extensions;
+
+///
+/// Responsible for registering Dapr AI service functionality.
+///
+public interface IDaprAiServiceBuilder
+{
+ ///
+ /// The registered services on the builder.
+ ///
+ public IServiceCollection Services { get; }
+}
diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs
index c70aef77b..bd0bd1d01 100644
--- a/src/Dapr.Client/DaprClientGrpc.cs
+++ b/src/Dapr.Client/DaprClientGrpc.cs
@@ -11,2257 +11,2258 @@
// limitations under the License.
// ------------------------------------------------------------------------
-namespace Dapr.Client
+using Dapr.Common.Extensions;
+
+namespace Dapr.Client;
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Json;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Google.Protobuf;
+using Google.Protobuf.WellKnownTypes;
+using Grpc.Core;
+using Grpc.Net.Client;
+using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
+
+///
+/// A client for interacting with the Dapr endpoints.
+///
+internal class DaprClientGrpc : DaprClient
{
- using System;
- using System.Buffers;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Net.Http;
- using System.Net.Http.Json;
- using System.Runtime.CompilerServices;
- using System.Runtime.InteropServices;
- using System.Text.Json;
- using System.Threading;
- using System.Threading.Tasks;
- using Google.Protobuf;
- using Google.Protobuf.WellKnownTypes;
- using Grpc.Core;
- using Grpc.Net.Client;
- using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
+ private const string AppIdKey = "appId";
+ private const string MethodNameKey = "methodName";
- ///
- /// A client for interacting with the Dapr endpoints.
- ///
- internal class DaprClientGrpc : DaprClient
- {
- private const string AppIdKey = "appId";
- private const string MethodNameKey = "methodName";
+ private readonly Uri httpEndpoint;
+ private readonly HttpClient httpClient;
- private readonly Uri httpEndpoint;
- private readonly HttpClient httpClient;
+ private readonly JsonSerializerOptions jsonSerializerOptions;
- private readonly JsonSerializerOptions jsonSerializerOptions;
+ private readonly GrpcChannel channel;
+ private readonly Autogenerated.Dapr.DaprClient client;
+ private readonly KeyValuePair? apiTokenHeader;
- private readonly GrpcChannel channel;
- private readonly Autogenerated.Dapr.DaprClient client;
- private readonly KeyValuePair? apiTokenHeader;
+ // property exposed for testing purposes
+ internal Autogenerated.Dapr.DaprClient Client => client;
- // property exposed for testing purposes
- internal Autogenerated.Dapr.DaprClient Client => client;
+ public override JsonSerializerOptions JsonSerializerOptions => jsonSerializerOptions;
- public override JsonSerializerOptions JsonSerializerOptions => jsonSerializerOptions;
+ internal DaprClientGrpc(
+ GrpcChannel channel,
+ Autogenerated.Dapr.DaprClient inner,
+ HttpClient httpClient,
+ Uri httpEndpoint,
+ JsonSerializerOptions jsonSerializerOptions,
+ KeyValuePair? apiTokenHeader)
+ {
+ this.channel = channel;
+ this.client = inner;
+ this.httpClient = httpClient;
+ this.httpEndpoint = httpEndpoint;
+ this.jsonSerializerOptions = jsonSerializerOptions;
+ this.apiTokenHeader = apiTokenHeader;
+
+ this.httpClient.DefaultRequestHeaders.UserAgent.Add(UserAgent());
+ }
- internal DaprClientGrpc(
- GrpcChannel channel,
- Autogenerated.Dapr.DaprClient inner,
- HttpClient httpClient,
- Uri httpEndpoint,
- JsonSerializerOptions jsonSerializerOptions,
- KeyValuePair? apiTokenHeader)
- {
- this.channel = channel;
- this.client = inner;
- this.httpClient = httpClient;
- this.httpEndpoint = httpEndpoint;
- this.jsonSerializerOptions = jsonSerializerOptions;
- this.apiTokenHeader = apiTokenHeader;
+ #region Publish Apis
+ ///
+ public override Task PublishEventAsync(
+ string pubsubName,
+ string topicName,
+ TData data,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName));
+ ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName));
+ ArgumentVerifier.ThrowIfNull(data, nameof(data));
- this.httpClient.DefaultRequestHeaders.UserAgent.Add(UserAgent());
- }
+ var content = TypeConverters.ToJsonByteString(data, this.JsonSerializerOptions);
+ return MakePublishRequest(pubsubName, topicName, content, null, data is CloudEvent ? Constants.ContentTypeCloudEvent : null, cancellationToken);
+ }
- #region Publish Apis
- ///
- public override Task PublishEventAsync(
- string pubsubName,
- string topicName,
- TData data,
- CancellationToken cancellationToken = default)
- {
- ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName));
- ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName));
- ArgumentVerifier.ThrowIfNull(data, nameof(data));
+ public override Task PublishEventAsync(
+ string pubsubName,
+ string topicName,
+ TData data,
+ Dictionary metadata,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName));
+ ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName));
+ ArgumentVerifier.ThrowIfNull(data, nameof(data));
+ ArgumentVerifier.ThrowIfNull(metadata, nameof(metadata));
- var content = TypeConverters.ToJsonByteString(data, this.JsonSerializerOptions);
- return MakePublishRequest(pubsubName, topicName, content, null, data is CloudEvent ? Constants.ContentTypeCloudEvent : null, cancellationToken);
- }
+ var content = TypeConverters.ToJsonByteString(data, this.JsonSerializerOptions);
+ return MakePublishRequest(pubsubName, topicName, content, metadata, data is CloudEvent ? Constants.ContentTypeCloudEvent : null, cancellationToken);
+ }
- public override Task PublishEventAsync(
- string pubsubName,
- string topicName,
- TData data,
- Dictionary metadata,
- CancellationToken cancellationToken = default)
- {
- ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName));
- ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName));
- ArgumentVerifier.ThrowIfNull(data, nameof(data));
- ArgumentVerifier.ThrowIfNull(metadata, nameof(metadata));
+ ///
+ public override Task PublishEventAsync(
+ string pubsubName,
+ string topicName,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName));
+ ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName));
+ return MakePublishRequest(pubsubName, topicName, null, null, null, cancellationToken);
+ }
- var content = TypeConverters.ToJsonByteString(data, this.JsonSerializerOptions);
- return MakePublishRequest(pubsubName, topicName, content, metadata, data is CloudEvent ? Constants.ContentTypeCloudEvent : null, cancellationToken);
- }
+ public override Task PublishEventAsync(
+ string pubsubName,
+ string topicName,
+ Dictionary metadata,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName));
+ ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName));
+ ArgumentVerifier.ThrowIfNull(metadata, nameof(metadata));
+ return MakePublishRequest(pubsubName, topicName, null, metadata, null, cancellationToken);
+ }
- ///
- public override Task PublishEventAsync(
- string pubsubName,
- string topicName,
- CancellationToken cancellationToken = default)
- {
- ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName));
- ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName));
- return MakePublishRequest(pubsubName, topicName, null, null, null, cancellationToken);
- }
+ public override Task PublishByteEventAsync(
+ string pubsubName,
+ string topicName,
+ ReadOnlyMemory data,
+ string dataContentType = Constants.ContentTypeApplicationJson,
+ Dictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName));
+ ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName));
+ return MakePublishRequest(pubsubName, topicName, ByteString.CopyFrom(data.Span), metadata, dataContentType, cancellationToken);
+ }
- public override Task PublishEventAsync(
- string pubsubName,
- string topicName,
- Dictionary metadata,
- CancellationToken cancellationToken = default)
+ private async Task MakePublishRequest(
+ string pubsubName,
+ string topicName,
+ ByteString content,
+ Dictionary metadata,
+ string dataContentType,
+ CancellationToken cancellationToken)
+ {
+ var envelope = new Autogenerated.PublishEventRequest()
{
- ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName));
- ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName));
- ArgumentVerifier.ThrowIfNull(metadata, nameof(metadata));
- return MakePublishRequest(pubsubName, topicName, null, metadata, null, cancellationToken);
- }
+ PubsubName = pubsubName,
+ Topic = topicName,
+ };
- public override Task PublishByteEventAsync(
- string pubsubName,
- string topicName,
- ReadOnlyMemory data,
- string dataContentType = Constants.ContentTypeApplicationJson,
- Dictionary metadata = default,
- CancellationToken cancellationToken = default)
+ if (content != null)
{
- ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName));
- ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName));
- return MakePublishRequest(pubsubName, topicName, ByteString.CopyFrom(data.Span), metadata, dataContentType, cancellationToken);
+ envelope.Data = content;
+ envelope.DataContentType = dataContentType ?? Constants.ContentTypeApplicationJson;
}
- private async Task MakePublishRequest(
- string pubsubName,
- string topicName,
- ByteString content,
- Dictionary metadata,
- string dataContentType,
- CancellationToken cancellationToken)
+ if (metadata != null)
{
- var envelope = new Autogenerated.PublishEventRequest()
- {
- PubsubName = pubsubName,
- Topic = topicName,
- };
-
- if (content != null)
- {
- envelope.Data = content;
- envelope.DataContentType = dataContentType ?? Constants.ContentTypeApplicationJson;
- }
-
- if (metadata != null)
+ foreach (var kvp in metadata)
{
- foreach (var kvp in metadata)
- {
- envelope.Metadata.Add(kvp.Key, kvp.Value);
- }
+ envelope.Metadata.Add(kvp.Key, kvp.Value);
}
+ }
- var options = CreateCallOptions(headers: null, cancellationToken);
+ var options = CreateCallOptions(headers: null, cancellationToken);
- try
- {
- await client.PublishEventAsync(envelope, options);
- }
- catch (RpcException ex)
- {
- throw new DaprException("Publish operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
- }
+ try
+ {
+ await client.PublishEventAsync(envelope, options);
}
-
- ///
- public override Task> BulkPublishEventAsync(
- string pubsubName,
- string topicName,
- IReadOnlyList events,
- Dictionary metadata = default,
- CancellationToken cancellationToken = default)
+ catch (RpcException ex)
{
- ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName));
- ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName));
- ArgumentVerifier.ThrowIfNull(events, nameof(events));
- return MakeBulkPublishRequest(pubsubName, topicName, events, metadata, cancellationToken);
+ throw new DaprException("Publish operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
}
+ }
+
+ ///
+ public override Task> BulkPublishEventAsync(
+ string pubsubName,
+ string topicName,
+ IReadOnlyList events,
+ Dictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(pubsubName, nameof(pubsubName));
+ ArgumentVerifier.ThrowIfNullOrEmpty(topicName, nameof(topicName));
+ ArgumentVerifier.ThrowIfNull(events, nameof(events));
+ return MakeBulkPublishRequest(pubsubName, topicName, events, metadata, cancellationToken);
+ }
- private async Task> MakeBulkPublishRequest(
- string pubsubName,
- string topicName,
- IReadOnlyList events,
- Dictionary metadata,
- CancellationToken cancellationToken)
- {
- var envelope = new Autogenerated.BulkPublishRequest()
- {
- PubsubName = pubsubName,
- Topic = topicName,
- };
+ private async Task> MakeBulkPublishRequest(
+ string pubsubName,
+ string topicName,
+ IReadOnlyList events,
+ Dictionary metadata,
+ CancellationToken cancellationToken)
+ {
+ var envelope = new Autogenerated.BulkPublishRequest()
+ {
+ PubsubName = pubsubName,
+ Topic = topicName,
+ };
- Dictionary> entryMap = new Dictionary>();
+ Dictionary> entryMap = new Dictionary>();
- for (int counter = 0; counter < events.Count; counter++)
+ for (int counter = 0; counter < events.Count; counter++)
+ {
+ var entry = new Autogenerated.BulkPublishRequestEntry()
{
- var entry = new Autogenerated.BulkPublishRequestEntry()
- {
- EntryId = counter.ToString(),
- Event = TypeConverters.ToJsonByteString(events[counter], this.jsonSerializerOptions),
- ContentType = events[counter] is CloudEvent ? Constants.ContentTypeCloudEvent : Constants.ContentTypeApplicationJson,
- Metadata = {},
- };
- envelope.Entries.Add(entry);
- entryMap.Add(counter.ToString(), new BulkPublishEntry(
- entry.EntryId, events[counter], entry.ContentType, entry.Metadata));
- }
+ EntryId = counter.ToString(),
+ Event = TypeConverters.ToJsonByteString(events[counter], this.jsonSerializerOptions),
+ ContentType = events[counter] is CloudEvent ? Constants.ContentTypeCloudEvent : Constants.ContentTypeApplicationJson,
+ Metadata = {},
+ };
+ envelope.Entries.Add(entry);
+ entryMap.Add(counter.ToString(), new BulkPublishEntry(
+ entry.EntryId, events[counter], entry.ContentType, entry.Metadata));
+ }
- if (metadata != null)
+ if (metadata != null)
+ {
+ foreach (var kvp in metadata)
{
- foreach (var kvp in metadata)
- {
- envelope.Metadata.Add(kvp.Key, kvp.Value);
- }
+ envelope.Metadata.Add(kvp.Key, kvp.Value);
}
+ }
- var options = CreateCallOptions(headers: null, cancellationToken);
+ var options = CreateCallOptions(headers: null, cancellationToken);
- try
- {
- var response = await client.BulkPublishEventAlpha1Async(envelope, options);
+ try
+ {
+ var response = await client.BulkPublishEventAlpha1Async(envelope, options);
- List> failedEntries = new List>();
+ List> failedEntries = new List>();
- foreach (var entry in response.FailedEntries)
- {
- BulkPublishResponseFailedEntry domainEntry = new BulkPublishResponseFailedEntry(
- entryMap[entry.EntryId], entry.Error);
- failedEntries.Add(domainEntry);
- }
-
- var bulkPublishResponse = new BulkPublishResponse(failedEntries);
-
- return bulkPublishResponse;
- }
- catch (RpcException ex)
+ foreach (var entry in response.FailedEntries)
{
- throw new DaprException("Bulk Publish operation failed: the Dapr endpoint indicated a " +
- "failure. See InnerException for details.", ex);
+ BulkPublishResponseFailedEntry domainEntry = new BulkPublishResponseFailedEntry(
+ entryMap[entry.EntryId], entry.Error);
+ failedEntries.Add(domainEntry);
}
+
+ var bulkPublishResponse = new BulkPublishResponse(failedEntries);
+
+ return bulkPublishResponse;
+ }
+ catch (RpcException ex)
+ {
+ throw new DaprException("Bulk Publish operation failed: the Dapr endpoint indicated a " +
+ "failure. See InnerException for details.", ex);
}
- #endregion
+ }
+ #endregion
- #region InvokeBinding Apis
+ #region InvokeBinding Apis
- ///
- public override async Task InvokeBindingAsync(
- string bindingName,
- string operation,
- TRequest data,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
- {
- ArgumentVerifier.ThrowIfNullOrEmpty(bindingName, nameof(bindingName));
- ArgumentVerifier.ThrowIfNullOrEmpty(operation, nameof(operation));
+ ///
+ public override async Task InvokeBindingAsync(
+ string bindingName,
+ string operation,
+ TRequest data,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(bindingName, nameof(bindingName));
+ ArgumentVerifier.ThrowIfNullOrEmpty(operation, nameof(operation));
+
+ var bytes = TypeConverters.ToJsonByteString(data, this.jsonSerializerOptions);
+ _ = await MakeInvokeBindingRequestAsync(bindingName, operation, bytes, metadata, cancellationToken);
+ }
+
+ ///
+ public override async Task InvokeBindingAsync(
+ string bindingName,
+ string operation,
+ TRequest data,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(bindingName, nameof(bindingName));
+ ArgumentVerifier.ThrowIfNullOrEmpty(operation, nameof(operation));
- var bytes = TypeConverters.ToJsonByteString(data, this.jsonSerializerOptions);
- _ = await MakeInvokeBindingRequestAsync(bindingName, operation, bytes, metadata, cancellationToken);
+ var bytes = TypeConverters.ToJsonByteString(data, this.jsonSerializerOptions);
+ var response = await MakeInvokeBindingRequestAsync(bindingName, operation, bytes, metadata, cancellationToken);
+
+ try
+ {
+ return TypeConverters.FromJsonByteString(response.Data, this.JsonSerializerOptions);
}
+ catch (JsonException ex)
+ {
+ throw new DaprException("Binding operation failed: the response payload could not be deserialized. See InnerException for details.", ex);
+ }
+ }
- ///
- public override async Task InvokeBindingAsync(
- string bindingName,
- string operation,
- TRequest data,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
+ public override async Task InvokeBindingAsync(BindingRequest request, CancellationToken cancellationToken = default)
+ {
+ var bytes = ByteString.CopyFrom(request.Data.Span);
+ var response = await this.MakeInvokeBindingRequestAsync(request.BindingName, request.Operation, bytes, request.Metadata, cancellationToken);
+ return new BindingResponse(request, response.Data.Memory, response.Metadata);
+ }
+
+ private async Task MakeInvokeBindingRequestAsync(
+ string name,
+ string operation,
+ ByteString data,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ var envelope = new Autogenerated.InvokeBindingRequest()
{
- ArgumentVerifier.ThrowIfNullOrEmpty(bindingName, nameof(bindingName));
- ArgumentVerifier.ThrowIfNullOrEmpty(operation, nameof(operation));
+ Name = name,
+ Operation = operation
+ };
- var bytes = TypeConverters.ToJsonByteString(data, this.jsonSerializerOptions);
- var response = await MakeInvokeBindingRequestAsync(bindingName, operation, bytes, metadata, cancellationToken);
+ if (data != null)
+ {
+ envelope.Data = data;
+ }
- try
- {
- return TypeConverters.FromJsonByteString(response.Data, this.JsonSerializerOptions);
- }
- catch (JsonException ex)
+ if (metadata != null)
+ {
+ foreach (var kvp in metadata)
{
- throw new DaprException("Binding operation failed: the response payload could not be deserialized. See InnerException for details.", ex);
+ envelope.Metadata.Add(kvp.Key, kvp.Value);
}
}
- public override async Task InvokeBindingAsync(BindingRequest request, CancellationToken cancellationToken = default)
+ var options = CreateCallOptions(headers: null, cancellationToken);
+ try
{
- var bytes = ByteString.CopyFrom(request.Data.Span);
- var response = await this.MakeInvokeBindingRequestAsync(request.BindingName, request.Operation, bytes, request.Metadata, cancellationToken);
- return new BindingResponse(request, response.Data.Memory, response.Metadata);
+ return await client.InvokeBindingAsync(envelope, options);
}
-
- private async Task MakeInvokeBindingRequestAsync(
- string name,
- string operation,
- ByteString data,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
+ catch (RpcException ex)
{
- var envelope = new Autogenerated.InvokeBindingRequest()
- {
- Name = name,
- Operation = operation
- };
+ throw new DaprException("Binding operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
+ }
+ }
+ #endregion
- if (data != null)
- {
- envelope.Data = data;
- }
+ #region InvokeMethod Apis
- if (metadata != null)
- {
- foreach (var kvp in metadata)
- {
- envelope.Metadata.Add(kvp.Key, kvp.Value);
- }
- }
+ ///
+ /// Creates an that can be used to perform service invocation for the
+ /// application identified by and invokes the method specified by
+ /// with the HTTP method specified by .
+ ///
+ /// The to use for the invocation request.
+ /// The Dapr application id to invoke the method on.
+ /// The name of the method to invoke.
+ /// An for use with SendInvokeMethodRequestAsync.
+ public override HttpRequestMessage CreateInvokeMethodRequest(HttpMethod httpMethod, string appId, string methodName)
+ {
+ return CreateInvokeMethodRequest(httpMethod, appId, methodName, new List>());
+ }
- var options = CreateCallOptions(headers: null, cancellationToken);
- try
- {
- return await client.InvokeBindingAsync(envelope, options);
- }
- catch (RpcException ex)
- {
- throw new DaprException("Binding operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
- }
- }
- #endregion
-
- #region InvokeMethod Apis
-
- ///
- /// Creates an that can be used to perform service invocation for the
- /// application identified by and invokes the method specified by
- /// with the HTTP method specified by .
- ///
- /// The to use for the invocation request.
- /// The Dapr application id to invoke the method on.
- /// The name of the method to invoke.
- /// An for use with SendInvokeMethodRequestAsync.
- public override HttpRequestMessage CreateInvokeMethodRequest(HttpMethod httpMethod, string appId, string methodName)
- {
- return CreateInvokeMethodRequest(httpMethod, appId, methodName, new List>());
- }
-
- ///
- /// Creates an that can be used to perform service invocation for the
- /// application identified by and invokes the method specified by
- /// with the HTTP method specified by .
- ///
- /// The to use for the invocation request.
- /// The Dapr application id to invoke the method on.
- /// The name of the method to invoke.
- /// A collection of key/value pairs to populate the query string from.
- /// An for use with SendInvokeMethodRequestAsync.
- public override HttpRequestMessage CreateInvokeMethodRequest(HttpMethod httpMethod, string appId, string methodName,
- IReadOnlyCollection> queryStringParameters)
- {
- ArgumentVerifier.ThrowIfNull(httpMethod, nameof(httpMethod));
- ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId));
- ArgumentVerifier.ThrowIfNull(methodName, nameof(methodName));
-
- // Note about this, it's possible to construct invalid stuff using path navigation operators
- // like `../..`. But the principle of garbage in -> garbage out holds.
- //
- // This approach avoids some common pitfalls that could lead to undesired encoding.
- var path = $"/v1.0/invoke/{appId}/method/{methodName.TrimStart('/')}";
- var requestUri = new Uri(this.httpEndpoint, path).AddQueryParameters(queryStringParameters);
- var request = new HttpRequestMessage(httpMethod, requestUri);
+ ///
+ /// Creates an that can be used to perform service invocation for the
+ /// application identified by and invokes the method specified by
+ /// with the HTTP method specified by .
+ ///
+ /// The to use for the invocation request.
+ /// The Dapr application id to invoke the method on.
+ /// The name of the method to invoke.
+ /// A collection of key/value pairs to populate the query string from.
+ /// An for use with SendInvokeMethodRequestAsync.
+ public override HttpRequestMessage CreateInvokeMethodRequest(HttpMethod httpMethod, string appId, string methodName,
+ IReadOnlyCollection> queryStringParameters)
+ {
+ ArgumentVerifier.ThrowIfNull(httpMethod, nameof(httpMethod));
+ ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId));
+ ArgumentVerifier.ThrowIfNull(methodName, nameof(methodName));
+
+ // Note about this, it's possible to construct invalid stuff using path navigation operators
+ // like `../..`. But the principle of garbage in -> garbage out holds.
+ //
+ // This approach avoids some common pitfalls that could lead to undesired encoding.
+ var path = $"/v1.0/invoke/{appId}/method/{methodName.TrimStart('/')}";
+ var requestUri = new Uri(this.httpEndpoint, path).AddQueryParameters(queryStringParameters);
+ var request = new HttpRequestMessage(httpMethod, requestUri);
- request.Options.Set(new HttpRequestOptionsKey(AppIdKey), appId);
- request.Options.Set(new HttpRequestOptionsKey(MethodNameKey), methodName);
-
- if (this.apiTokenHeader is not null)
- {
- request.Headers.TryAddWithoutValidation(this.apiTokenHeader.Value.Key, this.apiTokenHeader.Value.Value);
- }
+ request.Options.Set(new HttpRequestOptionsKey(AppIdKey), appId);
+ request.Options.Set(new HttpRequestOptionsKey(MethodNameKey), methodName);
- return request;
+ if (this.apiTokenHeader is not null)
+ {
+ request.Headers.TryAddWithoutValidation(this.apiTokenHeader.Value.Key, this.apiTokenHeader.Value.Value);
}
- ///
- /// Creates an that can be used to perform service invocation for the
- /// application identified by and invokes the method specified by
- /// with the HTTP method specified by and a JSON serialized request body specified by
- /// .
- ///
- /// The type of the data that will be JSON serialized and provided as the request body.
- /// The to use for the invocation request.
- /// The Dapr application id to invoke the method on.
- /// The name of the method to invoke.
- /// The data that will be JSON serialized and provided as the request body.
- /// A collection of key/value pairs to populate the query string from.
- /// An for use with SendInvokeMethodRequestAsync.
- public override HttpRequestMessage CreateInvokeMethodRequest(HttpMethod httpMethod, string appId, string methodName,
- IReadOnlyCollection> queryStringParameters, TRequest data)
- {
- ArgumentVerifier.ThrowIfNull(httpMethod, nameof(httpMethod));
- ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId));
- ArgumentVerifier.ThrowIfNull(methodName, nameof(methodName));
+ return request;
+ }
- var request = CreateInvokeMethodRequest(httpMethod, appId, methodName, queryStringParameters);
- request.Content = JsonContent.Create(data, options: this.JsonSerializerOptions);
- return request;
- }
+ ///
+ /// Creates an that can be used to perform service invocation for the
+ /// application identified by and invokes the method specified by
+ /// with the HTTP method specified by and a JSON serialized request body specified by
+ /// .
+ ///
+ /// The type of the data that will be JSON serialized and provided as the request body.
+ /// The to use for the invocation request.
+ /// The Dapr application id to invoke the method on.
+ /// The name of the method to invoke.
+ /// The data that will be JSON serialized and provided as the request body.
+ /// A collection of key/value pairs to populate the query string from.
+ /// An for use with SendInvokeMethodRequestAsync.
+ public override HttpRequestMessage CreateInvokeMethodRequest(HttpMethod httpMethod, string appId, string methodName,
+ IReadOnlyCollection> queryStringParameters, TRequest data)
+ {
+ ArgumentVerifier.ThrowIfNull(httpMethod, nameof(httpMethod));
+ ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId));
+ ArgumentVerifier.ThrowIfNull(methodName, nameof(methodName));
- public override async Task InvokeMethodWithResponseAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
+ var request = CreateInvokeMethodRequest(httpMethod, appId, methodName, queryStringParameters);
+ request.Content = JsonContent.Create(data, options: this.JsonSerializerOptions);
+ return request;
+ }
+
+ public override async Task InvokeMethodWithResponseAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNull(request, nameof(request));
+
+ if (!this.httpEndpoint.IsBaseOf(request.RequestUri))
{
- ArgumentVerifier.ThrowIfNull(request, nameof(request));
+ throw new InvalidOperationException("The provided request URI is not a Dapr service invocation URI.");
+ }
- if (!this.httpEndpoint.IsBaseOf(request.RequestUri))
- {
- throw new InvalidOperationException("The provided request URI is not a Dapr service invocation URI.");
- }
+ // Note: we intentionally DO NOT validate the status code here.
+ // This method allows you to 'invoke' without exceptions on non-2xx.
+ try
+ {
+ return await this.httpClient.SendAsync(request, cancellationToken);
+ }
+ catch (HttpRequestException ex)
+ {
+ // Our code path for creating requests places these keys in the request properties. We don't want to fail
+ // if they are not present.
+ request.Options.TryGetValue(new HttpRequestOptionsKey(AppIdKey), out var appId);
+ request.Options.TryGetValue(new HttpRequestOptionsKey(MethodNameKey), out var methodName);
- // Note: we intentionally DO NOT validate the status code here.
- // This method allows you to 'invoke' without exceptions on non-2xx.
- try
- {
- return await this.httpClient.SendAsync(request, cancellationToken);
- }
- catch (HttpRequestException ex)
- {
- // Our code path for creating requests places these keys in the request properties. We don't want to fail
- // if they are not present.
- request.Options.TryGetValue(new HttpRequestOptionsKey(AppIdKey), out var appId);
- request.Options.TryGetValue(new HttpRequestOptionsKey(MethodNameKey), out var methodName);
-
- throw new InvocationException(
- appId: appId as string,
- methodName: methodName as string,
- innerException: ex,
- response: null);
- }
+ throw new InvocationException(
+ appId: appId as string,
+ methodName: methodName as string,
+ innerException: ex,
+ response: null);
}
+ }
- ///
- ///
- /// Creates an that can be used to perform Dapr service invocation using
- /// objects.
- ///
- ///
- /// The client will read the property, and
- /// interpret the hostname as the destination app-id. The
- /// property will be replaced with a new URI with the authority section replaced by the instance's value
- /// and the path portion of the URI rewritten to follow the format of a Dapr service invocation request.
- ///
- ///
- ///
- /// An optional app-id. If specified, the app-id will be configured as the value of
- /// so that relative URIs can be used. It is mandatory to set this parameter if your app-id contains at least one upper letter.
- /// If some requests use absolute URL with an app-id which contains at least one upper letter, it will not work, the workaround is to create one HttpClient for each app-id with the app-ip parameter set.
- ///
- /// An that can be used to perform service invocation requests.
- ///
- ///
+ ///
+ ///
+ /// Creates an that can be used to perform Dapr service invocation using
+ /// objects.
+ ///
+ ///
+ /// The client will read the property, and
+ /// interpret the hostname as the destination app-id. The
+ /// property will be replaced with a new URI with the authority section replaced by the instance's value
+ /// and the path portion of the URI rewritten to follow the format of a Dapr service invocation request.
+ ///
+ ///
+ ///
+ /// An optional app-id. If specified, the app-id will be configured as the value of
+ /// so that relative URIs can be used. It is mandatory to set this parameter if your app-id contains at least one upper letter.
+ /// If some requests use absolute URL with an app-id which contains at least one upper letter, it will not work, the workaround is to create one HttpClient for each app-id with the app-ip parameter set.
+ ///
+ /// An that can be used to perform service invocation requests.
+ ///
+ ///
#nullable enable
- public override HttpClient CreateInvokableHttpClient(string? appId = null) =>
- DaprClient.CreateInvokeHttpClient(appId, this.httpEndpoint?.AbsoluteUri, this.apiTokenHeader?.Value);
- #nullable disable
+ public override HttpClient CreateInvokableHttpClient(string? appId = null) =>
+ DaprClient.CreateInvokeHttpClient(appId, this.httpEndpoint?.AbsoluteUri, this.apiTokenHeader?.Value);
+#nullable disable
- public async override Task InvokeMethodAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
+ public async override Task InvokeMethodAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNull(request, nameof(request));
+
+ var response = await InvokeMethodWithResponseAsync(request, cancellationToken);
+ try
+ {
+ response.EnsureSuccessStatusCode();
+ }
+ catch (HttpRequestException ex)
{
- ArgumentVerifier.ThrowIfNull(request, nameof(request));
+ // Our code path for creating requests places these keys in the request properties. We don't want to fail
+ // if they are not present.
+ request.Options.TryGetValue(new HttpRequestOptionsKey(AppIdKey), out var appId);
+ request.Options.TryGetValue(new HttpRequestOptionsKey(MethodNameKey), out var methodName);
- var response = await InvokeMethodWithResponseAsync(request, cancellationToken);
- try
- {
- response.EnsureSuccessStatusCode();
- }
- catch (HttpRequestException ex)
- {
- // Our code path for creating requests places these keys in the request properties. We don't want to fail
- // if they are not present.
- request.Options.TryGetValue(new HttpRequestOptionsKey(AppIdKey), out var appId);
- request.Options.TryGetValue(new HttpRequestOptionsKey(MethodNameKey), out var methodName);
-
- throw new InvocationException(
- appId: appId as string,
- methodName: methodName as string,
- innerException: ex,
- response: response);
- }
+ throw new InvocationException(
+ appId: appId as string,
+ methodName: methodName as string,
+ innerException: ex,
+ response: response);
}
+ }
- public async override Task InvokeMethodAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
- {
- ArgumentVerifier.ThrowIfNull(request, nameof(request));
+ public async override Task InvokeMethodAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNull(request, nameof(request));
- var response = await InvokeMethodWithResponseAsync(request, cancellationToken);
- try
- {
- response.EnsureSuccessStatusCode();
- }
- catch (HttpRequestException ex)
- {
- // Our code path for creating requests places these keys in the request properties. We don't want to fail
- // if they are not present.
- request.Options.TryGetValue(new HttpRequestOptionsKey(AppIdKey), out var appId);
- request.Options.TryGetValue(new HttpRequestOptionsKey(MethodNameKey), out var methodName);
-
- throw new InvocationException(
- appId: appId as string,
- methodName: methodName as string,
- innerException: ex,
- response: response);
- }
+ var response = await InvokeMethodWithResponseAsync(request, cancellationToken);
+ try
+ {
+ response.EnsureSuccessStatusCode();
+ }
+ catch (HttpRequestException ex)
+ {
+ // Our code path for creating requests places these keys in the request properties. We don't want to fail
+ // if they are not present.
+ request.Options.TryGetValue(new HttpRequestOptionsKey(AppIdKey), out var appId);
+ request.Options.TryGetValue(new HttpRequestOptionsKey(MethodNameKey), out var methodName);
- try
- {
- return await response.Content.ReadFromJsonAsync(this.jsonSerializerOptions, cancellationToken);
- }
- catch (HttpRequestException ex)
- {
- // Our code path for creating requests places these keys in the request properties. We don't want to fail
- // if they are not present.
- request.Options.TryGetValue(new HttpRequestOptionsKey(AppIdKey), out var appId);
- request.Options.TryGetValue(new HttpRequestOptionsKey(MethodNameKey), out var methodName);
-
- throw new InvocationException(
- appId: appId as string,
- methodName: methodName as string,
- innerException: ex,
- response: response);
- }
- catch (JsonException ex)
- {
- request.Options.TryGetValue(new HttpRequestOptionsKey(AppIdKey), out var appId);
- request.Options.TryGetValue(new HttpRequestOptionsKey(MethodNameKey), out var methodName);
-
- throw new InvocationException(
- appId: appId as string,
- methodName: methodName as string,
- innerException: ex,
- response: response);
- }
+ throw new InvocationException(
+ appId: appId as string,
+ methodName: methodName as string,
+ innerException: ex,
+ response: response);
}
- public override async Task InvokeMethodGrpcAsync(string appId, string methodName, CancellationToken cancellationToken = default)
+ try
{
- ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId));
- ArgumentVerifier.ThrowIfNullOrEmpty(methodName, nameof(methodName));
-
- var envelope = new Autogenerated.InvokeServiceRequest()
- {
- Id = appId,
- Message = new Autogenerated.InvokeRequest()
- {
- Method = methodName,
- },
- };
+ return await response.Content.ReadFromJsonAsync(this.jsonSerializerOptions, cancellationToken);
+ }
+ catch (HttpRequestException ex)
+ {
+ // Our code path for creating requests places these keys in the request properties. We don't want to fail
+ // if they are not present.
+ request.Options.TryGetValue(new HttpRequestOptionsKey(AppIdKey), out var appId);
+ request.Options.TryGetValue(new HttpRequestOptionsKey(MethodNameKey), out var methodName);
- var options = CreateCallOptions(headers: null, cancellationToken);
+ throw new InvocationException(
+ appId: appId as string,
+ methodName: methodName as string,
+ innerException: ex,
+ response: response);
+ }
+ catch (JsonException ex)
+ {
+ request.Options.TryGetValue(new HttpRequestOptionsKey(AppIdKey), out var appId);
+ request.Options.TryGetValue(new HttpRequestOptionsKey(MethodNameKey), out var methodName);
- try
- {
- _ = await this.Client.InvokeServiceAsync(envelope, options);
- }
- catch (RpcException ex)
- {
- throw new InvocationException(appId, methodName, ex);
- }
+ throw new InvocationException(
+ appId: appId as string,
+ methodName: methodName as string,
+ innerException: ex,
+ response: response);
}
+ }
- public override async Task InvokeMethodGrpcAsync(string appId, string methodName, TRequest data, CancellationToken cancellationToken = default)
- {
- ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId));
- ArgumentVerifier.ThrowIfNullOrEmpty(methodName, nameof(methodName));
+ public override async Task InvokeMethodGrpcAsync(string appId, string methodName, CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId));
+ ArgumentVerifier.ThrowIfNullOrEmpty(methodName, nameof(methodName));
- var envelope = new Autogenerated.InvokeServiceRequest()
+ var envelope = new Autogenerated.InvokeServiceRequest()
+ {
+ Id = appId,
+ Message = new Autogenerated.InvokeRequest()
{
- Id = appId,
- Message = new Autogenerated.InvokeRequest()
- {
- Method = methodName,
- ContentType = Constants.ContentTypeApplicationGrpc,
- Data = Any.Pack(data),
- },
- };
+ Method = methodName,
+ },
+ };
- var options = CreateCallOptions(headers: null, cancellationToken);
+ var options = CreateCallOptions(headers: null, cancellationToken);
- try
- {
- _ = await this.Client.InvokeServiceAsync(envelope, options);
- }
- catch (RpcException ex)
- {
- throw new InvocationException(appId, methodName, ex);
- }
+ try
+ {
+ _ = await this.Client.InvokeServiceAsync(envelope, options);
}
-
- public override async Task InvokeMethodGrpcAsync(string appId, string methodName, CancellationToken cancellationToken = default)
+ catch (RpcException ex)
{
- ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId));
- ArgumentVerifier.ThrowIfNullOrEmpty(methodName, nameof(methodName));
+ throw new InvocationException(appId, methodName, ex);
+ }
+ }
- var envelope = new Autogenerated.InvokeServiceRequest()
+ public override async Task InvokeMethodGrpcAsync(string appId, string methodName, TRequest data, CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId));
+ ArgumentVerifier.ThrowIfNullOrEmpty(methodName, nameof(methodName));
+
+ var envelope = new Autogenerated.InvokeServiceRequest()
+ {
+ Id = appId,
+ Message = new Autogenerated.InvokeRequest()
{
- Id = appId,
- Message = new Autogenerated.InvokeRequest()
- {
- Method = methodName,
- },
- };
+ Method = methodName,
+ ContentType = Constants.ContentTypeApplicationGrpc,
+ Data = Any.Pack(data),
+ },
+ };
- var options = CreateCallOptions(headers: null, cancellationToken);
+ var options = CreateCallOptions(headers: null, cancellationToken);
- try
- {
- var response = await this.Client.InvokeServiceAsync(envelope, options);
- return response.Data.Unpack();
- }
- catch (RpcException ex)
- {
- throw new InvocationException(appId, methodName, ex);
- }
+ try
+ {
+ _ = await this.Client.InvokeServiceAsync(envelope, options);
}
-
- public override async Task InvokeMethodGrpcAsync(string appId, string methodName, TRequest data, CancellationToken cancellationToken = default)
+ catch (RpcException ex)
{
- ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId));
- ArgumentVerifier.ThrowIfNullOrEmpty(methodName, nameof(methodName));
+ throw new InvocationException(appId, methodName, ex);
+ }
+ }
+
+ public override async Task InvokeMethodGrpcAsync(string appId, string methodName, CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId));
+ ArgumentVerifier.ThrowIfNullOrEmpty(methodName, nameof(methodName));
- var envelope = new Autogenerated.InvokeServiceRequest()
+ var envelope = new Autogenerated.InvokeServiceRequest()
+ {
+ Id = appId,
+ Message = new Autogenerated.InvokeRequest()
{
- Id = appId,
- Message = new Autogenerated.InvokeRequest()
- {
- Method = methodName,
- ContentType = Constants.ContentTypeApplicationGrpc,
- Data = Any.Pack(data),
- },
- };
+ Method = methodName,
+ },
+ };
- var options = CreateCallOptions(headers: null, cancellationToken);
+ var options = CreateCallOptions(headers: null, cancellationToken);
- try
- {
- var response = await this.Client.InvokeServiceAsync(envelope, options);
- return response.Data.Unpack();
- }
- catch (RpcException ex)
- {
- throw new InvocationException(appId, methodName, ex);
- }
+ try
+ {
+ var response = await this.Client.InvokeServiceAsync(envelope, options);
+ return response.Data.Unpack();
}
+ catch (RpcException ex)
+ {
+ throw new InvocationException(appId, methodName, ex);
+ }
+ }
- #endregion
-
- #region State Apis
+ public override async Task InvokeMethodGrpcAsync(string appId, string methodName, TRequest data, CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(appId, nameof(appId));
+ ArgumentVerifier.ThrowIfNullOrEmpty(methodName, nameof(methodName));
- ///
- public override async Task> GetBulkStateAsync(string storeName, IReadOnlyList keys, int? parallelism, IReadOnlyDictionary metadata = default, CancellationToken cancellationToken = default)
+ var envelope = new Autogenerated.InvokeServiceRequest()
{
- var rawBulkState = await GetBulkStateRawAsync(storeName, keys, parallelism, metadata, cancellationToken);
-
- var bulkResponse = new List();
- foreach (var item in rawBulkState)
+ Id = appId,
+ Message = new Autogenerated.InvokeRequest()
{
- bulkResponse.Add(new BulkStateItem(item.Key, item.Value.ToStringUtf8(), item.Etag));
- }
+ Method = methodName,
+ ContentType = Constants.ContentTypeApplicationGrpc,
+ Data = Any.Pack(data),
+ },
+ };
- return bulkResponse;
- }
-
- ///
- public override async Task>> GetBulkStateAsync(
- string storeName,
- IReadOnlyList keys,
- int? parallelism,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
- {
- var rawBulkState = await GetBulkStateRawAsync(storeName, keys, parallelism, metadata, cancellationToken);
-
- var bulkResponse = new List>();
- foreach (var item in rawBulkState)
- {
- var deserializedValue = TypeConverters.FromJsonByteString(item.Value, this.JsonSerializerOptions);
- bulkResponse.Add(new BulkStateItem(item.Key, deserializedValue, item.Etag));
- }
+ var options = CreateCallOptions(headers: null, cancellationToken);
- return bulkResponse;
+ try
+ {
+ var response = await this.Client.InvokeServiceAsync(envelope, options);
+ return response.Data.Unpack();
}
-
- ///
- /// Retrieves the bulk state data, but rather than deserializing the values, leaves the specific handling
- /// to the public callers of this method to avoid duplicate deserialization.
- ///
- private async Task> GetBulkStateRawAsync(
- string storeName,
- IReadOnlyList keys,
- int? parallelism,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
+ catch (RpcException ex)
{
- ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
- if (keys.Count == 0)
- throw new ArgumentException("keys do not contain any elements");
-
- var envelope = new Autogenerated.GetBulkStateRequest()
- {
- StoreName = storeName,
- Parallelism = parallelism ?? default
- };
+ throw new InvocationException(appId, methodName, ex);
+ }
+ }
- if (metadata != null)
- {
- foreach (var kvp in metadata)
- {
- envelope.Metadata.Add(kvp.Key, kvp.Value);
- }
- }
+ #endregion
- envelope.Keys.AddRange(keys);
+ #region State Apis
- var options = CreateCallOptions(headers: null, cancellationToken);
- Autogenerated.GetBulkStateResponse response;
+ ///
+ public override async Task> GetBulkStateAsync(string storeName, IReadOnlyList keys, int? parallelism, IReadOnlyDictionary metadata = default, CancellationToken cancellationToken = default)
+ {
+ var rawBulkState = await GetBulkStateRawAsync(storeName, keys, parallelism, metadata, cancellationToken);
- try
- {
- response = await client.GetBulkStateAsync(envelope, options);
- }
- catch (RpcException ex)
- {
- throw new DaprException(
- "State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.",
- ex);
- }
+ var bulkResponse = new List();
+ foreach (var item in rawBulkState)
+ {
+ bulkResponse.Add(new BulkStateItem(item.Key, item.Value.ToStringUtf8(), item.Etag));
+ }
- var bulkResponse = new List<(string Key, string Etag, ByteString Value)>();
- foreach (var item in response.Items)
- {
- bulkResponse.Add((item.Key, item.Etag, item.Data));
- }
+ return bulkResponse;
+ }
+
+ ///
+ public override async Task>> GetBulkStateAsync(
+ string storeName,
+ IReadOnlyList keys,
+ int? parallelism,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ var rawBulkState = await GetBulkStateRawAsync(storeName, keys, parallelism, metadata, cancellationToken);
- return bulkResponse;
+ var bulkResponse = new List>();
+ foreach (var item in rawBulkState)
+ {
+ var deserializedValue = TypeConverters.FromJsonByteString(item.Value, this.JsonSerializerOptions);
+ bulkResponse.Add(new BulkStateItem(item.Key, deserializedValue, item.Etag));
}
-
- ///
- public override async Task GetStateAsync(
- string storeName,
- string key,
- ConsistencyMode? consistencyMode = default,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
- {
- ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
- ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key));
-
- var envelope = new Autogenerated.GetStateRequest()
- {
- StoreName = storeName,
- Key = key,
- };
- if (metadata != null)
- {
- foreach (var kvp in metadata)
- {
- envelope.Metadata.Add(kvp.Key, kvp.Value);
- }
- }
+ return bulkResponse;
+ }
- if (consistencyMode != null)
- {
- envelope.Consistency = GetStateConsistencyForConsistencyMode(consistencyMode.Value);
- }
+ ///
+ /// Retrieves the bulk state data, but rather than deserializing the values, leaves the specific handling
+ /// to the public callers of this method to avoid duplicate deserialization.
+ ///
+ private async Task> GetBulkStateRawAsync(
+ string storeName,
+ IReadOnlyList keys,
+ int? parallelism,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
+ if (keys.Count == 0)
+ throw new ArgumentException("keys do not contain any elements");
- var options = CreateCallOptions(headers: null, cancellationToken);
- Autogenerated.GetStateResponse response;
+ var envelope = new Autogenerated.GetBulkStateRequest()
+ {
+ StoreName = storeName,
+ Parallelism = parallelism ?? default
+ };
- try
- {
- response = await client.GetStateAsync(envelope, options);
- }
- catch (RpcException ex)
+ if (metadata != null)
+ {
+ foreach (var kvp in metadata)
{
- throw new DaprException("State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
+ envelope.Metadata.Add(kvp.Key, kvp.Value);
}
+ }
- try
- {
- return TypeConverters.FromJsonByteString(response.Data, this.JsonSerializerOptions);
- }
- catch (JsonException ex)
- {
- throw new DaprException("State operation failed: the state payload could not be deserialized. See InnerException for details.", ex);
- }
+ envelope.Keys.AddRange(keys);
+
+ var options = CreateCallOptions(headers: null, cancellationToken);
+ Autogenerated.GetBulkStateResponse response;
+
+ try
+ {
+ response = await client.GetBulkStateAsync(envelope, options);
+ }
+ catch (RpcException ex)
+ {
+ throw new DaprException(
+ "State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.",
+ ex);
}
- ///
- public override async Task SaveBulkStateAsync(string storeName, IReadOnlyList> items, CancellationToken cancellationToken = default)
+ var bulkResponse = new List<(string Key, string Etag, ByteString Value)>();
+ foreach (var item in response.Items)
{
- ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
+ bulkResponse.Add((item.Key, item.Etag, item.Data));
+ }
- if (items.Count == 0)
- {
- throw new ArgumentException("items do not contain any elements");
- }
+ return bulkResponse;
+ }
+
+ ///
+ public override async Task GetStateAsync(
+ string storeName,
+ string key,
+ ConsistencyMode? consistencyMode = default,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
+ ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key));
- var envelope = new Autogenerated.SaveStateRequest()
- {
- StoreName = storeName,
- };
+ var envelope = new Autogenerated.GetStateRequest()
+ {
+ StoreName = storeName,
+ Key = key,
+ };
- foreach (var item in items)
+ if (metadata != null)
+ {
+ foreach (var kvp in metadata)
{
- var stateItem = new Autogenerated.StateItem()
- {
- Key = item.Key,
- };
-
- if (item.ETag != null)
- {
- stateItem.Etag = new Autogenerated.Etag() { Value = item.ETag };
- }
+ envelope.Metadata.Add(kvp.Key, kvp.Value);
+ }
+ }
- if (item.Metadata != null)
- {
- foreach (var kvp in item.Metadata)
- {
- stateItem.Metadata.Add(kvp.Key, kvp.Value);
- }
- }
+ if (consistencyMode != null)
+ {
+ envelope.Consistency = GetStateConsistencyForConsistencyMode(consistencyMode.Value);
+ }
- if (item.StateOptions != null)
- {
- stateItem.Options = ToAutoGeneratedStateOptions(item.StateOptions);
- }
+ var options = CreateCallOptions(headers: null, cancellationToken);
+ Autogenerated.GetStateResponse response;
- if (item.Value != null)
- {
- stateItem.Value = TypeConverters.ToJsonByteString(item.Value, this.jsonSerializerOptions);
- }
+ try
+ {
+ response = await client.GetStateAsync(envelope, options);
+ }
+ catch (RpcException ex)
+ {
+ throw new DaprException("State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
+ }
- envelope.States.Add(stateItem);
- }
+ try
+ {
+ return TypeConverters.FromJsonByteString(response.Data, this.JsonSerializerOptions);
+ }
+ catch (JsonException ex)
+ {
+ throw new DaprException("State operation failed: the state payload could not be deserialized. See InnerException for details.", ex);
+ }
+ }
- try
- {
- await this.Client.SaveStateAsync(envelope, cancellationToken: cancellationToken);
- }
- catch (RpcException ex)
- {
- throw new DaprException("State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
- }
+ ///
+ public override async Task SaveBulkStateAsync(string storeName, IReadOnlyList> items, CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
+ if (items.Count == 0)
+ {
+ throw new ArgumentException("items do not contain any elements");
}
- ///
- public override async Task DeleteBulkStateAsync(string storeName, IReadOnlyList items, CancellationToken cancellationToken = default)
+ var envelope = new Autogenerated.SaveStateRequest()
+ {
+ StoreName = storeName,
+ };
+
+ foreach (var item in items)
{
- var envelope = new Autogenerated.DeleteBulkStateRequest()
+ var stateItem = new Autogenerated.StateItem()
{
- StoreName = storeName,
+ Key = item.Key,
};
- foreach (var item in items)
+ if (item.ETag != null)
{
- var stateItem = new Autogenerated.StateItem()
- {
- Key = item.Key,
- };
-
- if (item.ETag != null)
- {
- stateItem.Etag = new Autogenerated.Etag() { Value = item.ETag };
- }
-
- if (item.Metadata != null)
- {
- foreach (var kvp in item.Metadata)
- {
- stateItem.Metadata.Add(kvp.Key, kvp.Value);
- }
- }
+ stateItem.Etag = new Autogenerated.Etag() { Value = item.ETag };
+ }
- if (item.StateOptions != null)
+ if (item.Metadata != null)
+ {
+ foreach (var kvp in item.Metadata)
{
- stateItem.Options = ToAutoGeneratedStateOptions(item.StateOptions);
+ stateItem.Metadata.Add(kvp.Key, kvp.Value);
}
-
- envelope.States.Add(stateItem);
}
- try
+ if (item.StateOptions != null)
{
- await this.Client.DeleteBulkStateAsync(envelope, cancellationToken: cancellationToken);
+ stateItem.Options = ToAutoGeneratedStateOptions(item.StateOptions);
}
- catch (RpcException ex)
+
+ if (item.Value != null)
{
- throw new DaprException("State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
+ stateItem.Value = TypeConverters.ToJsonByteString(item.Value, this.jsonSerializerOptions);
}
+ envelope.States.Add(stateItem);
+ }
+
+ try
+ {
+ await this.Client.SaveStateAsync(envelope, cancellationToken: cancellationToken);
+ }
+ catch (RpcException ex)
+ {
+ throw new DaprException("State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
}
- ///
- public override async Task<(TValue value, string etag)> GetStateAndETagAsync(
- string storeName,
- string key,
- ConsistencyMode? consistencyMode = default,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
+ }
+
+ ///
+ public override async Task DeleteBulkStateAsync(string storeName, IReadOnlyList items, CancellationToken cancellationToken = default)
+ {
+ var envelope = new Autogenerated.DeleteBulkStateRequest()
{
- ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
- ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key));
+ StoreName = storeName,
+ };
- var envelope = new Autogenerated.GetStateRequest()
+ foreach (var item in items)
+ {
+ var stateItem = new Autogenerated.StateItem()
{
- StoreName = storeName,
- Key = key
+ Key = item.Key,
};
- if (metadata != null)
+ if (item.ETag != null)
{
- foreach (var kvp in metadata)
- {
- envelope.Metadata.Add(kvp.Key, kvp.Value);
- }
+ stateItem.Etag = new Autogenerated.Etag() { Value = item.ETag };
}
- if (consistencyMode != null)
+ if (item.Metadata != null)
{
- envelope.Consistency = GetStateConsistencyForConsistencyMode(consistencyMode.Value);
+ foreach (var kvp in item.Metadata)
+ {
+ stateItem.Metadata.Add(kvp.Key, kvp.Value);
+ }
}
- var options = CreateCallOptions(headers: null, cancellationToken);
- Autogenerated.GetStateResponse response;
-
- try
- {
- response = await client.GetStateAsync(envelope, options);
- }
- catch (RpcException ex)
+ if (item.StateOptions != null)
{
- throw new DaprException("State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
+ stateItem.Options = ToAutoGeneratedStateOptions(item.StateOptions);
}
- try
- {
- return (TypeConverters.FromJsonByteString(response.Data, this.JsonSerializerOptions), response.Etag);
- }
- catch (JsonException ex)
- {
- throw new DaprException("State operation failed: the state payload could not be deserialized. See InnerException for details.", ex);
- }
+ envelope.States.Add(stateItem);
}
- ///
- public override async Task SaveStateAsync(
- string storeName,
- string key,
- TValue value,
- StateOptions stateOptions = default,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
- {
- ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
- ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key));
-
- _ = await this.MakeSaveStateCallAsync(
- storeName,
- key,
- value,
- etag: null,
- stateOptions,
- metadata,
- cancellationToken);
+ try
+ {
+ await this.Client.DeleteBulkStateAsync(envelope, cancellationToken: cancellationToken);
+ }
+ catch (RpcException ex)
+ {
+ throw new DaprException("State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
}
- ///
- public override async Task TrySaveStateAsync(
- string storeName,
- string key,
- TValue value,
- string etag,
- StateOptions stateOptions = default,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
+ }
+
+ ///
+ public override async Task<(TValue value, string etag)> GetStateAndETagAsync(
+ string storeName,
+ string key,
+ ConsistencyMode? consistencyMode = default,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
+ ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key));
+
+ var envelope = new Autogenerated.GetStateRequest()
{
- ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
- ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key));
- // Not all state stores treat empty etag as invalid. Therefore, we will not verify an empty etag and
- // rely on bubbling up the error if any from Dapr runtime
- ArgumentVerifier.ThrowIfNull(etag, nameof(etag));
+ StoreName = storeName,
+ Key = key
+ };
- return await this.MakeSaveStateCallAsync(storeName, key, value, etag, stateOptions, metadata, cancellationToken);
+ if (metadata != null)
+ {
+ foreach (var kvp in metadata)
+ {
+ envelope.Metadata.Add(kvp.Key, kvp.Value);
+ }
}
- private async Task MakeSaveStateCallAsync(
- string storeName,
- string key,
- TValue value,
- string etag = default,
- StateOptions stateOptions = default,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
+ if (consistencyMode != null)
{
- var envelope = new Autogenerated.SaveStateRequest()
- {
- StoreName = storeName,
- };
+ envelope.Consistency = GetStateConsistencyForConsistencyMode(consistencyMode.Value);
+ }
+ var options = CreateCallOptions(headers: null, cancellationToken);
+ Autogenerated.GetStateResponse response;
- var stateItem = new Autogenerated.StateItem()
- {
- Key = key,
- };
+ try
+ {
+ response = await client.GetStateAsync(envelope, options);
+ }
+ catch (RpcException ex)
+ {
+ throw new DaprException("State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
+ }
- if (metadata != null)
- {
- foreach (var kvp in metadata)
- {
- stateItem.Metadata.Add(kvp.Key, kvp.Value);
- }
- }
+ try
+ {
+ return (TypeConverters.FromJsonByteString(response.Data, this.JsonSerializerOptions), response.Etag);
+ }
+ catch (JsonException ex)
+ {
+ throw new DaprException("State operation failed: the state payload could not be deserialized. See InnerException for details.", ex);
+ }
+ }
- if (etag != null)
- {
- stateItem.Etag = new Autogenerated.Etag() { Value = etag };
- }
+ ///
+ public override async Task SaveStateAsync(
+ string storeName,
+ string key,
+ TValue value,
+ StateOptions stateOptions = default,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
+ ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key));
+
+ _ = await this.MakeSaveStateCallAsync(
+ storeName,
+ key,
+ value,
+ etag: null,
+ stateOptions,
+ metadata,
+ cancellationToken);
+ }
- if (stateOptions != null)
- {
- stateItem.Options = ToAutoGeneratedStateOptions(stateOptions);
- }
+ ///
+ public override async Task TrySaveStateAsync(
+ string storeName,
+ string key,
+ TValue value,
+ string etag,
+ StateOptions stateOptions = default,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
+ ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key));
+ // Not all state stores treat empty etag as invalid. Therefore, we will not verify an empty etag and
+ // rely on bubbling up the error if any from Dapr runtime
+ ArgumentVerifier.ThrowIfNull(etag, nameof(etag));
- if (value != null)
- {
- stateItem.Value = TypeConverters.ToJsonByteString(value, this.jsonSerializerOptions);
- }
+ return await this.MakeSaveStateCallAsync(storeName, key, value, etag, stateOptions, metadata, cancellationToken);
+ }
- envelope.States.Add(stateItem);
+ private async Task MakeSaveStateCallAsync(
+ string storeName,
+ string key,
+ TValue value,
+ string etag = default,
+ StateOptions stateOptions = default,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ var envelope = new Autogenerated.SaveStateRequest()
+ {
+ StoreName = storeName,
+ };
- var options = CreateCallOptions(headers: null, cancellationToken);
- try
- {
- await client.SaveStateAsync(envelope, options);
- return true;
- }
- catch (RpcException rpc) when (etag != null && rpc.StatusCode == StatusCode.Aborted)
- {
- // This kind of failure indicates an ETag mismatch. Aborted doesn't seem like
- // the right status code at first, but check the docs, it fits this use-case.
- //
- // When an ETag is used we surface this though the Try... pattern
- return false;
- }
- catch (RpcException ex)
- {
- throw new DaprException("State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
- }
- }
+ var stateItem = new Autogenerated.StateItem()
+ {
+ Key = key,
+ };
- ///
- public override async Task ExecuteStateTransactionAsync(
- string storeName,
- IReadOnlyList operations,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
+ if (metadata != null)
{
- ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
- ArgumentVerifier.ThrowIfNull(operations, nameof(operations));
- if (operations.Count == 0)
+ foreach (var kvp in metadata)
{
- throw new ArgumentException($"{nameof(operations)} does not contain any elements");
+ stateItem.Metadata.Add(kvp.Key, kvp.Value);
}
+ }
- await this.MakeExecuteStateTransactionCallAsync(
- storeName,
- operations,
- metadata,
- cancellationToken);
+ if (etag != null)
+ {
+ stateItem.Etag = new Autogenerated.Etag() { Value = etag };
}
- private async Task MakeExecuteStateTransactionCallAsync(
- string storeName,
- IReadOnlyList states,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
+ if (stateOptions != null)
{
- var envelope = new Autogenerated.ExecuteStateTransactionRequest()
- {
- StoreName = storeName,
- };
+ stateItem.Options = ToAutoGeneratedStateOptions(stateOptions);
+ }
- foreach (var state in states)
- {
- var stateOperation = new Autogenerated.TransactionalStateOperation
- {
- OperationType = state.OperationType.ToString().ToLower(),
- Request = ToAutogeneratedStateItem(state)
- };
+ if (value != null)
+ {
+ stateItem.Value = TypeConverters.ToJsonByteString(value, this.jsonSerializerOptions);
+ }
- envelope.Operations.Add(stateOperation);
+ envelope.States.Add(stateItem);
- }
+ var options = CreateCallOptions(headers: null, cancellationToken);
+ try
+ {
+ await client.SaveStateAsync(envelope, options);
+ return true;
+ }
+ catch (RpcException rpc) when (etag != null && rpc.StatusCode == StatusCode.Aborted)
+ {
+ // This kind of failure indicates an ETag mismatch. Aborted doesn't seem like
+ // the right status code at first, but check the docs, it fits this use-case.
+ //
+ // When an ETag is used we surface this though the Try... pattern
+ return false;
+ }
+ catch (RpcException ex)
+ {
+ throw new DaprException("State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
+ }
+ }
- // Add metadata that applies to all operations if specified
- if (metadata != null)
- {
- foreach (var kvp in metadata)
- {
- envelope.Metadata.Add(kvp.Key, kvp.Value);
- }
- }
- var options = CreateCallOptions(headers: null, cancellationToken);
- try
- {
- await client.ExecuteStateTransactionAsync(envelope, options);
- }
- catch (RpcException ex)
- {
- throw new DaprException("State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
- }
+ ///
+ public override async Task ExecuteStateTransactionAsync(
+ string storeName,
+ IReadOnlyList operations,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
+ ArgumentVerifier.ThrowIfNull(operations, nameof(operations));
+ if (operations.Count == 0)
+ {
+ throw new ArgumentException($"{nameof(operations)} does not contain any elements");
}
- private Autogenerated.StateItem ToAutogeneratedStateItem(StateTransactionRequest state)
+ await this.MakeExecuteStateTransactionCallAsync(
+ storeName,
+ operations,
+ metadata,
+ cancellationToken);
+ }
+
+ private async Task MakeExecuteStateTransactionCallAsync(
+ string storeName,
+ IReadOnlyList states,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ var envelope = new Autogenerated.ExecuteStateTransactionRequest()
{
- var stateOperation = new Autogenerated.StateItem
- {
- Key = state.Key
- };
+ StoreName = storeName,
+ };
- if (state.Value != null)
+ foreach (var state in states)
+ {
+ var stateOperation = new Autogenerated.TransactionalStateOperation
{
- stateOperation.Value = ByteString.CopyFrom(state.Value);
- }
+ OperationType = state.OperationType.ToString().ToLower(),
+ Request = ToAutogeneratedStateItem(state)
+ };
- if (state.ETag != null)
- {
- stateOperation.Etag = new Autogenerated.Etag() { Value = state.ETag };
- }
+ envelope.Operations.Add(stateOperation);
- if (state.Metadata != null)
- {
- foreach (var kvp in state.Metadata)
- {
- stateOperation.Metadata.Add(kvp.Key, kvp.Value);
- }
- }
+ }
- if (state.Options != null)
+ // Add metadata that applies to all operations if specified
+ if (metadata != null)
+ {
+ foreach (var kvp in metadata)
{
- stateOperation.Options = ToAutoGeneratedStateOptions(state.Options);
+ envelope.Metadata.Add(kvp.Key, kvp.Value);
}
-
- return stateOperation;
}
-
- ///
- public override async Task DeleteStateAsync(
- string storeName,
- string key,
- StateOptions stateOptions = default,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
+ var options = CreateCallOptions(headers: null, cancellationToken);
+ try
{
- ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
- ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key));
-
- _ = await this.MakeDeleteStateCallAsync(
- storeName,
- key,
- etag: null,
- stateOptions,
- metadata,
- cancellationToken);
+ await client.ExecuteStateTransactionAsync(envelope, options);
+ }
+ catch (RpcException ex)
+ {
+ throw new DaprException("State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
}
+ }
- ///
- public override async Task TryDeleteStateAsync(
- string storeName,
- string key,
- string etag,
- StateOptions stateOptions = default,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
+ private Autogenerated.StateItem ToAutogeneratedStateItem(StateTransactionRequest state)
+ {
+ var stateOperation = new Autogenerated.StateItem
{
- ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
- ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key));
- // Not all state stores treat empty etag as invalid. Therefore, we will not verify an empty etag and
- // rely on bubbling up the error if any from Dapr runtime
- ArgumentVerifier.ThrowIfNull(etag, nameof(etag));
+ Key = state.Key
+ };
- return await this.MakeDeleteStateCallAsync(storeName, key, etag, stateOptions, metadata, cancellationToken);
+ if (state.Value != null)
+ {
+ stateOperation.Value = ByteString.CopyFrom(state.Value);
}
- private async Task MakeDeleteStateCallAsync(
- string storeName,
- string key,
- string etag = default,
- StateOptions stateOptions = default,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
+ if (state.ETag != null)
{
- var deleteStateEnvelope = new Autogenerated.DeleteStateRequest()
- {
- StoreName = storeName,
- Key = key,
- };
+ stateOperation.Etag = new Autogenerated.Etag() { Value = state.ETag };
+ }
- if (metadata != null)
+ if (state.Metadata != null)
+ {
+ foreach (var kvp in state.Metadata)
{
- foreach (var kvp in metadata)
- {
- deleteStateEnvelope.Metadata.Add(kvp.Key, kvp.Value);
- }
+ stateOperation.Metadata.Add(kvp.Key, kvp.Value);
}
+ }
- if (etag != null)
- {
- deleteStateEnvelope.Etag = new Autogenerated.Etag() { Value = etag };
- }
+ if (state.Options != null)
+ {
+ stateOperation.Options = ToAutoGeneratedStateOptions(state.Options);
+ }
- if (stateOptions != null)
- {
- deleteStateEnvelope.Options = ToAutoGeneratedStateOptions(stateOptions);
- }
+ return stateOperation;
+ }
- var options = CreateCallOptions(headers: null, cancellationToken);
- try
- {
- await client.DeleteStateAsync(deleteStateEnvelope, options);
- return true;
- }
- catch (RpcException rpc) when (etag != null && rpc.StatusCode == StatusCode.Aborted)
- {
- // This kind of failure indicates an ETag mismatch. Aborted doesn't seem like
- // the right status code at first, but check the docs, it fits this use-case.
- //
- // When an ETag is used we surface this though the Try... pattern
- return false;
- }
- catch (RpcException ex)
- {
- throw new DaprException("State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
- }
- }
+ ///
+ public override async Task DeleteStateAsync(
+ string storeName,
+ string key,
+ StateOptions stateOptions = default,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
+ ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key));
+
+ _ = await this.MakeDeleteStateCallAsync(
+ storeName,
+ key,
+ etag: null,
+ stateOptions,
+ metadata,
+ cancellationToken);
+ }
+
+ ///
+ public override async Task TryDeleteStateAsync(
+ string storeName,
+ string key,
+ string etag,
+ StateOptions stateOptions = default,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
+ ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key));
+ // Not all state stores treat empty etag as invalid. Therefore, we will not verify an empty etag and
+ // rely on bubbling up the error if any from Dapr runtime
+ ArgumentVerifier.ThrowIfNull(etag, nameof(etag));
+
+ return await this.MakeDeleteStateCallAsync(storeName, key, etag, stateOptions, metadata, cancellationToken);
+ }
- ///
- public async override Task> QueryStateAsync(
- string storeName,
- string jsonQuery,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
+ private async Task MakeDeleteStateCallAsync(
+ string storeName,
+ string key,
+ string etag = default,
+ StateOptions stateOptions = default,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ var deleteStateEnvelope = new Autogenerated.DeleteStateRequest()
{
- var queryRequest = new Autogenerated.QueryStateRequest()
- {
- StoreName = storeName,
- Query = jsonQuery
- };
+ StoreName = storeName,
+ Key = key,
+ };
- if (metadata != null)
+ if (metadata != null)
+ {
+ foreach (var kvp in metadata)
{
- foreach (var kvp in metadata)
- {
- queryRequest.Metadata.Add(kvp.Key, kvp.Value);
- }
+ deleteStateEnvelope.Metadata.Add(kvp.Key, kvp.Value);
}
+ }
- var options = CreateCallOptions(headers: null, cancellationToken);
+ if (etag != null)
+ {
+ deleteStateEnvelope.Etag = new Autogenerated.Etag() { Value = etag };
+ }
- try
- {
- var items = new List>();
- var failedKeys = new List();
- var queryResponse = await client.QueryStateAlpha1Async(queryRequest, options);
- foreach (var item in queryResponse.Results)
- {
- if (!string.IsNullOrEmpty(item.Error))
- {
- // When we encounter an error, we record the key and prepare to throw an exception at the end of the results.
- failedKeys.Add(item.Key);
- continue;
- }
- items.Add(new StateQueryItem(item.Key, TypeConverters.FromJsonByteString(item.Data, this.JsonSerializerOptions), item.Etag, item.Error));
- }
+ if (stateOptions != null)
+ {
+ deleteStateEnvelope.Options = ToAutoGeneratedStateOptions(stateOptions);
+ }
- var results = new StateQueryResponse(items, queryResponse.Token, queryResponse.Metadata);
- if (failedKeys.Count > 0)
- {
- // We encountered some bad keys so we throw instead of returning to alert the user.
- throw new StateQueryException($"Encountered an error while processing state query results.", results, failedKeys);
- }
+ var options = CreateCallOptions(headers: null, cancellationToken);
- return results;
- }
- catch (RpcException ex)
- {
- throw new DaprException("Query state operation failed: the Dapr endpointed indicated a failure. See InnerException for details.", ex);
- }
- catch (JsonException ex)
- {
- throw new DaprException("State operation failed: the state payload could not be deserialized. See InnerException for details.", ex);
- }
+ try
+ {
+ await client.DeleteStateAsync(deleteStateEnvelope, options);
+ return true;
+ }
+ catch (RpcException rpc) when (etag != null && rpc.StatusCode == StatusCode.Aborted)
+ {
+ // This kind of failure indicates an ETag mismatch. Aborted doesn't seem like
+ // the right status code at first, but check the docs, it fits this use-case.
+ //
+ // When an ETag is used we surface this though the Try... pattern
+ return false;
+ }
+ catch (RpcException ex)
+ {
+ throw new DaprException("State operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
}
- #endregion
+ }
- #region Secret Apis
- ///
- public async override Task> GetSecretAsync(
- string storeName,
- string key,
- IReadOnlyDictionary metadata = default,
- CancellationToken cancellationToken = default)
+ ///
+ public async override Task> QueryStateAsync(
+ string storeName,
+ string jsonQuery,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ var queryRequest = new Autogenerated.QueryStateRequest()
{
- ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
- ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key));
+ StoreName = storeName,
+ Query = jsonQuery
+ };
- var envelope = new Autogenerated.GetSecretRequest()
+ if (metadata != null)
+ {
+ foreach (var kvp in metadata)
{
- StoreName = storeName,
- Key = key
- };
+ queryRequest.Metadata.Add(kvp.Key, kvp.Value);
+ }
+ }
- if (metadata != null)
+ var options = CreateCallOptions(headers: null, cancellationToken);
+
+ try
+ {
+ var items = new List>();
+ var failedKeys = new List();
+ var queryResponse = await client.QueryStateAlpha1Async(queryRequest, options);
+ foreach (var item in queryResponse.Results)
{
- foreach (var kvp in metadata)
+ if (!string.IsNullOrEmpty(item.Error))
{
- envelope.Metadata.Add(kvp.Key, kvp.Value);
+ // When we encounter an error, we record the key and prepare to throw an exception at the end of the results.
+ failedKeys.Add(item.Key);
+ continue;
}
+ items.Add(new StateQueryItem(item.Key, TypeConverters.FromJsonByteString(item.Data, this.JsonSerializerOptions), item.Etag, item.Error));
}
- var options = CreateCallOptions(headers: null, cancellationToken);
- Autogenerated.GetSecretResponse response;
-
- try
- {
- response = await client.GetSecretAsync(envelope, options);
- }
- catch (RpcException ex)
+ var results = new StateQueryResponse(items, queryResponse.Token, queryResponse.Metadata);
+ if (failedKeys.Count > 0)
{
- throw new DaprException("Secret operation failed: the Dapr endpoint indicated a failure. See InnerException for details.", ex);
+ // We encountered some bad keys so we throw instead of returning to alert the user.
+ throw new StateQueryException($"Encountered an error while processing state query results.", results, failedKeys);
}
- return response.Data.ToDictionary(kv => kv.Key, kv => kv.Value);
+ return results;
+ }
+ catch (RpcException ex)
+ {
+ throw new DaprException("Query state operation failed: the Dapr endpointed indicated a failure. See InnerException for details.", ex);
+ }
+ catch (JsonException ex)
+ {
+ throw new DaprException("State operation failed: the state payload could not be deserialized. See InnerException for details.", ex);
}
+ }
+ #endregion
+
+ #region Secret Apis
+ ///
+ public async override Task> GetSecretAsync(
+ string storeName,
+ string key,
+ IReadOnlyDictionary metadata = default,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentVerifier.ThrowIfNullOrEmpty(storeName, nameof(storeName));
+ ArgumentVerifier.ThrowIfNullOrEmpty(key, nameof(key));
- ///
- public async override Task>> GetBulkSecretAsync(
- string storeName,
- IReadOnlyDictionary