diff --git a/.devcontainer/localinit.sh b/.devcontainer/localinit.sh
index 80b27e4f4..69c4c1274 100644
--- a/.devcontainer/localinit.sh
+++ b/.devcontainer/localinit.sh
@@ -6,4 +6,4 @@ az extension add --name containerapp --yes
nvm install v18.12.1
# initialize Dapr
-dapr init --runtime-version=1.10.0-rc.2
\ No newline at end of file
+dapr init --runtime-version=1.14.0
\ No newline at end of file
diff --git a/.github/holopin.yml b/.github/holopin.yml
index 44a7f0c8a..7472cc284 100644
--- a/.github/holopin.yml
+++ b/.github/holopin.yml
@@ -1,6 +1,6 @@
organization: dapr
-defaultSticker: clmjkxscc122740fl0mkmb7egi
+defaultSticker: clrqfdv4x24910fl5n4iwu5oa
stickers:
-
- id: clmjkxscc122740fl0mkmb7egi
- alias: ghc2023
+ id: clrqfdv4x24910fl5n4iwu5oa
+ alias: sdk-badge
\ No newline at end of file
diff --git a/.github/workflows/itests.yml b/.github/workflows/itests.yml
index 8a299bd59..36741ce7c 100644
--- a/.github/workflows/itests.yml
+++ b/.github/workflows/itests.yml
@@ -42,9 +42,9 @@ jobs:
GOOS: linux
GOARCH: amd64
GOPROXY: https://proxy.golang.org
- DAPR_CLI_VER: 1.12.0
- DAPR_RUNTIME_VER: 1.12.0
- DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/release-1.12/install/install.sh
+ DAPR_CLI_VER: 1.14.0
+ DAPR_RUNTIME_VER: 1.14.0
+ DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/release-1.14/install/install.sh
DAPR_CLI_REF: ''
steps:
- name: Set up Dapr CLI
@@ -113,7 +113,7 @@ jobs:
- name: Build
# disable deterministic builds, just for test run. Deterministic builds break coverage for some reason
run: dotnet build --configuration release /p:GITHUB_ACTIONS=false
- - name: Run Test
+ - name: Run General Tests
id: tests
continue-on-error: true # proceed if tests fail, the report step will report the failure with more details.
run: |
@@ -128,8 +128,24 @@ jobs:
/p:CollectCoverage=true \
/p:CoverletOutputFormat=opencover \
/p:GITHUB_ACTIONS=false
+ - name: Run Generators Tests
+ id: generator-tests
+ continue-on-error: true # proceed if tests fail, the report step will report the failure with more details.
+ run: |
+ dotnet test ${{ github.workspace }}/test/Dapr.E2E.Test.Actors.Generators/Dapr.E2E.Test.Actors.Generators.csproj \
+ --configuration Release \
+ --framework ${{ matrix.framework }} \
+ --no-build \
+ --no-restore \
+ --logger "trx;LogFilePrefix=${{ matrix.prefix }}" \
+ --logger "GitHubActions;report-warnings=false" \
+ --logger "console;verbosity=detailed" \
+ --results-directory "${{ github.workspace }}/TestResults" \
+ /p:CollectCoverage=true \
+ /p:CoverletOutputFormat=opencover \
+ /p:GITHUB_ACTIONS=false
- name: Check test failure in PR
- if: github.event_name == 'pull_request' && steps.tests.outcome != 'success'
+ if: github.event_name == 'pull_request' && (steps.tests.outcome != 'success' || steps.generator-tests.outcome != 'success')
run: exit 1
- name: Upload test coverage
uses: codecov/codecov-action@v1
diff --git a/.github/workflows/sdk_build.yml b/.github/workflows/sdk_build.yml
index 4fde80610..fe935bfb8 100644
--- a/.github/workflows/sdk_build.yml
+++ b/.github/workflows/sdk_build.yml
@@ -32,7 +32,7 @@ jobs:
- name: Generate Packages
run: dotnet pack --configuration release
- name: Upload packages
- uses: actions/upload-artifact@master
+ uses: actions/upload-artifact@v4
with:
name: packages
path: ${{ env.NUPKG_OUTDIR }}
@@ -116,7 +116,7 @@ jobs:
if: startswith(github.ref, 'refs/tags/v') && !(endsWith(github.ref, '-rc') || endsWith(github.ref, '-dev') || endsWith(github.ref, '-prerelease'))
steps:
- name: Download release artifacts
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v4
with:
name: packages
path: packages
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f47877cbd..7712340a5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -123,3 +123,13 @@ A non-exclusive list of code that must be places in `vendor/`:
## Code of Conduct
This project has adopted the [Contributor Covenant Code of Conduct](https://github.com/dapr/community/blob/master/CODE-OF-CONDUCT.md)
+
+
+
+## GitHub Dapr Bot Commands
+
+Checkout the [daprbot documentation](https://docs.dapr.io/contributing/daprbot/) for Github commands you can run in this repo for common tasks. For example, you can comment `/assign` on an issue to assign it to yourself.
+
+
+
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 000000000..d85020770
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,46 @@
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index d7232c54e..948516fe2 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,6 @@
# Dapr SDK for .NET
-[![Build Status](https://github.com/dapr/dotnet-sdk/workflows/build/badge.svg)](https://github.com/dapr/dotnet-sdk/actions?workflow=build)
-[![codecov](https://codecov.io/gh/dapr/dotnet-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/dapr/dotnet-sdk)
-[![License: Apache](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0)
-[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgit%40github.com%3Adapr%2Fdotnet-sdk.git.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgit%40github.com%3Adapr%2Fdotnet-sdk.git?ref=badge_shield)
+[![NuGet Version](https://img.shields.io/nuget/v/Dapr.Client?logo=nuget&label=Latest%20version&style=flat)](https://www.nuget.org/packages/Dapr.Client) [![NuGet Downloads](https://img.shields.io/nuget/dt/Dapr.Client?style=flat&logo=nuget&label=Downloads)](https://www.nuget.org/packages/Dapr.Client) [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/dapr/dotnet-sdk/.github%2Fworkflows%2Fsdk_build.yml?branch=master&label=Build&logo=github)](https://github.com/dapr/dotnet-sdk/actions/workflows/sdk_build.yml) [![codecov](https://codecov.io/gh/dapr/dotnet-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/dapr/dotnet-sdk) [![GitHub License](https://img.shields.io/github/license/dapr/dotnet-sdk?style=flat&label=License&logo=github)](https://github.com/dapr/dotnet-sdk/blob/master/LICENSE) [![GitHub issue custom search in repo](https://img.shields.io/github/issues-search/dapr/dotnet-sdk?query=type%3Aissue%20is%3Aopen%20label%3A%22good%20first%20issue%22&label=Good%20first%20issues&style=flat&logo=github)](https://github.com/dapr/dotnet-sdk/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) [![Discord](https://img.shields.io/discord/778680217417809931?label=Discord&style=flat&logo=discord)](http://bit.ly/dapr-discord) [![YouTube Channel Views](https://img.shields.io/youtube/channel/views/UCtpSQ9BLB_3EXdWAUQYwnRA?style=flat&label=YouTube%20views&logo=youtube)](https://youtube.com/@daprdev) [![X (formerly Twitter) Follow](https://img.shields.io/twitter/follow/daprdev?logo=x&style=flat)](https://twitter.com/daprdev)
Dapr SDK for .NET allows you to:
diff --git a/all.sln b/all.sln
index 47fc9098c..1a5d78efb 100644
--- a/all.sln
+++ b/all.sln
@@ -1,4 +1,3 @@
-
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32929.385
@@ -33,6 +32,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1BD1276E-D28A-45EA-89B1-6AD48471500D}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
+ README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Actors.AspNetCore.Test", "test\Dapr.Actors.AspNetCore.Test\Dapr.Actors.AspNetCore.Test.csproj", "{9C1D6ABA-5EDE-4FA0-A8A9-0AB98CB74737}"
@@ -71,8 +71,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StateManagement", "examples
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceInvocation", "examples\Client\ServiceInvocation\ServiceInvocation.csproj", "{8B570E70-0E73-4042-A4B6-1CC3CC782A65}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PublishSubscribe", "examples\Client\PublishSubscribe\PublishSubscribe.csproj", "{DE6913E3-E5D9-4D1D-95F9-9FED87BD09BC}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.E2E.Test", "test\Dapr.E2E.Test\Dapr.E2E.Test.csproj", "{4AA9E7B7-36BF-4AAE-BFA3-C9CE8740F4A0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.E2E.Test.App", "test\Dapr.E2E.Test.App\Dapr.E2E.Test.App.csproj", "{345FC3FB-D1E9-4AE8-9052-17D20AB01FA2}"
@@ -104,6 +102,26 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BulkPublishEventExample", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowUnitTest", "examples\Workflow\WorkflowUnitTest\WorkflowUnitTest.csproj", "{8CA09061-2BEF-4506-A763-07062D2BD6AC}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GeneratedActor", "GeneratedActor", "{7592AFA4-426B-42F3-AE82-957C86814482}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ActorClient", "examples\GeneratedActor\ActorClient\ActorClient.csproj", "{61C24126-F39D-4BEA-96DC-FC87BA730554}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ActorCommon", "examples\GeneratedActor\ActorCommon\ActorCommon.csproj", "{CB903D21-4869-42EF-BDD6-5B1CFF674337}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Actors.Generators", "src\Dapr.Actors.Generators\Dapr.Actors.Generators.csproj", "{980B5FD8-0107-41F7-8FAD-E4E8BAE8A625}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ActorService", "examples\GeneratedActor\ActorService\ActorService.csproj", "{7C06FE2D-6C62-48F5-A505-F0D715C554DE}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Actors.Generators.Test", "test\Dapr.Actors.Generators.Test\Dapr.Actors.Generators.Test.csproj", "{AF89083D-4715-42E6-93E9-38497D12A8A6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.E2E.Test.Actors.Generators", "test\Dapr.E2E.Test.Actors.Generators\Dapr.E2E.Test.Actors.Generators.csproj", "{B5CDB0DC-B26D-48F1-B934-FE5C1C991940}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cryptography", "examples\Client\Cryptography\Cryptography.csproj", "{C74FBA78-13E8-407F-A173-4555AEE41FF3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Protos", "src\Dapr.Protos\Dapr.Protos.csproj", "{DFBABB04-50E9-42F6-B470-310E1B545638}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Common", "src\Dapr.Common\Dapr.Common.csproj", "{B445B19C-A925-4873-8CB7-8317898B6970}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -248,6 +266,42 @@ Global
{DDC41278-FB60-403A-B969-2AEBD7C2D83C}.Release|Any CPU.Build.0 = Release|Any CPU
{8CA09061-2BEF-4506-A763-07062D2BD6AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CA09061-2BEF-4506-A763-07062D2BD6AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {61C24126-F39D-4BEA-96DC-FC87BA730554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {61C24126-F39D-4BEA-96DC-FC87BA730554}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {61C24126-F39D-4BEA-96DC-FC87BA730554}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {61C24126-F39D-4BEA-96DC-FC87BA730554}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CB903D21-4869-42EF-BDD6-5B1CFF674337}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CB903D21-4869-42EF-BDD6-5B1CFF674337}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CB903D21-4869-42EF-BDD6-5B1CFF674337}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CB903D21-4869-42EF-BDD6-5B1CFF674337}.Release|Any CPU.Build.0 = Release|Any CPU
+ {980B5FD8-0107-41F7-8FAD-E4E8BAE8A625}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {980B5FD8-0107-41F7-8FAD-E4E8BAE8A625}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {980B5FD8-0107-41F7-8FAD-E4E8BAE8A625}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {980B5FD8-0107-41F7-8FAD-E4E8BAE8A625}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7C06FE2D-6C62-48F5-A505-F0D715C554DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7C06FE2D-6C62-48F5-A505-F0D715C554DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7C06FE2D-6C62-48F5-A505-F0D715C554DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7C06FE2D-6C62-48F5-A505-F0D715C554DE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AF89083D-4715-42E6-93E9-38497D12A8A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AF89083D-4715-42E6-93E9-38497D12A8A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AF89083D-4715-42E6-93E9-38497D12A8A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AF89083D-4715-42E6-93E9-38497D12A8A6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B5CDB0DC-B26D-48F1-B934-FE5C1C991940}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B5CDB0DC-B26D-48F1-B934-FE5C1C991940}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B5CDB0DC-B26D-48F1-B934-FE5C1C991940}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B5CDB0DC-B26D-48F1-B934-FE5C1C991940}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C74FBA78-13E8-407F-A173-4555AEE41FF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C74FBA78-13E8-407F-A173-4555AEE41FF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C74FBA78-13E8-407F-A173-4555AEE41FF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C74FBA78-13E8-407F-A173-4555AEE41FF3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DFBABB04-50E9-42F6-B470-310E1B545638}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DFBABB04-50E9-42F6-B470-310E1B545638}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DFBABB04-50E9-42F6-B470-310E1B545638}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DFBABB04-50E9-42F6-B470-310E1B545638}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B445B19C-A925-4873-8CB7-8317898B6970}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B445B19C-A925-4873-8CB7-8317898B6970}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B445B19C-A925-4873-8CB7-8317898B6970}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B445B19C-A925-4873-8CB7-8317898B6970}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -293,6 +347,16 @@ Global
{4A175C27-EAFE-47E7-90F6-873B37863656} = {0EF6EA64-D7C3-420D-9890-EAE8D54A57E6}
{DDC41278-FB60-403A-B969-2AEBD7C2D83C} = {0EF6EA64-D7C3-420D-9890-EAE8D54A57E6}
{8CA09061-2BEF-4506-A763-07062D2BD6AC} = {BF3ED6BF-ADF3-4D25-8E89-02FB8D945CA9}
+ {7592AFA4-426B-42F3-AE82-957C86814482} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}
+ {61C24126-F39D-4BEA-96DC-FC87BA730554} = {7592AFA4-426B-42F3-AE82-957C86814482}
+ {CB903D21-4869-42EF-BDD6-5B1CFF674337} = {7592AFA4-426B-42F3-AE82-957C86814482}
+ {980B5FD8-0107-41F7-8FAD-E4E8BAE8A625} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
+ {7C06FE2D-6C62-48F5-A505-F0D715C554DE} = {7592AFA4-426B-42F3-AE82-957C86814482}
+ {AF89083D-4715-42E6-93E9-38497D12A8A6} = {DD020B34-460F-455F-8D17-CF4A949F100B}
+ {B5CDB0DC-B26D-48F1-B934-FE5C1C991940} = {DD020B34-460F-455F-8D17-CF4A949F100B}
+ {C74FBA78-13E8-407F-A173-4555AEE41FF3} = {A7F41094-8648-446B-AECD-DCC2CC871F73}
+ {DFBABB04-50E9-42F6-B470-310E1B545638} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
+ {B445B19C-A925-4873-8CB7-8317898B6970} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40}
diff --git a/daprdocs/content/en/dotnet-sdk-contributing/dotnet-contributing.md b/daprdocs/content/en/dotnet-sdk-contributing/dotnet-contributing.md
index a4e546ffa..6664191d6 100644
--- a/daprdocs/content/en/dotnet-sdk-contributing/dotnet-contributing.md
+++ b/daprdocs/content/en/dotnet-sdk-contributing/dotnet-contributing.md
@@ -21,3 +21,7 @@ The `daprdocs` directory contains the markdown files that are rendered into the
- All rules in the [docs guide]({{< ref contributing-docs.md >}}) should be followed in addition to these.
- All files and directories should be prefixed with `dotnet-` to ensure all file/directory names are globally unique across all Dapr documentation.
+
+## GitHub Dapr Bot Commands
+
+Checkout the [daprbot documentation](https://docs.dapr.io/contributing/daprbot/) for Github commands you can run in this repo for common tasks. For example, you can comment `/assign` on an issue to assign it to yourself.
diff --git a/daprdocs/content/en/dotnet-sdk-docs/_index.md b/daprdocs/content/en/dotnet-sdk-docs/_index.md
index e823ca29f..121dde310 100644
--- a/daprdocs/content/en/dotnet-sdk-docs/_index.md
+++ b/daprdocs/content/en/dotnet-sdk-docs/_index.md
@@ -18,7 +18,7 @@ Dapr offers a variety of packages to help with the development of .NET applicati
- [Dapr CLI]({{< ref install-dapr-cli.md >}}) installed
- Initialized [Dapr environment]({{< ref install-dapr-selfhost.md >}})
-- [.NET Core 3.1 or .NET 5+](https://dotnet.microsoft.com/download) installed
+- [.NET 6+](https://dotnet.microsoft.com/download) installed
## Installation
diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-howto.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-howto.md
index ab41c3917..eaa13625d 100644
--- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-howto.md
+++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-howto.md
@@ -45,7 +45,7 @@ This project contains the implementation of the actor client which calls MyActor
- [Dapr CLI]({{< ref install-dapr-cli.md >}}) installed.
- Initialized [Dapr environment]({{< ref install-dapr-selfhost.md >}}).
-- [.NET Core 3.1 or .NET 6+](https://dotnet.microsoft.com/download) installed. Dapr .NET SDK uses [ASP.NET Core](https://docs.microsoft.com/aspnet/core/introduction-to-aspnet-core?view=aspnetcore-6.0).
+- [.NET 6+](https://dotnet.microsoft.com/download) installed. Dapr .NET SDK uses [ASP.NET Core](https://docs.microsoft.com/aspnet/core/introduction-to-aspnet-core?view=aspnetcore-6.0).
## Step 0: Prepare
@@ -319,11 +319,8 @@ namespace MyActorService
app.UseRouting();
- app.UseEndpoints(endpoints =>
- {
- // Register actors handlers that interface with the Dapr runtime.
- endpoints.MapActorsHandlers();
- });
+ // Register actors handlers that interface with the Dapr runtime.
+ app.MapActorsHandlers();
}
}
}
@@ -394,7 +391,7 @@ namespace MyActorClient
Console.WriteLine($"Calling GetDataAsync on {actorType}:{actorId}...");
var savedData = await proxy.GetDataAsync();
- Console.WriteLine($"Got response: {response}");
+ Console.WriteLine($"Got response: {savedData}");
}
}
}
@@ -458,7 +455,7 @@ The projects that you've created can now to test the sample.
Calling SetDataAsync on MyActor:1...
Got response: Success
Calling GetDataAsync on MyActor:1...
- Got response: Success
+ Got response: PropertyA: ValueA, PropertyB: ValueB
```
> 💡 This sample relies on a few assumptions. The default listening port for an ASP.NET Core web project is 5000, which is being passed to `dapr run` as `--app-port 5000`. The default HTTP port for the Dapr sidecar is 3500. We're telling the sidecar for `MyActorService` to use 3500 so that `MyActorClient` can rely on the default value.
diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-serialization.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-serialization.md
new file mode 100644
index 000000000..abbeb437d
--- /dev/null
+++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-serialization.md
@@ -0,0 +1,317 @@
+---
+type: docs
+title: "Actor serialization in the .NET SDK"
+linkTitle: "Actor serialization"
+weight: 300000
+description: Necessary steps to serialize your types using remoted Actors in .NET
+---
+
+The Dapr actor package enables you to use Dapr virtual actors within a .NET application with strongly-typed remoting, but if you intend to send and receive strongly-typed data from your methods, there are a few key ground rules to understand. In this guide, you will learn how to configure your classes and records so they are properly serialized and deserialized at runtime.
+
+# Data Contract Serialization
+When Dapr's virtual actors are invoked via the remoting proxy, your data is serialized using a serialization engine called the [Data Contract Serializer](https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/serializable-types) implemented by the [DataContractSerializer](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.datacontractserializer) class, which converts your C# types to and from XML documents. When sending or receiving primitives (like strings or ints), this serialization happens transparently and there's no requisite preparation needed on your part. However, when working with complex types such as those you create, there are some important rules to take into consideration so this process works smoothly.
+
+This serialization framework is not specific to Dapr and is separately maintained by the .NET team within the [.NET Github repository](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/DataContractSerializer.cs).
+
+## Serializable Types
+There are several important considerations to keep in mind when using the Data Contract Serializer:
+
+- By default, all types, read/write properties (after construction) and fields marked as publicly visible are serialized
+- All types must either expose a public parameterless constructor or be decorated with the DataContractAttribute attribute
+- Init-only setters are only supported with the use of the DataContractAttribute attribute
+- Read-only fields, properties without a Get and Set method and internal or properties with private Get and Set methods are ignored during serialization
+- Serialization is supported for types that use other complex types that are not themselves marked with the DataContractAttribute attribute through the use of the KnownTypesAttribute attribute
+- If a type is marked with the DataContractAttribute attribute, all members you wish to serialize and deserialize must be decorated with the DataMemberAttribute attribute as well or they'll be set to their default values
+
+## How does deserialization work?
+The approach used for deserialization depends on whether or not the type is decorated with the [DataContractAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.datacontractattribute) attribute. If this attribute isn't present, an instance of the type is created using the parameterless constructor. Each of the properties and fields are then mapped into the type using their respective setters and the instance is returned to the caller.
+
+If the type _is_ marked with `[DataContract]`, the serializer instead uses reflection to read the metadata of the type and determine which properties or fields should be included based on whether or not they're marked with the DataMemberAttribute attribute as it's performed on an opt-in basis. It then allocates an uninitialized object in memory (avoiding the use of any constructors, parameterless or not) and then sets the value directly on each mapped property or field, even if private or uses init-only setters. Serialization callbacks are invoked as applicable throughout this process and then the object is returned to the caller.
+
+Use of the serialization attributes is highly recommended as they grant more flexibility to override names and namespaces and generally use more of the modern C# functionality. While the default serializer can be relied on for primitive types, it's not recommended for any of your own types, whether they be classes, structs or records. It's recommended that if you decorate a type with the DataContractAttribute attribute, you also explicitly decorate each of the members you want to serialize or deserialize with the DataMemberAttribute attribute as well.
+
+### .NET Classes
+Classes are fully supported in the Data Contract Serializer provided that that other rules detailed on this page and the [Data Contract Serializer](https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/serializable-types) documentation are also followed.
+
+The most important thing to remember here is that you must either have a public parameterless constructor or you must decorate it with the appropriate attributes. Let's review some examples to really clarify what will and won't work.
+
+In the following example, we present a simple class named Doodad. We don't provide an explicit constructor here, so the compiler will provide an default parameterless constructor. Because we're using [supported primitive types](###supported-primitive-types) (Guid, string and int32) and all our members have a public getter and setter, no attributes are required and we'll be able to use this class without issue when sending and receiving it from a Dapr actor method.
+
+```csharp
+public class Doodad
+{
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+ public int Count { get; set; }
+}
+```
+
+By default, this will serialize using the names of the members as used in the type and whatever values it was instantiated with:
+
+```xml
+
+ a06ced64-4f42-48ad-84dd-46ae6a7e333d
+ DoodadName
+ 5
+
+```
+
+So let's tweak it - let's add our own constructor and only use init-only setters on the members. This will fail to serialize and deserialize not because of the use of the init-only setters, but because there's no parameterless constructors.
+
+```csharp
+// WILL NOT SERIALIZE PROPERLY!
+public class Doodad
+{
+ public Doodad(string name, int count)
+ {
+ Id = Guid.NewGuid();
+ Name = name;
+ Count = count;
+ }
+
+ public Guid Id { get; set; }
+ public string Name { get; init; }
+ public int Count { get; init; }
+}
+```
+
+If we add a public parameterless constructor to the type, we're good to go and this will work without further annotations.
+
+```csharp
+public class Doodad
+{
+ public Doodad()
+ {
+ }
+
+ public Doodad(string name, int count)
+ {
+ Id = Guid.NewGuid();
+ Name = name;
+ Count = count;
+ }
+
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+ public int Count { get; set; }
+}
+```
+
+But what if we don't want to add this constructor? Perhaps you don't want your developers to accidentally create an instance of this Doodad using an unintended constructor. That's where the more flexible attributes are useful. If you decorate your type with a [DataContractAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.datacontractattribute) attribute, you can drop your parameterless constructor and it will work once again.
+
+```csharp
+[DataContract]
+public class Doodad
+{
+ public Doodad(string name, int count)
+ {
+ Id = Guid.NewGuid();
+ Name = name;
+ Count = count;
+ }
+
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+ public int Count { get; set; }
+}
+```
+
+In the above example, we don't need to also use the [DataMemberAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.datamemberattribute) attributes because again, we're using [built-in primitives](###supported-primitive-types) that the serializer supports. But, we do get more flexibility if we use the attributes. From the DataContractAttribute attribute, we can specify our own XML namespace with the Namespace argument and, via the Name argument, change the name of the type as used when serialized into the XML document.
+
+It's a recommended practice to append the DataContractAttribute attribute to the type and the DataMemberAttribute attributes to all the members you want to serialize anyway - if they're not necessary and you're not changing the default values, they'll just be ignored, but they give you a mechanism to opt into serializing members that wouldn't otherwise have been included such as those marked as private or that are themselves complex types or collections.
+
+Note that if you do opt into serializing your private members, their values will be serialized into plain text - they can very well be viewed, intercepted and potentially manipulated based on how you're handing the data once serialized, so it's an important consideration whether you want to mark these members or not in your use case.
+
+In the following example, we'll look at using the attributes to change the serialized names of some of the members as well as introduce the [IgnoreDataMemberAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.ignoredatamemberattribute) attribute. As the name indicates, this tells the serializer to skip this property even though it'd be otherwise eligible to serialize. Further, because I'm decorating the type with the DataContractAttribute attribute, it means that I can use init-only setters on the properties.
+
+```csharp
+[DataContract(Name="Doodad")]
+public class Doodad
+{
+ public Doodad(string name = "MyDoodad", int count = 5)
+ {
+ Id = Guid.NewGuid();
+ Name = name;
+ Count = count;
+ }
+
+ [DataMember(Name = "id")]
+ public Guid Id { get; init; }
+ [IgnoreDataMember]
+ public string Name { get; init; }
+ [DataMember]
+ public int Count { get; init; }
+}
+```
+
+When this is serialized, because we're changing the names of the serialized members, we can expect a new instance of Doodad using the default values this to be serialized as:
+
+```xml
+
+ a06ced64-4f42-48ad-84dd-46ae6a7e333d
+ 5
+
+```
+
+#### Classes in C# 12 - Primary Constructors
+C# 12 brought us primary constructors on classes. Use of a primary constructor means the compiler will be prevented from creating the default implicit parameterless constructor. While a primary constructor on a class doesn't generate any public properties, it does mean that if you pass this primary constructor any arguments or have non-primitive types in your class, you'll either need to specify your own parameterless constructor or use the serialization attributes.
+
+Here's an example where we're using the primary constructor to inject an ILogger to a field and add our own parameterless constructor without the need for any attributes.
+
+```csharp
+public class Doodad(ILogger _logger)
+{
+ public Doodad() {} //Our parameterless constructor
+
+ public Doodad(string name, int count)
+ {
+ Id = Guid.NewGuid();
+ Name = name;
+ Count = count;
+ }
+
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+ public int Count { get; set; }
+}
+```
+
+And using our serialization attributes (again, opting for init-only setters since we're using the serialization attributes):
+
+```csharp
+[DataContract]
+public class Doodad(ILogger _logger)
+{
+ public Doodad(string name, int count)
+ {
+ Id = Guid.NewGuid();
+ Name = name;
+ Count = count;
+ }
+
+ [DataMember]
+ public Guid Id { get; init; }
+ [DataMember]
+ public string Name { get; init; }
+ [DataMember]
+ public int Count { get; init; }
+}
+```
+
+### .NET Structs
+Structs are supported by the Data Contract serializer provided that they are marked with the DataContractAttribute attribute and the members you wish to serialize are marked with the DataMemberAttribute attribute. Further, to support deserialization, the struct will also need to have a parameterless constructor. This works even if you define your own parameterless constructor as enabled in C# 10.
+
+```csharp
+[DataContract]
+public struct Doodad
+{
+ [DataMember]
+ public int Count { get; set; }
+}
+```
+
+### .NET Records
+Records were introduced in C# 9 and follow precisely the same rules as classes when it comes to serialization. We recommend that you should decorate all your records with the DataContractAttribute attribute and members you wish to serialize with DataMemberAttribute attributes so you don't experience any deserialization issues using this or other newer C# functionalities. Because record classes use init-only setters for properties by default and encourage the use of the primary constructor, applying these attributes to your types ensures that the serializer can properly otherwise accommodate your types as-is.
+
+Typically records are presented as a simple one-line statement using the new primary constructor concept:
+
+```csharp
+public record Doodad(Guid Id, string Name, int Count);
+```
+
+This will throw an error encouraging the use of the serialization attributes as soon as you use it in a Dapr actor method invocation because there's no parameterless constructor available nor is it decorated with the aforementioned attributes.
+
+Here we add an explicit parameterless constructor and it won't throw an error, but none of the values will be set during deserialization since they're created with init-only setters. Because this doesn't use the DataContractAttribute attribute or the DataMemberAttribute attribute on any members, the serializer will be unable to map the target members correctly during deserialization.
+```csharp
+public record Doodad(Guid Id, string Name, int Count)
+{
+ public Doodad() {}
+}
+```
+
+This approach does without the additional constructor and instead relies on the serialization attributes. Because we mark the type with the DataContractAttribute attribute and decorate each member with its own DataMemberAttribute attribute, the serialization engine will be able to map from the XML document to our type without issue.
+```csharp
+[DataContract]
+public record Doodad(
+ [property: DataMember] Guid Id,
+ [property: DataMember] string Name,
+ [property: DataMember] int Count)
+```
+
+### Supported Primitive Types
+There are several types built into .NET that are considered primitive and eligible for serialization without additional effort on the part of the developer:
+
+- [Byte](https://learn.microsoft.com/en-us/dotnet/api/system.byte)
+- [SByte](https://learn.microsoft.com/en-us/dotnet/api/system.sbyte)
+- [Int16](https://learn.microsoft.com/en-us/dotnet/api/system.int16)
+- [Int32](https://learn.microsoft.com/en-us/dotnet/api/system.int32)
+- [Int64](https://learn.microsoft.com/en-us/dotnet/api/system.int64)
+- [UInt16](https://learn.microsoft.com/en-us/dotnet/api/system.uint16)
+- [UInt32](https://learn.microsoft.com/en-us/dotnet/api/system.uint32)
+- [UInt64](https://learn.microsoft.com/en-us/dotnet/api/system.uint64)
+- [Single](https://learn.microsoft.com/en-us/dotnet/api/system.single)
+- [Double](https://learn.microsoft.com/en-us/dotnet/api/system.double)
+- [Boolean](https://learn.microsoft.com/en-us/dotnet/api/system.boolean)
+- [Char](https://learn.microsoft.com/en-us/dotnet/api/system.char)
+- [Decimal](https://learn.microsoft.com/en-us/dotnet/api/system.decimal)
+- [Object](https://learn.microsoft.com/en-us/dotnet/api/system.object)
+- [String](https://learn.microsoft.com/en-us/dotnet/api/system.string)
+
+There are additional types that aren't actually primitives but have similar built-in support:
+
+- [DateTime](https://learn.microsoft.com/en-us/dotnet/api/system.datetime)
+- [TimeSpan](https://learn.microsoft.com/en-us/dotnet/api/system.timespan)
+- [Guid](https://learn.microsoft.com/en-us/dotnet/api/system.guid)
+- [Uri](https://learn.microsoft.com/en-us/dotnet/api/system.uri)
+- [XmlQualifiedName](https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmlqualifiedname)
+
+Again, if you want to pass these types around via your actor methods, no additional consideration is necessary as they'll be serialized and deserialized without issue. Further, types that are themselves marked with the (SerializeableAttribute)[https://learn.microsoft.com/en-us/dotnet/api/system.serializableattribute] attribute will be serialized.
+
+### Enumeration Types
+Enumerations, including flag enumerations are serializable if appropriately marked. The enum members you wish to be serialized must be marked with the [EnumMemberAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.enummemberattribute) attribute in order to be serialized. Passing a custom value into the optional Value argument on this attribute will allow you to specify the value used for the member in the serialized document instead of having the serializer derive it from the name of the member.
+
+The enum type does not require that the type be decorated with the `DataContractAttribute` attribute - only that the members you wish to serialize be decorated with the `EnumMemberAttribute` attributes.
+
+```csharp
+public enum Colors
+{
+ [EnumMember]
+ Red,
+ [EnumMember(Value="g")]
+ Green,
+ Blue, //Even if used by a type, this value will not be serialized as it's not decorated with the EnumMember attribute
+}
+```
+
+### Collection Types
+With regards to the data contact serializer, all collection types that implement the [IEnumerable](https://learn.microsoft.com/en-us/dotnet/api/system.collections.ienumerable) interface including arays and generic collections are considered collections. Those types that implement [IDictionary](https://learn.microsoft.com/en-us/dotnet/api/system.collections.idictionary) or the generic [IDictionary](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.idictionary-2) are considered dictionary collections; all others are list collections.
+
+Not unlike other complex types, collection types must have a parameterless constructor available. Further, they must also have a method called Add so they can be properly serialized and deserialized. The types used by these collection types must themselves be marked with the `DataContractAttribute` attribute or otherwise be serializable as described throughout this document.
+
+### Data Contract Versioning
+As the data contract serializer is only used in Dapr with respect to serializing the values in the .NET SDK to and from the Dapr actor instances via the proxy methods, there's little need to consider versioning of data contracts as the data isn't being persisted between application versions using the same serializer. For those interested in learning more about data contract versioning visit [here](https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/data-contract-versioning).
+
+### Known Types
+Nesting your own complex types is easily accommodated by marking each of the types with the [DataContractAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.datacontractattribute) attribute. This informs the serializer as to how deserialization should be performed.
+But what if you're working with polymorphic types and one of your members is a base class or interface with derived classes or other implementations? Here, you'll use the [KnownTypeAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.knowntypeattribute) attribute to give a hint to the serializer about how to proceed.
+
+When you apply the [KnownTypeAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.knowntypeattribute) attribute to a type, you are informing the data contract serializer about what subtypes it might encounter allowing it to properly handle the serialization and deserialization of these types, even when the actual type at runtime is different from the declared type.
+
+```chsarp
+[DataContract]
+[KnownType(typeof(DerivedClass))]
+public class BaseClass
+{
+ //Members of the base class
+}
+
+[DataContract]
+public class DerivedClass : BaseClass
+{
+ //Additional members of the derived class
+}
+```
+
+In this example, the `BaseClass` is marked with `[KnownType(typeof(DerivedClass))]` which tells the data contract serializer that `DerivedClass` is a possible implementation of `BaseClass` that it may need to serialize or deserialize. Without this attribute, the serialize would not be aware of the `DerivedClass` when it encounters an instance of `BaseClass` that is actually of type `DerivedClass` and this could lead to a serialization exception because the serializer would not know how to handle the derived type. By specifying all possible derived types as known types, you ensure that the serializer can process the type and its members correctly.
+
+For more information and examples about using `[KnownType]`, please refer to the [official documentation](https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/data-contract-known-types).
\ No newline at end of file
diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-client/_index.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-client/_index.md
index f68028dc5..cab44468b 100644
--- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-client/_index.md
+++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-client/_index.md
@@ -21,13 +21,22 @@ The .NET SDK allows you to interface with all of the [Dapr building blocks]({{<
### Invoke a service
+#### HTTP
You can either use the `DaprClient` or `System.Net.Http.HttpClient` to invoke your services.
+{{% alert title="Note" color="primary" %}}
+ You can also [invoke a non-Dapr endpoint using either a named `HTTPEndpoint` or an FQDN URL to the non-Dapr environment]({{< ref "howto-invoke-non-dapr-endpoints.md#using-an-httpendpoint-resource-or-fqdn-url-for-non-dapr-endpoints" >}}).
+
+{{% /alert %}}
+
+
{{< tabs SDK HTTP>}}
{{% codetab %}}
```csharp
-using var client = new DaprClientBuilder().Build();
+using var client = new DaprClientBuilder().
+ UseTimeout(TimeSpan.FromSeconds(2)). // Optionally, set a timeout
+ Build();
// Invokes a POST method named "deposit" that takes input of type "Transaction"
var data = new { id = "17", amount = 99m };
@@ -40,15 +49,31 @@ Console.WriteLine("Returned: id:{0} | Balance:{1}", account.Id, account.Balance)
```csharp
var client = DaprClient.CreateInvokeHttpClient(appId: "routing");
+// To set a timeout on the HTTP client:
+client.Timeout = TimeSpan.FromSeconds(2);
+
var deposit = new Transaction { Id = "17", Amount = 99m };
var response = await client.PostAsJsonAsync("/deposit", deposit, cancellationToken);
var account = await response.Content.ReadFromJsonAsync(cancellationToken: cancellationToken);
Console.WriteLine("Returned: id:{0} | Balance:{1}", account.Id, account.Balance);
```
{{% /codetab %}}
-
{{< /tabs >}}
+#### gRPC
+You can use the `DaprClient` to invoke your services over gRPC.
+
+```csharp
+using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
+var invoker = DaprClient.CreateInvocationInvoker(appId: myAppId, daprEndpoint: serviceEndpoint);
+var client = new MyService.MyServiceClient(invoker);
+
+var options = new CallOptions(cancellationToken: cts.Token, deadline: DateTime.UtcNow.AddSeconds(1));
+await client.MyMethodAsync(new Empty(), options);
+
+Assert.Equal(StatusCode.DeadlineExceeded, ex.StatusCode);
+```
+
- For a full guide on service invocation visit [How-To: Invoke a service]({{< ref howto-invoke-discover-services.md >}}).
### Save & get application state
@@ -105,7 +130,7 @@ Console.WriteLine("Published deposit event!");
```
- For a full list of state operations visit [How-To: Publish & subscribe]({{< ref howto-publish-subscribe.md >}}).
-- Visit [.NET SDK examples](https://github.com/dapr/dotnet-sdk/tree/master/examples/client/PublishSubscribe) for code samples and instructions to try out pub/sub
+- Visit [.NET SDK examples](https://github.com/dapr/dotnet-sdk/tree/master/examples/Client/PublishSubscribe) for code samples and instructions to try out pub/sub
### Interact with output bindings
@@ -141,7 +166,7 @@ var secrets = await client.GetSecretAsync("mysecretstore", "key-value-pair-secre
Console.WriteLine($"Got secret keys: {string.Join(", ", secrets.Keys)}");
```
-{{% / codetab %}}
+{{% /codetab %}}
{{% codetab %}}
diff --git a/examples/Actor/ActorClient/Program.cs b/examples/Actor/ActorClient/Program.cs
index 5d7f06fb2..f6ca26f53 100644
--- a/examples/Actor/ActorClient/Program.cs
+++ b/examples/Actor/ActorClient/Program.cs
@@ -1,4 +1,4 @@
-// ------------------------------------------------------------------------
+// ------------------------------------------------------------------------
// Copyright 2021 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.
@@ -11,6 +11,9 @@
// limitations under the License.
// ------------------------------------------------------------------------
+using Dapr.Actors.Communication;
+using IDemoActor;
+
namespace ActorClient
{
using System;
@@ -18,7 +21,6 @@ namespace ActorClient
using System.Threading.Tasks;
using Dapr.Actors;
using Dapr.Actors.Client;
- using IDemoActorInterface;
///
/// Actor Client class.
@@ -43,10 +45,10 @@ public static async Task Main(string[] args)
// Make strongly typed Actor calls with Remoting.
// DemoActor is the type registered with Dapr runtime in the service.
- var proxy = ActorProxy.Create(actorId, "DemoActor");
+ var proxy = ActorProxy.Create(actorId, "DemoActor");
Console.WriteLine("Making call using actor proxy to save data.");
- await proxy.SaveData(data);
+ await proxy.SaveData(data, TimeSpan.FromMinutes(10));
Console.WriteLine("Making call using actor proxy to get data.");
var receivedData = await proxy.GetData();
Console.WriteLine($"Received data is {receivedData}.");
diff --git a/examples/Actor/DemoActor/BankService.cs b/examples/Actor/DemoActor/BankService.cs
index 0a164183f..a24eadedb 100644
--- a/examples/Actor/DemoActor/BankService.cs
+++ b/examples/Actor/DemoActor/BankService.cs
@@ -11,9 +11,9 @@
// limitations under the License.
// ------------------------------------------------------------------------
-using IDemoActorInterface;
+using IDemoActor;
-namespace DaprDemoActor
+namespace DemoActor
{
public class BankService
{
diff --git a/examples/Actor/DemoActor/DemoActor.cs b/examples/Actor/DemoActor/DemoActor.cs
index 62c100f79..b5ef53e93 100644
--- a/examples/Actor/DemoActor/DemoActor.cs
+++ b/examples/Actor/DemoActor/DemoActor.cs
@@ -1,4 +1,4 @@
-// ------------------------------------------------------------------------
+// ------------------------------------------------------------------------
// Copyright 2021 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.
@@ -11,14 +11,14 @@
// limitations under the License.
// ------------------------------------------------------------------------
-namespace DaprDemoActor
-{
- using System;
- using System.Text.Json;
- using System.Threading.Tasks;
- using Dapr.Actors.Runtime;
- using IDemoActorInterface;
+using System;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Dapr.Actors.Runtime;
+using IDemoActor;
+namespace DemoActor
+{
// The following example showcases a few features of Actors
//
// Every actor should inherit from the Actor type, and must implement one or more actor interfaces.
@@ -27,7 +27,7 @@ namespace DaprDemoActor
// For Actors to use Reminders, it must derive from IRemindable.
// If you don't intend to use Reminder feature, you can skip implementing IRemindable and reminder
// specific methods which are shown in the code below.
- public class DemoActor : Actor, IDemoActor, IBankActor, IRemindable
+ public class DemoActor : Actor, IDemoActor.IDemoActor, IBankActor, IRemindable
{
private const string StateName = "my_data";
@@ -41,12 +41,12 @@ public DemoActor(ActorHost host, BankService bank)
this.bank = bank;
}
- public async Task SaveData(MyData data)
+ public async Task SaveData(MyData data, TimeSpan ttl)
{
Console.WriteLine($"This is Actor id {this.Id} with data {data}.");
// Set State using StateManager, state is saved after the method execution.
- await this.StateManager.SetStateAsync(StateName, data);
+ await this.StateManager.SetStateAsync(StateName, data, ttl);
}
public Task GetData()
@@ -109,7 +109,7 @@ public async Task ReceiveReminderAsync(string reminderName, byte[] state, TimeSp
// This method is invoked when an actor reminder is fired.
var actorState = await this.StateManager.GetStateAsync(StateName);
actorState.PropertyB = $"Reminder triggered at '{DateTime.Now:yyyy-MM-ddTHH:mm:ss}'";
- await this.StateManager.SetStateAsync(StateName, actorState);
+ await this.StateManager.SetStateAsync(StateName, actorState, ttl: TimeSpan.FromMinutes(5));
}
class TimerParams
@@ -173,7 +173,7 @@ public async Task TimerCallback(byte[] data)
{
var state = await this.StateManager.GetStateAsync(StateName);
state.PropertyA = $"Timer triggered at '{DateTime.Now:yyyyy-MM-ddTHH:mm:s}'";
- await this.StateManager.SetStateAsync(StateName, state);
+ await this.StateManager.SetStateAsync(StateName, state, ttl: TimeSpan.FromMinutes(5));
var timerParams = JsonSerializer.Deserialize(data);
Console.WriteLine("Timer parameter1: " + timerParams.IntParam);
Console.WriteLine("Timer parameter2: " + timerParams.StringParam);
diff --git a/examples/Actor/DemoActor/DemoActor.csproj b/examples/Actor/DemoActor/DemoActor.csproj
index 1ee37fdbe..24a42ee0e 100644
--- a/examples/Actor/DemoActor/DemoActor.csproj
+++ b/examples/Actor/DemoActor/DemoActor.csproj
@@ -1,13 +1,24 @@
-
- net6
-
-
-
-
-
-
-
+
+ net6
+
+
+
+ true
+ true
+ demo-actor
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/Actor/DemoActor/Program.cs b/examples/Actor/DemoActor/Program.cs
index a56681fdb..1d538b471 100644
--- a/examples/Actor/DemoActor/Program.cs
+++ b/examples/Actor/DemoActor/Program.cs
@@ -11,11 +11,11 @@
// limitations under the License.
// ------------------------------------------------------------------------
-namespace DaprDemoActor
-{
- using Microsoft.AspNetCore.Hosting;
- using Microsoft.Extensions.Hosting;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Hosting;
+namespace DemoActor
+{
public class Program
{
public static void Main(string[] args)
diff --git a/examples/Actor/DemoActor/Startup.cs b/examples/Actor/DemoActor/Startup.cs
index c04dfdcba..f1165e3c7 100644
--- a/examples/Actor/DemoActor/Startup.cs
+++ b/examples/Actor/DemoActor/Startup.cs
@@ -1,4 +1,4 @@
-// ------------------------------------------------------------------------
+// ------------------------------------------------------------------------
// Copyright 2021 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.
@@ -11,14 +11,14 @@
// limitations under the License.
// ------------------------------------------------------------------------
-namespace DaprDemoActor
-{
- using Microsoft.AspNetCore.Builder;
- using Microsoft.AspNetCore.Hosting;
- using Microsoft.Extensions.Configuration;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Hosting;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+namespace DemoActor
+{
public class Startup
{
public Startup(IConfiguration configuration)
diff --git a/examples/Actor/DemoActor/demo-actor.yaml b/examples/Actor/DemoActor/demo-actor.yaml
new file mode 100644
index 000000000..99a8abd34
--- /dev/null
+++ b/examples/Actor/DemoActor/demo-actor.yaml
@@ -0,0 +1,67 @@
+apiVersion: dapr.io/v1alpha1
+kind: Component
+metadata:
+ name: statestore
+spec:
+ type: state.in-memory
+ version: v1
+ metadata:
+ - name: actorStateStore
+ value: "true"
+---
+kind: Service
+apiVersion: v1
+metadata:
+ name: demoactor
+ labels:
+ app: demoactor
+spec:
+ selector:
+ app: demoactor
+ ports:
+ - name: app-port
+ protocol: TCP
+ port: 5010
+ targetPort: app-port
+ - name: dapr-http
+ protocol: TCP
+ port: 3500
+ targetPort: 3500
+ type: LoadBalancer
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: demoactor
+ labels:
+ app: demoactor
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: demoactor
+ template:
+ metadata:
+ labels:
+ app: demoactor
+ annotations:
+ dapr.io/enabled: "true"
+ dapr.io/app-id: "demoactor"
+ dapr.io/app-port: "5010"
+ dapr.io/enable-api-logging: "true"
+ dapr.io/sidecar-listen-addresses: "0.0.0.0"
+ spec:
+ containers:
+ - name: demoactor
+ # image: /demo-actor:latest
+ image: demo-actor:latest
+ # if you are using docker desktop, you can use imagePullPolicy: Never to use local image
+ imagePullPolicy: Never
+ env:
+ - name: APP_PORT
+ value: "5010"
+ - name: ASPNETCORE_URLS
+ value: "http://+:5010"
+ ports:
+ - name: app-port
+ containerPort: 5010
diff --git a/examples/Actor/IDemoActor/IBankActor.cs b/examples/Actor/IDemoActor/IBankActor.cs
index 95ac23844..c495f027b 100644
--- a/examples/Actor/IDemoActor/IBankActor.cs
+++ b/examples/Actor/IDemoActor/IBankActor.cs
@@ -11,12 +11,12 @@
// limitations under the License.
// ------------------------------------------------------------------------
-namespace IDemoActorInterface
-{
- using System;
- using System.Threading.Tasks;
- using Dapr.Actors;
+using System;
+using System.Threading.Tasks;
+using Dapr.Actors;
+namespace IDemoActor
+{
public interface IBankActor : IActor
{
Task GetAccountBalance();
diff --git a/examples/Actor/IDemoActor/IDemoActor.cs b/examples/Actor/IDemoActor/IDemoActor.cs
index c2926a048..6f2d32801 100644
--- a/examples/Actor/IDemoActor/IDemoActor.cs
+++ b/examples/Actor/IDemoActor/IDemoActor.cs
@@ -1,4 +1,4 @@
-// ------------------------------------------------------------------------
+// ------------------------------------------------------------------------
// Copyright 2021 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.
@@ -11,13 +11,12 @@
// limitations under the License.
// ------------------------------------------------------------------------
-namespace IDemoActorInterface
-{
- using System;
- using System.Threading.Tasks;
- using Dapr.Actors;
- using Dapr.Actors.Runtime;
+using System;
+using System.Threading.Tasks;
+using Dapr.Actors;
+namespace IDemoActor
+{
///
/// Interface for Actor method.
///
@@ -27,8 +26,9 @@ public interface IDemoActor : IActor
/// Method to save data.
///
/// DAta to save.
+ /// TTL of state key.
/// A task that represents the asynchronous save operation.
- Task SaveData(MyData data);
+ Task SaveData(MyData data, TimeSpan ttl);
///
/// Method to get data.
diff --git a/examples/Actor/README.md b/examples/Actor/README.md
index ddc42cecf..89b6bf0bb 100644
--- a/examples/Actor/README.md
+++ b/examples/Actor/README.md
@@ -4,7 +4,7 @@ The Actor example shows how to create a virtual actor (`DemoActor`) and invoke i
## Prerequisites
-- [.NET Core 3.1 or .NET 5+](https://dotnet.microsoft.com/download) installed
+- [.NET 6+](https://dotnet.microsoft.com/download) installed
- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/)
- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/)
- [Dapr .NET SDK](https://github.com/dapr/dotnet-sdk/)
@@ -80,3 +80,80 @@ On Windows:
```sh
curl -X POST http://127.0.0.1:3500/v1.0/actors/DemoActor/abc/method/GetData
```
+
+### Build and push Docker image
+You can build the docker image of `DemoActor` service by running the following commands in the `DemoActor` project directory:
+
+``` Bash
+dotnet publish --os linux --arch x64 /t:PublishContainer -p ContainerImageTags='"latest"' --self-contained
+```
+
+The build produce and image with tag `demo-actor:latest` and load it in the local registry.
+Now the image can be pushed to your remote Docker registry by running the following commands:
+
+``` Bash
+# Replace with your Docker registry
+docker tag demo-actor:latest /demo-actor:latest
+
+# Push the image to your Docker registry
+docker push /demo-actor:latest
+```
+
+### Deploy the Actor service to Kubernetes
+#### Prerequisites
+- A Kubernetes cluster with `kubectl` configured to access it.
+- Dapr v1.14+ installed on the Kubernetes cluster. Follow the instructions [here](https://docs.dapr.io/getting-started/install-dapr-kubernetes/).
+- A Docker registry where you pushed the `DemoActor` image.
+
+#### Deploy the Actor service
+For quick deployment you can install dapr in dev mode using the following command:
+
+``` Bash
+dapr init -k --dev
+```
+
+To deploy the `DemoActor` service to Kubernetes, you can use the provided Kubernetes manifest file `demo-actor.yaml` in the `DemoActor` project directory.
+Before applying the manifest file, replace the image name in the manifest file with the image name you pushed to your Docker registry.
+
+Part to update in `demo-actor.yaml`:
+``` YAML
+image: /demoactor:latest
+```
+
+To install the application in `default` namespace, run the following command:
+
+``` Bash
+kubectl apply -f demo-actor.yaml
+```
+
+This will deploy the `DemoActor` service to Kubernetes. You can check the status of the deployment by running:
+
+``` Bash
+kubectl get pods -n default --watch
+```
+
+The manifest create 2 services:
+
+- `demoactor` service: The service that hosts the `DemoActor` actor.
+- `demoactor-dapr` service: The service that hosts the Dapr sidecar for the `DemoActor` actor.
+
+### Make client calls to the deployed Actor service
+To make client calls to the deployed `DemoActor` service, you can use the `ActorClient` project.
+Before running the client, update the `DAPR_HTTP_PORT` environment variable in the `ActorClient` project directory to the port on which Dapr is running in the Kubernetes cluster.
+
+On Linux, MacOS:
+``` Bash
+export DAPR_HTTP_PORT=3500
+```
+
+Than port-forward the `DemoActor` service to your local machine:
+
+``` Bash
+kubectl port-forward svc/demoactor 3500:3500
+```
+
+Now you can run the client project from the `ActorClient` directory:
+
+``` Bash
+dotnet run
+```
\ No newline at end of file
diff --git a/examples/AspNetCore/ControllerSample/Controllers/SampleController.cs b/examples/AspNetCore/ControllerSample/Controllers/SampleController.cs
index 485614150..5b339288c 100644
--- a/examples/AspNetCore/ControllerSample/Controllers/SampleController.cs
+++ b/examples/AspNetCore/ControllerSample/Controllers/SampleController.cs
@@ -11,6 +11,8 @@
// limitations under the License.
// ------------------------------------------------------------------------
+using System.Linq;
+
namespace ControllerSample.Controllers
{
using System;
@@ -43,6 +45,7 @@ public SampleController(ILogger logger)
/// State store name.
///
public const string StoreName = "statestore";
+
private readonly ILogger logger;
///
@@ -72,6 +75,11 @@ public ActionResult Get([FromState(StoreName)] StateEntry acco
[HttpPost("deposit")]
public async Task> Deposit(Transaction transaction, [FromServices] DaprClient daprClient)
{
+ // Example reading cloudevent properties from the headers
+ var headerEntries = Request.Headers.Aggregate("", (current, header) => current + ($"------- Header: {header.Key} : {header.Value}" + Environment.NewLine));
+
+ logger.LogInformation(headerEntries);
+
logger.LogInformation("Enter deposit");
var state = await daprClient.GetStateEntryAsync(StoreName, transaction.Id);
state.Value ??= new Account() { Id = transaction.Id, };
@@ -83,7 +91,7 @@ public async Task> Deposit(Transaction transaction, [FromS
}
state.Value.Balance += transaction.Amount;
- logger.LogInformation("Balance for Id {0} is {1}",state.Value.Id, state.Value.Balance);
+ logger.LogInformation("Balance for Id {0} is {1}", state.Value.Id, state.Value.Balance);
await state.SaveAsync();
return state.Value;
}
@@ -98,22 +106,23 @@ public async Task> Deposit(Transaction transaction, [FromS
[Topic("pubsub", "multideposit", "amountDeadLetterTopic", false)]
[BulkSubscribe("multideposit", 500, 2000)]
[HttpPost("multideposit")]
- public async Task> MultiDeposit([FromBody] BulkSubscribeMessage>
- bulkMessage, [FromServices] DaprClient daprClient)
+ public async Task> MultiDeposit([FromBody]
+ BulkSubscribeMessage>
+ bulkMessage, [FromServices] DaprClient daprClient)
{
logger.LogInformation("Enter bulk deposit");
-
+
List entries = new List();
foreach (var entry in bulkMessage.Entries)
- {
+ {
try
{
var transaction = entry.Event.Data;
var state = await daprClient.GetStateEntryAsync(StoreName, transaction.Id);
state.Value ??= new Account() { Id = transaction.Id, };
- logger.LogInformation("Id is {0}, the amount to be deposited is {1}",
+ logger.LogInformation("Id is {0}, the amount to be deposited is {1}",
transaction.Id, transaction.Amount);
if (transaction.Amount < 0m)
@@ -124,12 +133,16 @@ public async Task> MultiDeposit([FromBody
state.Value.Balance += transaction.Amount;
logger.LogInformation("Balance is {0}", state.Value.Balance);
await state.SaveAsync();
- entries.Add(new BulkSubscribeAppResponseEntry(entry.EntryId, BulkSubscribeAppResponseStatus.SUCCESS));
- } catch (Exception e) {
+ entries.Add(
+ new BulkSubscribeAppResponseEntry(entry.EntryId, BulkSubscribeAppResponseStatus.SUCCESS));
+ }
+ catch (Exception e)
+ {
logger.LogError(e.Message);
entries.Add(new BulkSubscribeAppResponseEntry(entry.EntryId, BulkSubscribeAppResponseStatus.RETRY));
}
}
+
return new BulkSubscribeAppResponse(entries);
}
@@ -165,6 +178,7 @@ public async Task> Withdraw(Transaction transaction, [From
{
return this.NotFound();
}
+
if (transaction.Amount < 0m)
{
return BadRequest(new { statusCode = 400, message = "bad request" });
@@ -185,7 +199,8 @@ public async Task> Withdraw(Transaction transaction, [From
/// "pubsub", the first parameter into the Topic attribute, is name of the default pub/sub configured by the Dapr CLI.
[Topic("pubsub", "withdraw", "event.type ==\"withdraw.v2\"", 1)]
[HttpPost("withdraw.v2")]
- public async Task> WithdrawV2(TransactionV2 transaction, [FromServices] DaprClient daprClient)
+ public async Task> WithdrawV2(TransactionV2 transaction,
+ [FromServices] DaprClient daprClient)
{
logger.LogInformation("Enter withdraw.v2");
if (transaction.Channel == "mobile" && transaction.Amount > 10000)
@@ -214,12 +229,15 @@ public async Task> WithdrawV2(TransactionV2 transaction, [
/// "pubsub", the first parameter into the Topic attribute, is name of the default pub/sub configured by the Dapr CLI.
[Topic("pubsub", "rawDeposit", true)]
[HttpPost("rawDeposit")]
- public async Task> RawDeposit([FromBody] JsonDocument rawTransaction, [FromServices] DaprClient daprClient)
+ public async Task> RawDeposit([FromBody] JsonDocument rawTransaction,
+ [FromServices] DaprClient daprClient)
{
var transactionString = rawTransaction.RootElement.GetProperty("data_base64").GetString();
- logger.LogInformation($"Enter deposit: {transactionString} - {Encoding.UTF8.GetString(Convert.FromBase64String(transactionString))}");
+ logger.LogInformation(
+ $"Enter deposit: {transactionString} - {Encoding.UTF8.GetString(Convert.FromBase64String(transactionString))}");
var transactionJson = JsonSerializer.Deserialize(Convert.FromBase64String(transactionString));
- var transaction = JsonSerializer.Deserialize(transactionJson.RootElement.GetProperty("data").GetRawText());
+ var transaction =
+ JsonSerializer.Deserialize(transactionJson.RootElement.GetProperty("data").GetRawText());
var state = await daprClient.GetStateEntryAsync(StoreName, transaction.Id);
state.Value ??= new Account() { Id = transaction.Id, };
logger.LogInformation("Id is {0}, the amount to be deposited is {1}", transaction.Id, transaction.Amount);
@@ -239,7 +257,8 @@ public async Task> RawDeposit([FromBody] JsonDocument rawT
/// Method for returning a BadRequest result which will cause Dapr sidecar to throw an RpcException
///
[HttpPost("throwException")]
- public async Task> ThrowException(Transaction transaction, [FromServices] DaprClient daprClient)
+ public async Task> ThrowException(Transaction transaction,
+ [FromServices] DaprClient daprClient)
{
logger.LogInformation("Enter ThrowException");
var task = Task.Delay(10);
diff --git a/examples/AspNetCore/ControllerSample/CustomTopicAttribute.cs b/examples/AspNetCore/ControllerSample/CustomTopicAttribute.cs
index 96eb918fb..5c9996aea 100644
--- a/examples/AspNetCore/ControllerSample/CustomTopicAttribute.cs
+++ b/examples/AspNetCore/ControllerSample/CustomTopicAttribute.cs
@@ -11,6 +11,8 @@
// limitations under the License.
// ------------------------------------------------------------------------
+using Dapr.AspNetCore;
+
namespace ControllerSample
{
using System;
diff --git a/examples/AspNetCore/ControllerSample/Startup.cs b/examples/AspNetCore/ControllerSample/Startup.cs
index 11b81d8b3..ddc6d1c5f 100644
--- a/examples/AspNetCore/ControllerSample/Startup.cs
+++ b/examples/AspNetCore/ControllerSample/Startup.cs
@@ -11,6 +11,11 @@
// limitations under the License.
// ------------------------------------------------------------------------
+
+using Dapr;
+using Dapr.AspNetCore;
+
+
namespace ControllerSample
{
using Microsoft.AspNetCore.Builder;
@@ -61,7 +66,10 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseRouting();
- app.UseCloudEvents();
+ app.UseCloudEvents(new CloudEventsMiddlewareOptions
+ {
+ ForwardCloudEventPropertiesAsHeaders = true
+ });
app.UseAuthorization();
diff --git a/examples/AspNetCore/GrpcServiceSample/GrpcServiceSample.csproj b/examples/AspNetCore/GrpcServiceSample/GrpcServiceSample.csproj
index 123763489..2319f6a56 100644
--- a/examples/AspNetCore/GrpcServiceSample/GrpcServiceSample.csproj
+++ b/examples/AspNetCore/GrpcServiceSample/GrpcServiceSample.csproj
@@ -10,15 +10,16 @@
-
-
-
-
-
+
+
+
+
+
+
diff --git a/examples/AspNetCore/GrpcServiceSample/Services/BankingService.cs b/examples/AspNetCore/GrpcServiceSample/Services/BankingService.cs
index 56b80cad6..9518fd610 100644
--- a/examples/AspNetCore/GrpcServiceSample/Services/BankingService.cs
+++ b/examples/AspNetCore/GrpcServiceSample/Services/BankingService.cs
@@ -22,7 +22,7 @@
using GrpcServiceSample.Generated;
using Microsoft.Extensions.Logging;
-namespace GrpcServiceSample
+namespace GrpcServiceSample.Services
{
///
/// BankAccount gRPC service
diff --git a/examples/AspNetCore/GrpcServiceSample/Startup.cs b/examples/AspNetCore/GrpcServiceSample/Startup.cs
index 752d62448..4aa5ac7d3 100644
--- a/examples/AspNetCore/GrpcServiceSample/Startup.cs
+++ b/examples/AspNetCore/GrpcServiceSample/Startup.cs
@@ -11,6 +11,8 @@
// limitations under the License.
// ------------------------------------------------------------------------
+using Dapr.AspNetCore;
+using GrpcServiceSample.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
diff --git a/examples/Client/ConfigurationApi/ConfigurationApi.csproj b/examples/Client/ConfigurationApi/ConfigurationApi.csproj
index dee6a9878..761ebb38f 100644
--- a/examples/Client/ConfigurationApi/ConfigurationApi.csproj
+++ b/examples/Client/ConfigurationApi/ConfigurationApi.csproj
@@ -8,6 +8,7 @@
+
diff --git a/examples/Client/ConfigurationApi/Startup.cs b/examples/Client/ConfigurationApi/Startup.cs
index 62a77ac49..db5b921c9 100644
--- a/examples/Client/ConfigurationApi/Startup.cs
+++ b/examples/Client/ConfigurationApi/Startup.cs
@@ -1,4 +1,5 @@
using System;
+using Dapr.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
diff --git a/examples/Client/Cryptography/Components/azurekeyvault.yaml b/examples/Client/Cryptography/Components/azurekeyvault.yaml
new file mode 100644
index 000000000..5932e0bc8
--- /dev/null
+++ b/examples/Client/Cryptography/Components/azurekeyvault.yaml
@@ -0,0 +1,25 @@
+apiVersion: dapr.io/v1alpha1
+kind: Component
+metadata:
+ name: azurekeyvault
+spec:
+ type: crypto.azure.keyvault
+ metadata:
+ - name: vaultName
+ value: ""
+ - name: azureEnvironment
+ value: AZUREPUBLICCLOUD
+ - name: azureTenantId
+ secretKeyRef:
+ name: read_azure_tenant_id
+ key: read_azure_tenant_id
+ - name: azureClientId
+ secretKeyRef:
+ name: read_azure_client_id
+ key: read_azure_client_id
+ - name: azureClientSecret
+ secretKeyRef:
+ name: read_azure_client_secret
+ key: read_azure_client_secret
+auth:
+ secureStore: envvar-secret-store
\ No newline at end of file
diff --git a/examples/Client/Cryptography/Components/env-secretstore.yaml b/examples/Client/Cryptography/Components/env-secretstore.yaml
new file mode 100644
index 000000000..fb191414d
--- /dev/null
+++ b/examples/Client/Cryptography/Components/env-secretstore.yaml
@@ -0,0 +1,7 @@
+apiVersion: dapr.io/v1alpha1
+kind: Component
+metadata:
+ name: envvar-secret-store
+spec:
+ type: secretstores.local.env
+ version: v1
\ No newline at end of file
diff --git a/examples/Client/Cryptography/Cryptography.csproj b/examples/Client/Cryptography/Cryptography.csproj
new file mode 100644
index 000000000..525c38562
--- /dev/null
+++ b/examples/Client/Cryptography/Cryptography.csproj
@@ -0,0 +1,25 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+ latest
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
\ No newline at end of file
diff --git a/examples/Client/Cryptography/Example.cs b/examples/Client/Cryptography/Example.cs
new file mode 100644
index 000000000..2c2d41626
--- /dev/null
+++ b/examples/Client/Cryptography/Example.cs
@@ -0,0 +1,22 @@
+// ------------------------------------------------------------------------
+// Copyright 2023 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 Cryptography
+{
+ internal abstract class Example
+ {
+ public abstract string DisplayName { get; }
+
+ public abstract Task RunAsync(CancellationToken cancellationToken);
+ }
+}
diff --git a/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs b/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs
new file mode 100644
index 000000000..aa9c404a7
--- /dev/null
+++ b/examples/Client/Cryptography/Examples/EncryptDecryptFileStreamExample.cs
@@ -0,0 +1,77 @@
+// ------------------------------------------------------------------------
+// Copyright 2023 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Buffers;
+using Dapr.Client;
+#pragma warning disable CS0618 // Type or member is obsolete
+
+namespace Cryptography.Examples
+{
+ internal class EncryptDecryptFileStreamExample : Example
+ {
+ public override string DisplayName => "Use Cryptography to encrypt and decrypt a file";
+ public override async Task RunAsync(CancellationToken cancellationToken)
+ {
+ using var client = new DaprClientBuilder().Build();
+
+ const string componentName = "azurekeyvault"; // Change this to match the name of the component containing your vault
+ const string keyName = "myKey";
+
+ // The name of the file we're using as an example
+ const string fileName = "file.txt";
+
+ Console.WriteLine("Original file contents:");
+ foreach (var line in await File.ReadAllLinesAsync(fileName, cancellationToken))
+ {
+ Console.WriteLine(line);
+ }
+ Console.WriteLine();
+
+ //Encrypt from a file stream and buffer the resulting bytes to an in-memory buffer
+ await using var encryptFs = new FileStream(fileName, FileMode.Open);
+
+ var bufferedEncryptedBytes = new ArrayBufferWriter();
+ await foreach (var bytes in (await client.EncryptAsync(componentName, encryptFs, keyName,
+ new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken))
+ .WithCancellation(cancellationToken))
+ {
+ bufferedEncryptedBytes.Write(bytes.Span);
+ }
+
+ Console.WriteLine($"Encrypted bytes: {Convert.ToBase64String(bufferedEncryptedBytes.GetSpan())}");
+ Console.WriteLine();
+
+ //We'll write to a temporary file via a FileStream
+ var tempDecryptedFile = Path.GetTempFileName();
+ await using var decryptFs = new FileStream(tempDecryptedFile, FileMode.Create);
+
+ //We'll stream the decrypted bytes from a MemoryStream into the above temporary file
+ await using var encryptedMs = new MemoryStream(bufferedEncryptedBytes.WrittenMemory.ToArray());
+ await foreach (var result in (await client.DecryptAsync(componentName, encryptedMs, keyName,
+ cancellationToken)).WithCancellation(cancellationToken))
+ {
+ decryptFs.Write(result.Span);
+ }
+
+ decryptFs.Close();
+
+ //Let's confirm the value as written to the file
+ var decryptedValue = await File.ReadAllTextAsync(tempDecryptedFile, cancellationToken);
+ Console.WriteLine($"Decrypted value: ");
+ Console.WriteLine(decryptedValue);
+
+ //And some cleanup to delete our temp file
+ File.Delete(tempDecryptedFile);
+ }
+ }
+}
diff --git a/examples/Client/Cryptography/Examples/EncryptDecryptStringExample.cs b/examples/Client/Cryptography/Examples/EncryptDecryptStringExample.cs
new file mode 100644
index 000000000..a37ca1b8b
--- /dev/null
+++ b/examples/Client/Cryptography/Examples/EncryptDecryptStringExample.cs
@@ -0,0 +1,47 @@
+// ------------------------------------------------------------------------
+// Copyright 2023 The Dapr Authors
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// ------------------------------------------------------------------------
+
+using System.Text;
+using Dapr.Client;
+#pragma warning disable CS0618 // Type or member is obsolete
+
+namespace Cryptography.Examples
+{
+ internal class EncryptDecryptStringExample : Example
+ {
+ public override string DisplayName => "Using Cryptography to encrypt and decrypt a string";
+
+ public override async Task RunAsync(CancellationToken cancellationToken)
+ {
+ using var client = new DaprClientBuilder().Build();
+
+ const string componentName = "azurekeyvault"; //Change this to match the name of the component containing your vault
+ const string keyName = "myKey"; //Change this to match the name of the key in your Vault
+
+
+ const string plaintextStr = "This is the value we're going to encrypt today";
+ Console.WriteLine($"Original string value: '{plaintextStr}'");
+
+ //Encrypt the string
+ var plaintextBytes = Encoding.UTF8.GetBytes(plaintextStr);
+ var encryptedBytesResult = await client.EncryptAsync(componentName, plaintextBytes, keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa),
+ cancellationToken);
+
+ Console.WriteLine($"Encrypted bytes: '{Convert.ToBase64String(encryptedBytesResult.Span)}'");
+
+ //Decrypt the string
+ var decryptedBytes = await client.DecryptAsync(componentName, encryptedBytesResult, keyName, new DecryptionOptions(), cancellationToken);
+ Console.WriteLine($"Decrypted string: '{Encoding.UTF8.GetString(decryptedBytes.ToArray())}'");
+ }
+ }
+}
diff --git a/examples/Client/Cryptography/Program.cs b/examples/Client/Cryptography/Program.cs
new file mode 100644
index 000000000..da81bef8f
--- /dev/null
+++ b/examples/Client/Cryptography/Program.cs
@@ -0,0 +1,46 @@
+// ------------------------------------------------------------------------
+// Copyright 2023 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 Cryptography.Examples;
+
+namespace Cryptography
+{
+ class Program
+ {
+ private static readonly Example[] Examples = new Example[]
+ {
+ new EncryptDecryptStringExample(),
+ new EncryptDecryptFileStreamExample()
+ };
+
+ static async Task Main(string[] args)
+ {
+ if (args.Length > 0 && int.TryParse(args[0], out var index) && index >= 0 && index < Examples.Length)
+ {
+ var cts = new CancellationTokenSource();
+ Console.CancelKeyPress += (object? sender, ConsoleCancelEventArgs e) => cts.Cancel();
+
+ await Examples[index].RunAsync(cts.Token);
+ return 0;
+ }
+
+ Console.WriteLine("Hello, please choose a sample to run:");
+ for (var i = 0; i < Examples.Length; i++)
+ {
+ Console.WriteLine($"{i}: {Examples[i].DisplayName}");
+ }
+ Console.WriteLine();
+ return 1;
+ }
+ }
+}
diff --git a/examples/Client/Cryptography/README.md b/examples/Client/Cryptography/README.md
new file mode 100644
index 000000000..c0c884369
--- /dev/null
+++ b/examples/Client/Cryptography/README.md
@@ -0,0 +1,92 @@
+# Dapr .NET SDK Cryptography example
+
+## Prerequisites
+
+- [.NET 8+](https://dotnet.microsoft.com/download) installed
+- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli)
+- [Initialized Dapr environment](https://docs.dapr.io/getting-started/installation)
+- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/)
+- [Azure Key Vault instance](https://learn.microsoft.com/en-us/azure/key-vault/general/quick-create-portal)
+- [Entra Service Principal](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app)
+
+### Service Principal/Environment Variables Setup
+In your Azure portal, open Microsoft Entra ID and click `App Registrations`. Click the button at the top to create a new registration. Select a name for your service principal
+and click register, noting this name for later.
+
+Once the registration is completed, open it from the list and select Certificates & Secrets from the left navigation. Select "Client secrets" from the page body (middle column)
+and click the button to add a new client secret giving it an optional description and changing the expiry date as you desire. Click Add to create the secret. Record the secret
+value it shows you - it will not be shown to you again without creating another client secret.
+
+Click Overview from the left navigation and record the "Application (client) ID" and the "Directory (tenant) ID" values.
+
+On your computer (assuming Windows), open your start menu and type "Environment Variables". An option should appear named "Edit the system environment variables". Select this
+and your System Properties window will open. Click the "Environment Variables" button in the bottom and said window will appear. Click the "New..." button under System variables
+to add the requisite service principal values to your environment variables. You can change these names as to want by updating the `./Components/azurekeyvault.yaml` names, but for now
+configure as follows:
+
+| Variable Name | Value |
+|--|--|
+| read_azure_client_id | Paste the value from your app registration overview for "Application (client) ID" |
+| read_azure_client_secret | Paste the value of the client secret you generated for your app registration |
+| read_azure_tenant_id | Paste the valeu from your app registration overview for "Directory (tenant) ID" |
+
+Click OK to save your environment variables and to close your System Properties window. You may need to close restart your command line tool for it to recognize the new values.
+
+### Azure Key Vault Setup
+
+This example is implemented using the Azure Key Vault and will not work without it. Assuming you have a Key Vault instance configured, ensure that
+you have the `Key Vault Crypto Officer` role assigned to yourself as you'll need to in order to generate a new key in the instance. After selecting Keys
+under the Objects header, click the `Generate/Import` button at the top of the instance panel.
+
+Under options, select `Generate` and name your key. This example is pre-configured to assume a key name of 'myKey', but feel free to change this (but also update the name in the example
+you wish to run). The other default options are fine for our purposes, so click Create at the bottom and if you've got the appropriate roles, it will show up in the list of Keys.
+
+Update your `./Components/azurekeyvault.yaml` file with the name of your Key Vault under `vaultName` where it currently reads "changeMe". This sample assumes authentication
+via a service principal, so you might also need to set this up.
+
+Back in the Azure Portal, assign at least the `Key Vault Crypto User` role to the service principal you previously created in the last step. Do this by clicking
+`Access Control (IAM)` from the left navigation, clicking "Add" from the top and clicking "Add Role Assignment". Select `Key Vault Crypto User` from the list and click the Next
+button. Ensuring that the "User, group or service principal" option is selected, click the "Select members" link and search for the name of the app registration you created. Click
+Add to add this service principal to the list of members for the new role assignment and click Review + Assign twice to assign the role. This will take effect within a few seconds
+or minutes. This step ensures that while Dapr can authenticate as your service principal, that it also has permission to access and use the key in your Key Vault.
+
+## Running the example
+
+To run the sample locally, run this command in the DaprClient directory:
+
+```sh
+dapr run --resources-path ./Components --app-id DaprClient -- dotnet run
+```
+
+Running the following command will output a list of the samples included:
+
+```sh
+dapr run --resources-path ./Components --app-id DaprClient -- dotnet run
+```
+
+Press Ctrl+C to exit, and then run the command again and provide a sample number to run the samples.
+
+For example, run this command to run the first sample from the list produced earlier (the 0th example):
+
+```sh
+dapr run --resources-path ./Components --app-id DaprClient -- dotnet run 0
+```
+
+## Encryption/Decryption with strings
+See [EncryptDecryptStringExample.cs](./EncryptDecryptStringExample.cs) for an example of using `DaprClient` for basic
+string-based encryption and decryption operations as performed against UTF-8 encoded byte arrays.
+
+## Encryption/Decryption with streams
+See [EncryptDecryptFileStreamExample.cs](./EncryptDecryptFileStreamExample.cs) for an example of using `DaprClient`
+to perform an encrypt and decrypt operation against a stream of data. In the example, we stream a local file to the
+sidecar to encrypt and write the result (as it's streamed back) to an in-memory buffer. Once the operation fully
+completes, we perform the decrypt operation against this in-memory buffer and write the decrypted result back out to a
+temporary file.
+
+In either operation, rather than load the entire stream into memory and send all at once to the
+sidecar as we do in the other string-based example (as this might cause you to run out of memory either on the
+node the app is running on or do the same to the sidecar itself), this example instead breaks the input stream into
+more manageable 4KB chunks (a value you can override via the `EncryptionOptions` or `DecryptionOptions` parameters
+respectively up to 64KB. Further, rather than waiting for the entire stream to send to the sidecar before the
+encryption operation proceeds, it immediately works to process the sidecar response, continuing to minimize resource
+usage.
diff --git a/examples/Client/Cryptography/file.txt b/examples/Client/Cryptography/file.txt
new file mode 100644
index 000000000..9e8638939
--- /dev/null
+++ b/examples/Client/Cryptography/file.txt
@@ -0,0 +1,26 @@
+# The Road Not Taken
+## By Robert Lee Frost
+
+Two roads diverged in a yellow wood,
+And sorry I could not travel both
+And be one traveler, long I stood
+And looked down one as far as I could
+To where it bent in the undergrowth;
+
+Then took the other, as just as fair
+And having perhaps the better claim,
+Because it was grassy and wanted wear;
+Though as for that, the passing there
+Had worn them really about the same,
+
+And both that morning equally lay
+In leaves no step had trodden black
+Oh, I kept the first for another day!
+Yet knowing how way leads on to way,
+I doubted if I should ever come back.
+
+I shall be telling this with a sigh
+Somewhere ages and ages hence:
+Two roads diverged in a wood, and I,
+I took the one less traveled by,
+And that has made all the difference.
\ No newline at end of file
diff --git a/examples/Client/DistributedLock/DistributedLock.csproj b/examples/Client/DistributedLock/DistributedLock.csproj
index 9c3272e6e..4c04fb907 100644
--- a/examples/Client/DistributedLock/DistributedLock.csproj
+++ b/examples/Client/DistributedLock/DistributedLock.csproj
@@ -3,6 +3,7 @@
+
diff --git a/examples/Client/DistributedLock/Startup.cs b/examples/Client/DistributedLock/Startup.cs
index 0309af0f5..9f40e4752 100644
--- a/examples/Client/DistributedLock/Startup.cs
+++ b/examples/Client/DistributedLock/Startup.cs
@@ -1,4 +1,5 @@
-using DistributedLock.Services;
+using Dapr.AspNetCore;
+using DistributedLock.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
diff --git a/examples/Client/PublishSubscribe/BulkPublishEventExample/BulkPublishEventExample.csproj b/examples/Client/PublishSubscribe/BulkPublishEventExample/BulkPublishEventExample.csproj
index 3f22acaf8..b1e7647c7 100644
--- a/examples/Client/PublishSubscribe/BulkPublishEventExample/BulkPublishEventExample.csproj
+++ b/examples/Client/PublishSubscribe/BulkPublishEventExample/BulkPublishEventExample.csproj
@@ -16,9 +16,9 @@
-
-
-
+
+
+
diff --git a/examples/Client/PublishSubscribe/BulkPublishEventExample/README.md b/examples/Client/PublishSubscribe/BulkPublishEventExample/README.md
index dfcc99ca6..39d206fa2 100644
--- a/examples/Client/PublishSubscribe/BulkPublishEventExample/README.md
+++ b/examples/Client/PublishSubscribe/BulkPublishEventExample/README.md
@@ -2,7 +2,7 @@
## Prerequisites
-- [.NET Core 3.1 or .NET 5+](https://dotnet.microsoft.com/download) installed
+- [.NET 6+](https://dotnet.microsoft.com/download) installed
- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/)
- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/)
- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/)
diff --git a/examples/Client/PublishSubscribe/PublishEventExample/PublishEventExample.csproj b/examples/Client/PublishSubscribe/PublishEventExample/PublishEventExample.csproj
index 2df4ec967..52b77a3e5 100644
--- a/examples/Client/PublishSubscribe/PublishEventExample/PublishEventExample.csproj
+++ b/examples/Client/PublishSubscribe/PublishEventExample/PublishEventExample.csproj
@@ -16,9 +16,9 @@
-
-
-
+
+
+
diff --git a/examples/Client/PublishSubscribe/PublishEventExample/README.md b/examples/Client/PublishSubscribe/PublishEventExample/README.md
index 455fc2537..9f3af565f 100644
--- a/examples/Client/PublishSubscribe/PublishEventExample/README.md
+++ b/examples/Client/PublishSubscribe/PublishEventExample/README.md
@@ -2,7 +2,7 @@
## Prerequisites
-- [.NET Core 3.1 or .NET 5+](https://dotnet.microsoft.com/download) installed
+- [.NET 6+](https://dotnet.microsoft.com/download) installed
- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/)
- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/)
- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/)
diff --git a/examples/Client/ServiceInvocation/ServiceInvocation.csproj b/examples/Client/ServiceInvocation/ServiceInvocation.csproj
index e3df962a1..7b165835e 100644
--- a/examples/Client/ServiceInvocation/ServiceInvocation.csproj
+++ b/examples/Client/ServiceInvocation/ServiceInvocation.csproj
@@ -16,9 +16,9 @@
-
-
-
+
+
+
diff --git a/examples/Client/StateManagement/StateManagement.csproj b/examples/Client/StateManagement/StateManagement.csproj
index e3df962a1..7b165835e 100644
--- a/examples/Client/StateManagement/StateManagement.csproj
+++ b/examples/Client/StateManagement/StateManagement.csproj
@@ -16,9 +16,9 @@
-
-
-
+
+
+
diff --git a/examples/GeneratedActor/ActorClient/ActorClient.csproj b/examples/GeneratedActor/ActorClient/ActorClient.csproj
new file mode 100644
index 000000000..73b5c2027
--- /dev/null
+++ b/examples/GeneratedActor/ActorClient/ActorClient.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net6
+ 10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/GeneratedActor/ActorClient/IClientActor.cs b/examples/GeneratedActor/ActorClient/IClientActor.cs
new file mode 100644
index 000000000..c5c732cb9
--- /dev/null
+++ b/examples/GeneratedActor/ActorClient/IClientActor.cs
@@ -0,0 +1,28 @@
+// ------------------------------------------------------------------------
+// Copyright 2023 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.Actors.Generators;
+
+namespace GeneratedActor;
+
+internal sealed record ClientState(string Value);
+
+[GenerateActorClient]
+internal interface IClientActor
+{
+ [ActorMethod(Name = "GetState")]
+ Task GetStateAsync(CancellationToken cancellationToken = default);
+
+ [ActorMethod(Name = "SetState")]
+ Task SetStateAsync(ClientState state, CancellationToken cancellationToken = default);
+}
diff --git a/examples/GeneratedActor/ActorClient/Program.cs b/examples/GeneratedActor/ActorClient/Program.cs
new file mode 100644
index 000000000..87f714907
--- /dev/null
+++ b/examples/GeneratedActor/ActorClient/Program.cs
@@ -0,0 +1,30 @@
+// ------------------------------------------------------------------------
+// Copyright 2023 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.Actors;
+using Dapr.Actors.Client;
+using GeneratedActor;
+
+Console.WriteLine("Testing generated client...");
+
+var proxy = ActorProxy.Create(ActorId.CreateRandom(), "RemoteActor");
+
+using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
+
+var client = new ClientActorClient(proxy);
+
+var state = await client.GetStateAsync(cancellationTokenSource.Token);
+
+await client.SetStateAsync(new ClientState("Hello, World!"), cancellationTokenSource.Token);
+
+Console.WriteLine("Done!");
diff --git a/examples/GeneratedActor/ActorCommon/ActorCommon.csproj b/examples/GeneratedActor/ActorCommon/ActorCommon.csproj
new file mode 100644
index 000000000..2cbc61e2c
--- /dev/null
+++ b/examples/GeneratedActor/ActorCommon/ActorCommon.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net6
+ 10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/examples/GeneratedActor/ActorCommon/IRemoteActor.cs b/examples/GeneratedActor/ActorCommon/IRemoteActor.cs
new file mode 100644
index 000000000..6d136a704
--- /dev/null
+++ b/examples/GeneratedActor/ActorCommon/IRemoteActor.cs
@@ -0,0 +1,25 @@
+// ------------------------------------------------------------------------
+// Copyright 2023 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.Actors;
+
+namespace GeneratedActor;
+
+public sealed record RemoteState(string Value);
+
+public interface IRemoteActor : IActor
+{
+ Task GetState();
+
+ Task SetState(RemoteState state);
+}
diff --git a/examples/GeneratedActor/ActorService/ActorService.csproj b/examples/GeneratedActor/ActorService/ActorService.csproj
new file mode 100644
index 000000000..a74104363
--- /dev/null
+++ b/examples/GeneratedActor/ActorService/ActorService.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net6
+ 10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/examples/GeneratedActor/ActorService/Program.cs b/examples/GeneratedActor/ActorService/Program.cs
new file mode 100644
index 000000000..f6e62f720
--- /dev/null
+++ b/examples/GeneratedActor/ActorService/Program.cs
@@ -0,0 +1,36 @@
+// ------------------------------------------------------------------------
+// Copyright 2023 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 GeneratedActor;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Services.AddActors(
+ options =>
+ {
+ options.UseJsonSerialization = true;
+ options.Actors.RegisterActor();
+ });
+
+var app = builder.Build();
+
+app.UseRouting();
+
+#pragma warning disable ASP0014
+app.UseEndpoints(
+ endpoints =>
+ {
+ endpoints.MapActorsHandlers();
+ });
+
+app.Run();
diff --git a/examples/GeneratedActor/ActorService/Properties/launchSettings.json b/examples/GeneratedActor/ActorService/Properties/launchSettings.json
new file mode 100644
index 000000000..8fbb1f581
--- /dev/null
+++ b/examples/GeneratedActor/ActorService/Properties/launchSettings.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:56372",
+ "sslPort": 0
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:5226",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/examples/GeneratedActor/ActorService/RemoteActor.cs b/examples/GeneratedActor/ActorService/RemoteActor.cs
new file mode 100644
index 000000000..f04921f69
--- /dev/null
+++ b/examples/GeneratedActor/ActorService/RemoteActor.cs
@@ -0,0 +1,45 @@
+// ------------------------------------------------------------------------
+// Copyright 2023 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.Actors.Runtime;
+
+namespace GeneratedActor;
+
+internal sealed class RemoteActor : Actor, IRemoteActor
+{
+ private readonly ILogger logger;
+
+ private RemoteState currentState = new("default");
+
+ public RemoteActor(ActorHost host, ILogger logger)
+ : base(host)
+ {
+ this.logger = logger;
+ }
+
+ public Task GetState()
+ {
+ this.logger.LogInformation("GetStateAsync called.");
+
+ return Task.FromResult(this.currentState);
+ }
+
+ public Task SetState(RemoteState state)
+ {
+ this.logger.LogInformation("SetStateAsync called.");
+
+ this.currentState = state;
+
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/examples/GeneratedActor/ActorService/appsettings.Development.json b/examples/GeneratedActor/ActorService/appsettings.Development.json
new file mode 100644
index 000000000..ff66ba6b2
--- /dev/null
+++ b/examples/GeneratedActor/ActorService/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/examples/GeneratedActor/ActorService/appsettings.json b/examples/GeneratedActor/ActorService/appsettings.json
new file mode 100644
index 000000000..4d566948d
--- /dev/null
+++ b/examples/GeneratedActor/ActorService/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/examples/GeneratedActor/README.md b/examples/GeneratedActor/README.md
new file mode 100644
index 000000000..cd595b30e
--- /dev/null
+++ b/examples/GeneratedActor/README.md
@@ -0,0 +1,115 @@
+# Generated Actor Client Example
+
+An example of generating a strongly-typed actor client.
+
+## Prerequisites
+
+- [.NET 6+](https://dotnet.microsoft.com/download) installed
+- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/)
+- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/)
+- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/)
+
+## Overview
+
+Two options for invoking actor methods exist in the Dapr .NET SDK, a strongly-type (remoting) option and a loosely-typed (non-remoting) option. Each has its own advantages and disadvantages. A "middle" option also exists that combines the two and gains benefits of both without some of the disadvantages of either. Using .NET Source Generators, the Dapr .NET SDK can generate a strongly-typed client implementation that uses loosely-typed method invocation under the covers.
+
+Strongly-typed clients are generated by:
+
+1. Referencing the `Dapr.Actors.Generators` NuGet package.
+
+ ```xml
+
+
+
+
+
+ ```
+
+1. Add the `Dapr.Actors.Generators.GenerateActorClientAttribute` to the actor interface.
+
+ ```csharp
+ using Dapr.Actors.Generators;
+
+ namespace Sample;
+
+ internal sealed record SampleState(string Value);
+
+ [GenerateActorClient]
+ internal interface ISampleActor
+ {
+ [ActorMethod(Name = "GetState")]
+ Task GetStateAsync(CancellationToken cancellationToken = default);
+
+ [ActorMethod(Name = "SetState")]
+ Task SetStateAsync(SampleState state, CancellationToken cancellationToken = default);
+ }
+ ```
+
+ > The `Dapr.Actors.Generators.ActorMethodAttribute` can be used to map interface methods definitions to specific actor methods should the names differ (e.g. the interface uses "Async" suffix common in .NET but the actor methods do not).
+
+1. A strongly-typed client will be generated that can be used to invoke actor methods.
+
+ ```csharp
+ using Dapr.Actors;
+ using Dapr.Actors.Client;
+ using Sample;
+
+ var proxy = ActorProxy.Create(ActorId.CreateRandom(), "SampleActor");
+
+ using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
+
+ var client = new SampleActorClient(proxy);
+
+ var state = await client.GetStateAsync(cancellationTokenSource.Token);
+
+ await client.SetStateAsync(new SampleState("Hello, World!"), cancellationTokenSource.Token);
+
+ ```
+
+## Run the example
+
+### Start the ActorService
+
+Change directory to the `ActorService` folder:
+
+```bash
+cd examples/GeneratedActor/ActorService
+```
+
+To start the `ActorService`, execute the following command:
+
+```bash
+dapr run --app-id generated-service --app-port 5226 -- dotnet run
+```
+
+### Run the ActorClient
+
+Change directory to the `ActorClient` folder:
+
+```bash
+cd examples/GeneratedActor/ActorClient
+```
+
+To run the `ActorClient`, execute the following command:
+
+```bash
+dapr run --app-id generated-client -- dotnet run
+```
+
+### Expected output
+
+You should see the following output from the `ActorClient`:
+
+```
+== APP == Testing generated client...
+== APP == Done!
+```
+
+You should see also see the following output from the `ActorService`:
+
+```
+== APP == info: GeneratedActor.RemoteActor[0]
+== APP == GetStateAsync called.
+== APP == info: GeneratedActor.RemoteActor[0]
+== APP == SetStateAsync called.
+```
\ No newline at end of file
diff --git a/examples/Workflow/WorkflowConsoleApp/Activities/ProcessPaymentActivity.cs b/examples/Workflow/WorkflowConsoleApp/Activities/ProcessPaymentActivity.cs
index dc4cc531b..1ddb51bbf 100644
--- a/examples/Workflow/WorkflowConsoleApp/Activities/ProcessPaymentActivity.cs
+++ b/examples/Workflow/WorkflowConsoleApp/Activities/ProcessPaymentActivity.cs
@@ -1,7 +1,6 @@
using Dapr.Client;
using Dapr.Workflow;
using Microsoft.Extensions.Logging;
-using WorkflowConsoleApp.Models;
namespace WorkflowConsoleApp.Activities
{
diff --git a/examples/Workflow/WorkflowConsoleApp/Activities/RequestApprovalActivity.cs b/examples/Workflow/WorkflowConsoleApp/Activities/RequestApprovalActivity.cs
index af0b1fa13..d40078fc8 100644
--- a/examples/Workflow/WorkflowConsoleApp/Activities/RequestApprovalActivity.cs
+++ b/examples/Workflow/WorkflowConsoleApp/Activities/RequestApprovalActivity.cs
@@ -1,6 +1,5 @@
using Dapr.Workflow;
using Microsoft.Extensions.Logging;
-using WorkflowConsoleApp.Models;
namespace WorkflowConsoleApp.Activities
{
diff --git a/examples/Workflow/WorkflowConsoleApp/Activities/ReserveInventoryActivity.cs b/examples/Workflow/WorkflowConsoleApp/Activities/ReserveInventoryActivity.cs
index fc6c48921..cdae1c6ed 100644
--- a/examples/Workflow/WorkflowConsoleApp/Activities/ReserveInventoryActivity.cs
+++ b/examples/Workflow/WorkflowConsoleApp/Activities/ReserveInventoryActivity.cs
@@ -1,7 +1,6 @@
using Dapr.Client;
using Dapr.Workflow;
using Microsoft.Extensions.Logging;
-using WorkflowConsoleApp.Models;
namespace WorkflowConsoleApp.Activities
{
diff --git a/examples/Workflow/WorkflowConsoleApp/Activities/UpdateInventoryActivity.cs b/examples/Workflow/WorkflowConsoleApp/Activities/UpdateInventoryActivity.cs
index 947dab6cb..c035aadde 100644
--- a/examples/Workflow/WorkflowConsoleApp/Activities/UpdateInventoryActivity.cs
+++ b/examples/Workflow/WorkflowConsoleApp/Activities/UpdateInventoryActivity.cs
@@ -1,6 +1,5 @@
using Dapr.Client;
using Dapr.Workflow;
-using WorkflowConsoleApp.Models;
using Microsoft.Extensions.Logging;
namespace WorkflowConsoleApp.Activities
diff --git a/examples/Workflow/WorkflowConsoleApp/Models.cs b/examples/Workflow/WorkflowConsoleApp/Models.cs
index 6c9583d84..7892c7525 100644
--- a/examples/Workflow/WorkflowConsoleApp/Models.cs
+++ b/examples/Workflow/WorkflowConsoleApp/Models.cs
@@ -1,4 +1,4 @@
-namespace WorkflowConsoleApp.Models
+namespace WorkflowConsoleApp
{
public record OrderPayload(string Name, double TotalCost, int Quantity = 1);
public record InventoryRequest(string RequestId, string ItemName, int Quantity);
diff --git a/examples/Workflow/WorkflowConsoleApp/Program.cs b/examples/Workflow/WorkflowConsoleApp/Program.cs
index 055b1b4c1..26d34615d 100644
--- a/examples/Workflow/WorkflowConsoleApp/Program.cs
+++ b/examples/Workflow/WorkflowConsoleApp/Program.cs
@@ -1,12 +1,12 @@
using Dapr.Client;
using Dapr.Workflow;
using WorkflowConsoleApp.Activities;
-using WorkflowConsoleApp.Models;
using WorkflowConsoleApp.Workflows;
using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using WorkflowConsoleApp;
const string StoreName = "statestore";
-const string DaprWorkflowComponent = "dapr";
// The workflow host is a background service that connects to the sidecar over gRPC
var builder = Host.CreateDefaultBuilder(args).ConfigureServices(services =>
@@ -124,6 +124,8 @@
amount = 1;
}
+ var daprWorkflowClient = host.Services.GetRequiredService();
+
// Construct the order with a unique order ID
string orderId = $"{itemName.ToLowerInvariant()}-{Guid.NewGuid().ToString()[..8]}";
double totalCost = amount * item.PerItemCost;
@@ -131,18 +133,16 @@
// Start the workflow using the order ID as the workflow ID
Console.WriteLine($"Starting order workflow '{orderId}' purchasing {amount} {itemName}");
- await daprClient.StartWorkflowAsync(
- workflowComponent: DaprWorkflowComponent,
- workflowName: nameof(OrderProcessingWorkflow),
+ await daprWorkflowClient.ScheduleNewWorkflowAsync(
+ name: nameof(OrderProcessingWorkflow),
input: orderInfo,
instanceId: orderId);
// Wait for the workflow to start and confirm the input
- GetWorkflowResponse state = await daprClient.WaitForWorkflowStartAsync(
- instanceId: orderId,
- workflowComponent: DaprWorkflowComponent);
+ WorkflowState state = await daprWorkflowClient.WaitForWorkflowStartAsync(
+ instanceId: orderId);
- Console.WriteLine($"{state.WorkflowName} (ID = {orderId}) started successfully with {state.ReadInputAs()}");
+ Console.WriteLine($"{nameof(OrderProcessingWorkflow)} (ID = {orderId}) started successfully with {state.ReadInputAs()}");
// Wait for the workflow to complete
while (true)
@@ -150,22 +150,20 @@ await daprClient.StartWorkflowAsync(
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
- state = await daprClient.WaitForWorkflowCompletionAsync(
+ state = await daprWorkflowClient.WaitForWorkflowCompletionAsync(
instanceId: orderId,
- workflowComponent: DaprWorkflowComponent,
- cancellationToken: cts.Token);
+ cancellation: cts.Token);
break;
}
catch (OperationCanceledException)
{
// Check to see if the workflow is blocked waiting for an approval
- state = await daprClient.GetWorkflowAsync(
- instanceId: orderId,
- workflowComponent: DaprWorkflowComponent);
- if (state.Properties.TryGetValue("dapr.workflow.custom_status", out string customStatus) &&
- customStatus.Contains("Waiting for approval"))
+ state = await daprWorkflowClient.GetWorkflowStateAsync(
+ instanceId: orderId);
+
+ if(state.ReadCustomStatusAs()?.Contains("Waiting for approval") == true)
{
- Console.WriteLine($"{state.WorkflowName} (ID = {orderId}) requires approval. Approve? [Y/N]");
+ Console.WriteLine($"{nameof(OrderProcessingWorkflow)} (ID = {orderId}) requires approval. Approve? [Y/N]");
string approval = Console.ReadLine();
ApprovalResult approvalResult = ApprovalResult.Unspecified;
if (string.Equals(approval, "Y", StringComparison.OrdinalIgnoreCase))
@@ -182,11 +180,10 @@ await daprClient.StartWorkflowAsync(
if (approvalResult != ApprovalResult.Unspecified)
{
// Raise the workflow event to the workflow
- await daprClient.RaiseWorkflowEventAsync(
+ await daprWorkflowClient.RaiseEventAsync(
instanceId: orderId,
- workflowComponent: DaprWorkflowComponent,
eventName: "ManagerApproval",
- eventData: approvalResult);
+ eventPayload: approvalResult);
}
// otherwise, keep waiting
diff --git a/examples/Workflow/WorkflowConsoleApp/Workflows/OrderProcessingWorkflow.cs b/examples/Workflow/WorkflowConsoleApp/Workflows/OrderProcessingWorkflow.cs
index bd2a710b6..3b8af5951 100644
--- a/examples/Workflow/WorkflowConsoleApp/Workflows/OrderProcessingWorkflow.cs
+++ b/examples/Workflow/WorkflowConsoleApp/Workflows/OrderProcessingWorkflow.cs
@@ -1,6 +1,5 @@
using Dapr.Workflow;
using WorkflowConsoleApp.Activities;
-using WorkflowConsoleApp.Models;
namespace WorkflowConsoleApp.Workflows
{
diff --git a/examples/Workflow/WorkflowUnitTest/OrderProcessingTests.cs b/examples/Workflow/WorkflowUnitTest/OrderProcessingTests.cs
index ac53c4081..e38a0c940 100644
--- a/examples/Workflow/WorkflowUnitTest/OrderProcessingTests.cs
+++ b/examples/Workflow/WorkflowUnitTest/OrderProcessingTests.cs
@@ -1,8 +1,8 @@
using System.Threading.Tasks;
using Dapr.Workflow;
using Moq;
+using WorkflowConsoleApp;
using WorkflowConsoleApp.Activities;
-using WorkflowConsoleApp.Models;
using WorkflowConsoleApp.Workflows;
using Xunit;
diff --git a/examples/Workflow/WorkflowUnitTest/WorkflowUnitTest.csproj b/examples/Workflow/WorkflowUnitTest/WorkflowUnitTest.csproj
index 4ce0c9801..dec14a713 100644
--- a/examples/Workflow/WorkflowUnitTest/WorkflowUnitTest.csproj
+++ b/examples/Workflow/WorkflowUnitTest/WorkflowUnitTest.csproj
@@ -7,14 +7,14 @@
-
-
-
-
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitiveall
-
+ runtime; build; native; contentfiles; analyzers; buildtransitiveall
diff --git a/properties/dapr_managed_netcore.props b/properties/dapr_managed_netcore.props
index 3bafcb50c..6e8c01bfe 100644
--- a/properties/dapr_managed_netcore.props
+++ b/properties/dapr_managed_netcore.props
@@ -53,7 +53,7 @@
-
+ allruntime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/samples/Client/README.md b/samples/Client/README.md
index 2501bfadc..2bb738d89 100644
--- a/samples/Client/README.md
+++ b/samples/Client/README.md
@@ -8,7 +8,7 @@ The following examples will show you how to:
## Prerequisites
-* [.Net Core 3.1 or .NET 5+](https://dotnet.microsoft.com/download)
+* [.NET 6+](https://dotnet.microsoft.com/download)
* [Dapr CLI](https://github.com/dapr/cli)
* [Dapr DotNet SDK](https://github.com/dapr/dotnet-sdk)
diff --git a/src/Dapr.Actors.AspNetCore/ActorsEndpointRouteBuilderExtensions.cs b/src/Dapr.Actors.AspNetCore/ActorsEndpointRouteBuilderExtensions.cs
index 55d161d9a..574a172a8 100644
--- a/src/Dapr.Actors.AspNetCore/ActorsEndpointRouteBuilderExtensions.cs
+++ b/src/Dapr.Actors.AspNetCore/ActorsEndpointRouteBuilderExtensions.cs
@@ -103,7 +103,7 @@ private static IEndpointConventionBuilder MapActorMethodEndpoint(this IEndpointR
try
{
- var (header, body) = await runtime.DispatchWithRemotingAsync(actorTypeName, actorId, methodName, daprActorheader, context.Request.Body);
+ var (header, body) = await runtime.DispatchWithRemotingAsync(actorTypeName, actorId, methodName, daprActorheader, context.Request.Body, context.RequestAborted);
// Item 1 is header , Item 2 is body
if (header != string.Empty)
@@ -112,14 +112,14 @@ private static IEndpointConventionBuilder MapActorMethodEndpoint(this IEndpointR
context.Response.Headers[Constants.ErrorResponseHeaderName] = header; // add error header
}
- await context.Response.Body.WriteAsync(body, 0, body.Length); // add response message body
+ await context.Response.Body.WriteAsync(body, 0, body.Length, context.RequestAborted); // add response message body
}
catch (Exception ex)
{
var (header, body) = CreateExceptionResponseMessage(ex);
context.Response.Headers[Constants.ErrorResponseHeaderName] = header;
- await context.Response.Body.WriteAsync(body, 0, body.Length);
+ await context.Response.Body.WriteAsync(body, 0, body.Length, context.RequestAborted);
}
finally
{
@@ -130,7 +130,7 @@ private static IEndpointConventionBuilder MapActorMethodEndpoint(this IEndpointR
{
try
{
- await runtime.DispatchWithoutRemotingAsync(actorTypeName, actorId, methodName, context.Request.Body, context.Response.Body);
+ await runtime.DispatchWithoutRemotingAsync(actorTypeName, actorId, methodName, context.Request.Body, context.Response.Body, context.RequestAborted);
}
finally
{
diff --git a/src/Dapr.Actors.AspNetCore/Dapr.Actors.AspNetCore.csproj b/src/Dapr.Actors.AspNetCore/Dapr.Actors.AspNetCore.csproj
index 1114b7828..82c5863db 100644
--- a/src/Dapr.Actors.AspNetCore/Dapr.Actors.AspNetCore.csproj
+++ b/src/Dapr.Actors.AspNetCore/Dapr.Actors.AspNetCore.csproj
@@ -1,12 +1,6 @@
-
-
diff --git a/src/Dapr.Actors.Generators/ActorClientGenerator.cs b/src/Dapr.Actors.Generators/ActorClientGenerator.cs
new file mode 100644
index 000000000..349d80188
--- /dev/null
+++ b/src/Dapr.Actors.Generators/ActorClientGenerator.cs
@@ -0,0 +1,303 @@
+// ------------------------------------------------------------------------
+// Copyright 2023 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.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Dapr.Actors.Generators;
+
+///
+/// Generates strongly-typed actor clients that use the non-remoting actor proxy.
+///
+[Generator]
+public sealed class ActorClientGenerator : ISourceGenerator
+{
+ private const string GeneratorsNamespace = "Dapr.Actors.Generators";
+
+ private const string ActorMethodAttributeTypeName = "ActorMethodAttribute";
+ private const string ActorMethodAttributeFullTypeName = GeneratorsNamespace + "." + ActorMethodAttributeTypeName;
+
+ private const string GenerateActorClientAttribute = "GenerateActorClientAttribute";
+ private const string GenerateActorClientAttributeFullTypeName = GeneratorsNamespace + "." + GenerateActorClientAttribute;
+
+ private const string ActorMethodAttributeText = $@"
+ //
+
+ #nullable enable
+
+ using System;
+
+ namespace {GeneratorsNamespace}
+ {{
+ [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
+ internal sealed class ActorMethodAttribute : Attribute
+ {{
+ public string? Name {{ get; set; }}
+ }}
+ }}";
+
+ private const string GenerateActorClientAttributeText = $@"
+ //
+
+ #nullable enable
+
+ using System;
+
+ namespace {GeneratorsNamespace}
+ {{
+ [AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)]
+ internal sealed class GenerateActorClientAttribute : Attribute
+ {{
+ public string? Name {{ get; set; }}
+
+ public string? Namespace {{ get; set; }}
+ }}
+ }}";
+
+ private sealed class ActorInterfaceSyntaxReceiver : ISyntaxContextReceiver
+ {
+ private readonly List models = new();
+
+ public IEnumerable Models => this.models;
+
+ #region ISyntaxContextReceiver Members
+
+ public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
+ {
+ if (context.Node is not InterfaceDeclarationSyntax interfaceDeclarationSyntax
+ || interfaceDeclarationSyntax.AttributeLists.Count == 0)
+ {
+ return;
+ }
+
+ var interfaceSymbol = context.SemanticModel.GetDeclaredSymbol(interfaceDeclarationSyntax) as INamedTypeSymbol;
+
+ if (interfaceSymbol is null
+ || !interfaceSymbol.GetAttributes().Any(a => a.AttributeClass?.ToString() == GenerateActorClientAttributeFullTypeName))
+ {
+ return;
+ }
+
+ this.models.Add(interfaceSymbol);
+ }
+
+ #endregion
+ }
+
+ #region ISourceGenerator Members
+
+ ///
+ public void Execute(GeneratorExecutionContext context)
+ {
+ if (context.SyntaxContextReceiver is not ActorInterfaceSyntaxReceiver actorInterfaceSyntaxReceiver)
+ {
+ return;
+ }
+
+ var actorMethodAttributeSymbol = context.Compilation.GetTypeByMetadataName(ActorMethodAttributeFullTypeName) ?? throw new InvalidOperationException("Could not find ActorMethodAttribute.");
+ var generateActorClientAttributeSymbol = context.Compilation.GetTypeByMetadataName(GenerateActorClientAttributeFullTypeName) ?? throw new InvalidOperationException("Could not find GenerateActorClientAttribute.");
+ var cancellationTokenSymbol = context.Compilation.GetTypeByMetadataName("System.Threading.CancellationToken") ?? throw new InvalidOperationException("Could not find CancellationToken.");
+
+ foreach (var interfaceSymbol in actorInterfaceSyntaxReceiver.Models)
+ {
+ try
+ {
+ var actorInterfaceTypeName = interfaceSymbol.Name;
+ var fullyQualifiedActorInterfaceTypeName = interfaceSymbol.ToString();
+
+ var attributeData = interfaceSymbol.GetAttributes().Single(a => a.AttributeClass?.Equals(generateActorClientAttributeSymbol, SymbolEqualityComparer.Default) == true);
+
+ var accessibility = GetClientAccessibility(interfaceSymbol);
+ var clientTypeName = GetClientName(interfaceSymbol, attributeData);
+ var namespaceName = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "Namespace").Value.Value?.ToString() ?? interfaceSymbol.ContainingNamespace.ToDisplayString();
+
+ var members = interfaceSymbol.GetMembers().OfType().Where(m => m.MethodKind == MethodKind.Ordinary).ToList();
+
+ var methodImplementations = String.Join("\n", members.Select(member => GenerateMethodImplementation(member, actorMethodAttributeSymbol, cancellationTokenSymbol)));
+
+ var source = $@"
+//
+
+namespace {namespaceName}
+{{
+ {accessibility} sealed class {clientTypeName} : {fullyQualifiedActorInterfaceTypeName}
+ {{
+ private readonly Dapr.Actors.Client.ActorProxy actorProxy;
+
+ public {clientTypeName}(Dapr.Actors.Client.ActorProxy actorProxy)
+ {{
+ this.actorProxy = actorProxy;
+ }}
+
+ {methodImplementations}
+ }}
+}}
+";
+ // Add the source code to the compilation
+ context.AddSource($"{namespaceName}.{clientTypeName}.g.cs", source);
+ }
+ catch (DiagnosticsException e)
+ {
+ foreach (var diagnostic in e.Diagnostics)
+ {
+ context.ReportDiagnostic(diagnostic);
+ }
+ }
+ }
+ }
+
+ ///
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ /*
+ while (!Debugger.IsAttached)
+ {
+ System.Threading.Thread.Sleep(500);
+ }
+ */
+
+ context.RegisterForPostInitialization(
+ i =>
+ {
+ i.AddSource($"{ActorMethodAttributeFullTypeName}.g.cs", ActorMethodAttributeText);
+ i.AddSource($"{GenerateActorClientAttributeFullTypeName}.g.cs", GenerateActorClientAttributeText);
+ });
+
+ context.RegisterForSyntaxNotifications(() => new ActorInterfaceSyntaxReceiver());
+ }
+
+ #endregion
+
+ private static string GetClientAccessibility(INamedTypeSymbol interfaceSymbol)
+ {
+ return interfaceSymbol.DeclaredAccessibility switch
+ {
+ Accessibility.Public => "public",
+ Accessibility.Internal => "internal",
+ Accessibility.Private => "private",
+ Accessibility.Protected => "protected",
+ Accessibility.ProtectedAndInternal => "protected internal",
+ _ => throw new InvalidOperationException("Unexpected accessibility.")
+ };
+ }
+
+ private static string GetClientName(INamedTypeSymbol interfaceSymbol, AttributeData attributeData)
+ {
+ string? clientName = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "Name").Value.Value?.ToString();
+
+ clientName ??= $"{(interfaceSymbol.Name.StartsWith("I") ? interfaceSymbol.Name.Substring(1) : interfaceSymbol.Name)}Client";
+
+ return clientName;
+ }
+
+ private static string GenerateMethodImplementation(IMethodSymbol method, INamedTypeSymbol generateActorClientAttributeSymbol, INamedTypeSymbol cancellationTokenSymbol)
+ {
+ int cancellationTokenIndex = method.Parameters.IndexOf(p => p.Type.Equals(cancellationTokenSymbol, SymbolEqualityComparer.Default));
+ var cancellationTokenParameter = cancellationTokenIndex != -1 ? method.Parameters[cancellationTokenIndex] : null;
+
+ if (cancellationTokenParameter is not null && cancellationTokenIndex != method.Parameters.Length - 1)
+ {
+ throw new DiagnosticsException(new[]
+ {
+ Diagnostic.Create(
+ new DiagnosticDescriptor(
+ "DAPR0001",
+ "Invalid method signature.",
+ "Cancellation tokens must be the last argument.",
+ "Dapr.Actors.Generators",
+ DiagnosticSeverity.Error,
+ true),
+ cancellationTokenParameter.Locations.First())
+ });
+ }
+
+ if ((method.Parameters.Length > 1 && cancellationTokenIndex == -1)
+ || (method.Parameters.Length > 2))
+ {
+ throw new DiagnosticsException(new[]
+ {
+ Diagnostic.Create(
+ new DiagnosticDescriptor(
+ "DAPR0002",
+ "Invalid method signature.",
+ "Only methods with a single argument or a single argument followed by a cancellation token are supported.",
+ "Dapr.Actors.Generators",
+ DiagnosticSeverity.Error,
+ true),
+ method.Locations.First())
+ });
+ }
+
+ var attributeData = method.GetAttributes().SingleOrDefault(a => a.AttributeClass?.Equals(generateActorClientAttributeSymbol, SymbolEqualityComparer.Default) == true);
+
+ string? actualMethodName = attributeData?.NamedArguments.SingleOrDefault(kvp => kvp.Key == "Name").Value.Value?.ToString() ?? method.Name;
+
+ var requestParameter = method.Parameters.Length > 0 && cancellationTokenIndex != 0 ? method.Parameters[0] : null;
+
+ var returnTypeArgument = (method.ReturnType as INamedTypeSymbol)?.TypeArguments.FirstOrDefault();
+
+ string argumentDefinitions = String.Join(", ", method.Parameters.Select(p => $"{p.Type} {p.Name}"));
+
+ if (cancellationTokenParameter is not null
+ && cancellationTokenParameter.IsOptional
+ && cancellationTokenParameter.HasExplicitDefaultValue
+ && cancellationTokenParameter.ExplicitDefaultValue is null)
+ {
+ argumentDefinitions = argumentDefinitions + " = default";
+ }
+
+ string argumentList = String.Join(", ", new[] { $@"""{actualMethodName}""" }.Concat(method.Parameters.Select(p => p.Name)));
+
+ string templateArgs =
+ returnTypeArgument is not null
+ ? $"<{(requestParameter is not null ? $"{requestParameter.Type}, " : "")}{returnTypeArgument}>"
+ : "";
+
+ return
+ $@"public {method.ReturnType} {method.Name}({argumentDefinitions})
+ {{
+ return this.actorProxy.InvokeMethodAsync{templateArgs}({argumentList});
+ }}";
+ }
+}
+
+internal static class Extensions
+{
+ public static int IndexOf(this IEnumerable source, Func predicate)
+ {
+ int index = 0;
+
+ foreach (var item in source)
+ {
+ if (predicate(item))
+ {
+ return index;
+ }
+
+ index++;
+ }
+
+ return -1;
+ }
+}
+
+internal sealed class DiagnosticsException : Exception
+{
+ public DiagnosticsException(IEnumerable diagnostics)
+ : base(String.Join("\n", diagnostics.Select(d => d.ToString())))
+ {
+ this.Diagnostics = diagnostics.ToArray();
+ }
+
+ public IEnumerable Diagnostics { get; }
+}
diff --git a/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj b/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj
new file mode 100644
index 000000000..370d422f1
--- /dev/null
+++ b/src/Dapr.Actors.Generators/Dapr.Actors.Generators.csproj
@@ -0,0 +1,45 @@
+
+
+
+ enable
+ enable
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+ netstandard2.0
+
+
+
+ false
+
+
+ true
+
+
+ false
+
+
+ This package contains source generators for interacting with Actor services using Dapr.
+ $(PackageTags);Actors
+
+
+
+
+
+
+
+
diff --git a/src/Dapr.Actors/ActorReference.cs b/src/Dapr.Actors/ActorReference.cs
index 0e199c3ae..d72b6676f 100644
--- a/src/Dapr.Actors/ActorReference.cs
+++ b/src/Dapr.Actors/ActorReference.cs
@@ -1,4 +1,4 @@
-// ------------------------------------------------------------------------
+// ------------------------------------------------------------------------
// Copyright 2021 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.
@@ -16,6 +16,7 @@ namespace Dapr.Actors
using System;
using System.Runtime.Serialization;
using Dapr.Actors.Client;
+ using Dapr.Actors.Runtime;
///
/// Encapsulation of a reference to an actor for serialization.
@@ -69,23 +70,28 @@ public object Bind(Type actorInterfaceType)
private static ActorReference GetActorReference(object actor)
{
- if (actor == null)
- {
- throw new ArgumentNullException("actor");
- }
+ ArgumentNullException.ThrowIfNull(actor, nameof(actor));
- // try as IActorProxy for backward compatibility as customers's mock framework may rely on it before V2 remoting stack.
- if (actor is IActorProxy actorProxy)
+ var actorReference = actor switch
{
- return new ActorReference()
+ // try as IActorProxy for backward compatibility as customers's mock framework may rely on it before V2 remoting stack.
+ IActorProxy actorProxy => new ActorReference()
{
ActorId = actorProxy.ActorId,
ActorType = actorProxy.ActorType,
- };
- }
+ },
+ // Handle case when we want to get ActorReference inside the Actor implementation,
+ // we gather actor id and actor type from Actor base class.
+ Actor actorBase => new ActorReference()
+ {
+ ActorId = actorBase.Id,
+ ActorType = actorBase.Host.ActorTypeInfo.ActorTypeName,
+ },
+ // Handle case when we can't cast to IActorProxy or Actor.
+ _ => throw new ArgumentOutOfRangeException("actor", "Invalid actor object type."),
+ };
- // TODO check for ActorBase
- throw new ArgumentOutOfRangeException("actor");
+ return actorReference;
}
}
}
diff --git a/src/Dapr.Actors/Communication/ActorStateResponse.cs b/src/Dapr.Actors/Communication/ActorStateResponse.cs
new file mode 100644
index 000000000..22b3bf20e
--- /dev/null
+++ b/src/Dapr.Actors/Communication/ActorStateResponse.cs
@@ -0,0 +1,50 @@
+// ------------------------------------------------------------------------
+// Copyright 2023 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.Actors.Communication
+{
+ using System;
+
+ ///
+ /// Represents a response from fetching an actor state key.
+ ///
+ public class ActorStateResponse
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The response value.
+ /// The time to live expiration time.
+ public ActorStateResponse(T value, DateTimeOffset? ttlExpireTime)
+ {
+ this.Value = value;
+ this.TTLExpireTime = ttlExpireTime;
+ }
+
+ ///
+ /// Gets the response value as a string.
+ ///
+ ///
+ /// The response value as a string.
+ ///
+ public T Value { get; }
+
+ ///
+ /// Gets the time to live expiration time.
+ ///
+ ///
+ /// The time to live expiration time.
+ ///
+ public DateTimeOffset? TTLExpireTime { get; }
+ }
+}
diff --git a/src/Dapr.Actors/Constants.cs b/src/Dapr.Actors/Constants.cs
index be2d8f49f..038caf101 100644
--- a/src/Dapr.Actors/Constants.cs
+++ b/src/Dapr.Actors/Constants.cs
@@ -1,4 +1,4 @@
-// ------------------------------------------------------------------------
+// ------------------------------------------------------------------------
// Copyright 2021 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.
@@ -22,6 +22,7 @@ internal static class Constants
public const string RequestHeaderName = "X-DaprRequestHeader";
public const string ErrorResponseHeaderName = "X-DaprErrorResponseHeader";
public const string ReentrancyRequestHeaderName = "Dapr-Reentrancy-Id";
+ public const string TTLResponseHeaderName = "Metadata.ttlExpireTime";
public const string Dapr = "dapr";
public const string Config = "config";
public const string State = "state";
diff --git a/src/Dapr.Actors/Dapr.Actors.csproj b/src/Dapr.Actors/Dapr.Actors.csproj
index 3fb63ea20..54d3487b8 100644
--- a/src/Dapr.Actors/Dapr.Actors.csproj
+++ b/src/Dapr.Actors/Dapr.Actors.csproj
@@ -6,17 +6,13 @@
$(PackageTags);Actors
-
-
-
-
-
+
-
+
diff --git a/src/Dapr.Actors/DaprHttpInteractor.cs b/src/Dapr.Actors/DaprHttpInteractor.cs
index 4695375fb..2565bab62 100644
--- a/src/Dapr.Actors/DaprHttpInteractor.cs
+++ b/src/Dapr.Actors/DaprHttpInteractor.cs
@@ -57,7 +57,7 @@ public DaprHttpInteractor(
this.httpClient.Timeout = requestTimeout ?? this.httpClient.Timeout;
}
- public async Task GetStateAsync(string actorType, string actorId, string keyName, CancellationToken cancellationToken = default)
+ public async Task> GetStateAsync(string actorType, string actorId, string keyName, CancellationToken cancellationToken = default)
{
var relativeUrl = string.Format(CultureInfo.InvariantCulture, Constants.ActorStateKeyRelativeUrlFormat, actorType, actorId, keyName);
@@ -72,7 +72,18 @@ HttpRequestMessage RequestFunc()
using var response = await this.SendAsync(RequestFunc, relativeUrl, cancellationToken);
var stringResponse = await response.Content.ReadAsStringAsync();
- return stringResponse;
+
+ DateTimeOffset? ttlExpireTime = null;
+ if (response.Headers.TryGetValues(Constants.TTLResponseHeaderName, out IEnumerable headerValues))
+ {
+ var ttlExpireTimeString = headerValues.First();
+ if (!string.IsNullOrEmpty(ttlExpireTimeString))
+ {
+ ttlExpireTime = DateTime.Parse(ttlExpireTimeString, CultureInfo.InvariantCulture);
+ }
+ }
+
+ return new ActorStateResponse(stringResponse, ttlExpireTime);
}
public Task SaveStateTransactionallyAsync(string actorType, string actorId, string data, CancellationToken cancellationToken = default)
diff --git a/src/Dapr.Actors/IDaprInteractor.cs b/src/Dapr.Actors/IDaprInteractor.cs
index 8f30aa18f..5849328a8 100644
--- a/src/Dapr.Actors/IDaprInteractor.cs
+++ b/src/Dapr.Actors/IDaprInteractor.cs
@@ -1,4 +1,4 @@
-// ------------------------------------------------------------------------
+// ------------------------------------------------------------------------
// Copyright 2021 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.
@@ -52,7 +52,7 @@ internal interface IDaprInteractor
/// Name of key to get value for.
/// Cancels the operation.
/// A task that represents the asynchronous operation.
- Task GetStateAsync(string actorType, string actorId, string keyName, CancellationToken cancellationToken = default);
+ Task> GetStateAsync(string actorType, string actorId, string keyName, CancellationToken cancellationToken = default);
///
/// Invokes Actor method.
diff --git a/src/Dapr.Actors/Runtime/ActorManager.cs b/src/Dapr.Actors/Runtime/ActorManager.cs
index b7ee3bf3e..80049d65f 100644
--- a/src/Dapr.Actors/Runtime/ActorManager.cs
+++ b/src/Dapr.Actors/Runtime/ActorManager.cs
@@ -148,16 +148,16 @@ async Task