This commit is contained in:
Whit Waldo 2025-07-22 09:00:56 +00:00 committed by GitHub
commit 419fc22b9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
694 changed files with 44087 additions and 35516 deletions

View File

@ -5,6 +5,8 @@ on:
branches:
- master
- release-*
- dev-*
- feature-*
tags:
- v*
@ -12,6 +14,8 @@ on:
branches:
- master
- release-*
- dev-*
- feature-*
jobs:
build:
@ -20,18 +24,8 @@ jobs:
strategy:
fail-fast: false
matrix:
dotnet-version: ['6.0', '7.0', '8.0', '9.0']
dotnet-version: ['8.0', '9.0']
include:
- dotnet-version: '6.0'
display-name: '.NET 6.0'
framework: 'net6'
prefix: 'net6'
install-version: '6.0.x'
- dotnet-version: '7.0'
display-name: '.NET 7.0'
framework: 'net7'
prefix: 'net7'
install-version: '7.0.x'
- dotnet-version: '8.0'
display-name: '.NET 8.0'
framework: 'net8'
@ -50,7 +44,7 @@ jobs:
GOPROXY: https://proxy.golang.org
DAPR_CLI_VER: 1.15.0
DAPR_RUNTIME_VER: 1.15.3
DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/release-1.14/install/install.sh
DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/release-1.15/install/install.sh
DAPR_CLI_REF: ''
steps:
- name: Set up Dapr CLI
@ -124,10 +118,12 @@ jobs:
with:
dotnet-version: '9.0.x'
dotnet-quality: 'ga'
- name: Restore dependencies
run: dotnet restore
- 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 General Tests
run: dotnet build --configuration release --no-restore /p:GITHUB_ACTIONS=false
- name: Run General Integration Tests
id: tests
continue-on-error: true # proceed if tests fail, the report step will report the failure with more details.
run: |
@ -142,7 +138,7 @@ jobs:
/p:CollectCoverage=true \
/p:CoverletOutputFormat=opencover \
/p:GITHUB_ACTIONS=false
- name: Run Generators Tests
- name: Run Generators Integration Tests
id: generator-tests
continue-on-error: true # proceed if tests fail, the report step will report the failure with more details.
run: |

View File

@ -5,6 +5,8 @@ on:
branches:
- master
- release-*
- dev-*
- feature-*
tags:
- v*
@ -12,6 +14,13 @@ on:
branches:
- master
- release-*
- dev-*
- feature-*
workflow_run:
workflows: [ "integration-test" ]
types:
- completed
jobs:
build:
@ -28,8 +37,10 @@ jobs:
with:
dotnet-version: 9.0.x
dotnet-quality: 'ga'
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration release
run: dotnet build --configuration release --no-restore
- name: Generate Packages
run: dotnet pack --configuration release
- name: Upload packages
@ -44,18 +55,8 @@ jobs:
strategy:
fail-fast: false
matrix:
dotnet-version: ['6.0', '7.0', '8.0', '9.0']
dotnet-version: ['8.0', '9.0']
include:
- dotnet-version: '6.0'
display-name: '.NET 6.0'
framework: 'net6'
prefix: 'net6'
install-version: '6.0.x'
- dotnet-version: '7.0'
display-name: '.NET 7.0'
framework: 'net7'
prefix: 'net7'
install-version: '7.0.x'
- dotnet-version: '8.0'
display-name: '.NET 8.0'
framework: 'net8'
@ -125,7 +126,11 @@ jobs:
name: Publish Packages
needs: ['build', 'test']
runs-on: ubuntu-latest
if: startswith(github.ref, 'refs/tags/v') && !(endsWith(github.ref, '-rc') || endsWith(github.ref, '-dev') || endsWith(github.ref, '-prerelease'))
# Only run this job if workflow_run was successful and we're on a tag
if: |
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') &&
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@v4

View File

@ -54,13 +54,13 @@ This section describes the guidelines for contributing code / docs to Dapr.
All contributions come through pull requests. To submit a proposed change, we recommend following this workflow:
1. Make sure there's an issue (bug or proposal) raised, which sets the expectations for the contribution you are about to make.
1. Fork the relevant repo and create a new branch
1. Create your change
2. Fork the relevant repo and create a new branch
3. Create your change
- Code changes require tests
1. Update relevant documentation for the change
1. Commit and open a PR
1. Wait for the CI process to finish and make sure all checks are green
1. A maintainer of the project will be assigned, and you can expect a review within a few days
4. Update relevant documentation for the change
5. Commit and open a PR
6. Wait for the CI process to finish and make sure all checks are green
7. A maintainer of the project will be assigned, and you can expect a review within a few days
#### Use work-in-progress PRs for early feedback

View File

@ -4,49 +4,53 @@
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.2" />
<PackageVersion Include="GitHubActionsTestLogger" Version="1.1.2" />
<PackageVersion Include="Google.Api.CommonProtos" Version="2.2.0" />
<PackageVersion Include="Google.Protobuf" Version="3.30.2" />
<PackageVersion Include="BenchmarkDotNet" Version="0.15.2" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.4" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
<PackageVersion Include="Google.Api.CommonProtos" Version="2.17.0" />
<PackageVersion Include="Google.Protobuf" Version="3.31.1" />
<PackageVersion Include="Grpc.AspNetCore" Version="2.71.0" />
<PackageVersion Include="Grpc.Core.Testing" Version="2.46.6" />
<PackageVersion Include="Grpc.Net.Client" Version="2.71.0" />
<PackageVersion Include="Grpc.Net.ClientFactory" Version="2.71.0" />
<PackageVersion Include="Grpc.Tools" Version="2.71.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.35" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="6.0.35" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Grpc.Tools" Version="2.72.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.17" Condition="'$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net8'" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.6" Condition="'$(TargetFramework)' == 'net9.0' Or '$(TargetFramework)' == 'net9'" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="8.0.17" Condition="'$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net8'" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="9.0.6" Condition="'$(TargetFramework)' == 'net9.0' Or '$(TargetFramework)' == 'net9'" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing" Version="1.1.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit" Version="1.1.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing" Version="1.1.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.SourceGenerators.Testing.XUnit" Version="1.1.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" />
<PackageVersion Include="Microsoft.DurableTask.Client.Grpc" Version="1.10.0" />
<PackageVersion Include="Microsoft.DurableTask.Worker.Grpc" Version="1.10.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.4" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
<PackageVersion Include="MinVer" Version="2.3.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.14.0" />
<PackageVersion Include="Microsoft.DurableTask.Client.Grpc" Version="1.5.0" />
<PackageVersion Include="Microsoft.DurableTask.Worker.Grpc" Version="1.5.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.6" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.6" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="MinVer" Version="6.0.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="protobuf-net.Grpc.AspNetCore" Version="1.2.2" />
<PackageVersion Include="Serilog.AspNetCore" Version="6.1.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageVersion Include="Shouldly" Version="4.2.1" />
<PackageVersion Include="System.Formats.Asn1" Version="6.0.1" />
<PackageVersion Include="System.Text.Json" Version="6.0.10" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.extensibility.core" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="Shouldly" Version="4.3.0" />
<PackageVersion Include="System.Formats.Asn1" Version="9.0.6" />
<PackageVersion Include="System.Text.Json" Version="9.0.6" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.extensibility.core" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.1" />
</ItemGroup>
</Project>

View File

@ -44,12 +44,21 @@ This repo builds the following packages:
- Dapr.AspNetCore
- Dapr.Actors
- Dapr.Actors.AspNetCore
- Dapr.Actors.Generators
- Dapr.AI
- Dapr.Jobs
- Dapr.Messaging
- Dapr.Extensions.Configuration
- Dapr.Workflow
It also builds the following packages which are not intended for public use and contain common types used in the packages above:
- Dapr.Common
- Dapr.Protos
### Prerequisites
Each project is a normal C# project. At minimum, you need [.NET 6.0 SDK](https://dotnet.microsoft.com/download/dotnet/6.0) to build, test, and generate NuGet packages.
Each project is a normal C# project. At minimum, you need [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) to build, test, and generate NuGet packages.
Also make sure to reference the [.NET SDK contribution guide](https://docs.dapr.io/contributing/sdk-contrib/dotnet-contributing/)
@ -59,7 +68,7 @@ On macOS or Linux we recommend [Visual Studio Code](https://code.visualstudio.co
**Windows:**
On Windows, we recommend installing [the latest Visual Studio 2019](https://www.visualstudio.com/vs/) which will set you up with all the .NET build tools and allow you to open the solution files. Community Edition is free and can be used to build everything here.
On Windows, we recommend installing [the latest Visual Studio 2022](https://www.visualstudio.com/vs/) which will set you up with all the .NET build tools and allow you to open the solution files. Community Edition is free and can be used to build everything here.
Make sure you [update Visual Studio to the most recent release](https://docs.microsoft.com/visualstudio/install/update-visual-studio).

139
all.sln
View File

@ -54,7 +54,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControllerSample", "example
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Actor", "Actor", "{02374BD0-BF0B-40F8-A04A-C4C4D61D4992}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IDemoActor", "examples\Actor\IDemoActor\IDemoActor.csproj", "{7957E852-1291-4FAA-9034-FB66CE817FF1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoActor.Interfaces", "examples\Actor\DemoActor.Interfaces\DemoActor.Interfaces.csproj", "{7957E852-1291-4FAA-9034-FB66CE817FF1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DemoActor", "examples\Actor\DemoActor\DemoActor.csproj", "{626D74DD-4F37-4F74-87A3-5A6888684F5E}"
EndProject
@ -111,8 +111,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapr.Actors.Generators.Test
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}"
@ -143,8 +141,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Messaging.Test", "test
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Messaging", "src\Dapr.Messaging\Dapr.Messaging.csproj", "{0EAE36A1-B578-4F13-A113-7A477ECA1BDA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StreamingSubscriptionExample", "examples\Client\PublishSubscribe\StreamingSubscriptionExample\StreamingSubscriptionExample.csproj", "{290D1278-F613-4DF3-9DF5-F37E38CDC363}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Jobs", "src\Dapr.Jobs\Dapr.Jobs.csproj", "{C8BB6A85-A7EA-40C0-893D-F36F317829B3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Jobs.Test", "test\Dapr.Jobs.Test\Dapr.Jobs.Test.csproj", "{BF9828E9-5597-4D42-AA6E-6E6C12214204}"
@ -155,6 +151,48 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JobsSample", "examples\Jobs
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Workflow.Test", "test\Dapr.Workflow.Test\Dapr.Workflow.Test.csproj", "{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Workflow.Analyzers", "src\Dapr.Workflow.Analyzers\Dapr.Workflow.Analyzers.csproj", "{55A7D436-CC8C-47E6-B43A-DFE32E0FE38C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Workflow.Analyzers.Test", "test\Dapr.Workflow.Analyzers.Test\Dapr.Workflow.Analyzers.Test.csproj", "{CE0D5FEB-F6DB-4EB8-B8A9-6A4A32944539}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Jobs.Analyzers", "src\Dapr.Jobs.Analyzer\Dapr.Jobs.Analyzers.csproj", "{28B87C37-4B52-400F-B84D-64F134931BDC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Jobs.Analyzers.Test", "test\Dapr.Jobs.Analyzer.Test\Dapr.Jobs.Analyzers.Test.csproj", "{CADEAE45-8981-4723-B641-9C28251C7D3B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Actors.Analyzers", "src\Dapr.Actors.Analyzers\Dapr.Actors.Analyzers\Dapr.Actors.Analyzers.csproj", "{E49C822C-E921-48DF-897B-3E603CA596D2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Actors.Analyzers.Test", "test\Dapr.Actors.Analyzers.Test\Dapr.Actors.Analyzers.Test.csproj", "{A2C0F203-11FF-4B7F-A94F-B9FD873573FE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Analyzers.Common", "test\Dapr.Analyzers.Common\Dapr.Analyzers.Common.csproj", "{7E23E229-6823-4D84-AF3A-AE14CEAEF52A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Cryptography", "src\Dapr.Cryptography\Dapr.Cryptography.csproj", "{160EFFA0-F6B9-49E4-B62B-68C0D53DB425}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Cryptography.Test", "test\Dapr.Cryptography.Test\Dapr.Cryptography.Test.csproj", "{B508EBD6-0F14-480C-A446-45A09052733B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StreamingSubscriptionExample", "examples\Messaging\StreamingSubscriptionExample\StreamingSubscriptionExample.csproj", "{E070F694-335D-4D96-8951-F41D0A5F2A8B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Cryptography", "Cryptography", "{6843B5B3-9E95-4022-B792-8A1DE6BFEFEC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cryptography", "examples\Cryptography\Cryptography.csproj", "{097D5F6F-D26F-4BFB-9074-FA52577EB442}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Messaging", "Messaging", "{442E80E5-8040-4123-B88A-26FD36BA95D9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowParallelFanOut", "examples\Workflow\WorkflowParallelFanOut\WorkflowParallelFanOut.csproj", "{5764B1AA-66B8-43AE-9E0D-0B3B71714B92}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosting", "Hosting", "{953D770B-2DE8-4D1B-B1D4-ED46F4F5F31A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Aspire", "Aspire", "{55E08C7F-81C8-4D0B-AB18-87C89B261477}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BackendApp", "examples\Hosting\Aspire\ServiceInvocationDemo\BackendApp\BackendApp.csproj", "{3553BE3C-C188-460A-AC4C-D3D82DC0922A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FrontendApp", "examples\Hosting\Aspire\ServiceInvocationDemo\FrontendApp\FrontendApp.csproj", "{A6AA3F39-AB3E-4475-B3E2-D53549CBDA49}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceInvocationDemo.AppHost", "examples\Hosting\Aspire\ServiceInvocationDemo\ServiceInvocationDemo.AppHost\ServiceInvocationDemo.AppHost.csproj", "{97A47B0B-9D3B-4CF0-A62C-650F2F211A59}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceInvocationDemo.ServiceDefaults", "examples\Hosting\Aspire\ServiceInvocationDemo\ServiceInvocationDemo.ServiceDefaults\ServiceInvocationDemo.ServiceDefaults.csproj", "{5BB15C36-BAF7-44F6-BF85-C533B8B47862}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "examples\Hosting\Aspire\ServiceInvocationDemo\Common\Common.csproj", "{6CD90C22-0F79-4D61-8DCE-5BE22C1304C4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -403,6 +441,74 @@ Global
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}.Release|Any CPU.Build.0 = Release|Any CPU
{55A7D436-CC8C-47E6-B43A-DFE32E0FE38C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{55A7D436-CC8C-47E6-B43A-DFE32E0FE38C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{55A7D436-CC8C-47E6-B43A-DFE32E0FE38C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{55A7D436-CC8C-47E6-B43A-DFE32E0FE38C}.Release|Any CPU.Build.0 = Release|Any CPU
{CE0D5FEB-F6DB-4EB8-B8A9-6A4A32944539}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CE0D5FEB-F6DB-4EB8-B8A9-6A4A32944539}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CE0D5FEB-F6DB-4EB8-B8A9-6A4A32944539}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CE0D5FEB-F6DB-4EB8-B8A9-6A4A32944539}.Release|Any CPU.Build.0 = Release|Any CPU
{28B87C37-4B52-400F-B84D-64F134931BDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{28B87C37-4B52-400F-B84D-64F134931BDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{28B87C37-4B52-400F-B84D-64F134931BDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{28B87C37-4B52-400F-B84D-64F134931BDC}.Release|Any CPU.Build.0 = Release|Any CPU
{CADEAE45-8981-4723-B641-9C28251C7D3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CADEAE45-8981-4723-B641-9C28251C7D3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CADEAE45-8981-4723-B641-9C28251C7D3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CADEAE45-8981-4723-B641-9C28251C7D3B}.Release|Any CPU.Build.0 = Release|Any CPU
{E49C822C-E921-48DF-897B-3E603CA596D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E49C822C-E921-48DF-897B-3E603CA596D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E49C822C-E921-48DF-897B-3E603CA596D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E49C822C-E921-48DF-897B-3E603CA596D2}.Release|Any CPU.Build.0 = Release|Any CPU
{A2C0F203-11FF-4B7F-A94F-B9FD873573FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A2C0F203-11FF-4B7F-A94F-B9FD873573FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A2C0F203-11FF-4B7F-A94F-B9FD873573FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A2C0F203-11FF-4B7F-A94F-B9FD873573FE}.Release|Any CPU.Build.0 = Release|Any CPU
{7E23E229-6823-4D84-AF3A-AE14CEAEF52A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E23E229-6823-4D84-AF3A-AE14CEAEF52A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E23E229-6823-4D84-AF3A-AE14CEAEF52A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E23E229-6823-4D84-AF3A-AE14CEAEF52A}.Release|Any CPU.Build.0 = Release|Any CPU
{160EFFA0-F6B9-49E4-B62B-68C0D53DB425}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{160EFFA0-F6B9-49E4-B62B-68C0D53DB425}.Debug|Any CPU.Build.0 = Debug|Any CPU
{160EFFA0-F6B9-49E4-B62B-68C0D53DB425}.Release|Any CPU.ActiveCfg = Release|Any CPU
{160EFFA0-F6B9-49E4-B62B-68C0D53DB425}.Release|Any CPU.Build.0 = Release|Any CPU
{B508EBD6-0F14-480C-A446-45A09052733B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B508EBD6-0F14-480C-A446-45A09052733B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B508EBD6-0F14-480C-A446-45A09052733B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B508EBD6-0F14-480C-A446-45A09052733B}.Release|Any CPU.Build.0 = Release|Any CPU
{E070F694-335D-4D96-8951-F41D0A5F2A8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E070F694-335D-4D96-8951-F41D0A5F2A8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E070F694-335D-4D96-8951-F41D0A5F2A8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E070F694-335D-4D96-8951-F41D0A5F2A8B}.Release|Any CPU.Build.0 = Release|Any CPU
{097D5F6F-D26F-4BFB-9074-FA52577EB442}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{097D5F6F-D26F-4BFB-9074-FA52577EB442}.Debug|Any CPU.Build.0 = Debug|Any CPU
{097D5F6F-D26F-4BFB-9074-FA52577EB442}.Release|Any CPU.ActiveCfg = Release|Any CPU
{097D5F6F-D26F-4BFB-9074-FA52577EB442}.Release|Any CPU.Build.0 = Release|Any CPU
{5764B1AA-66B8-43AE-9E0D-0B3B71714B92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5764B1AA-66B8-43AE-9E0D-0B3B71714B92}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5764B1AA-66B8-43AE-9E0D-0B3B71714B92}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5764B1AA-66B8-43AE-9E0D-0B3B71714B92}.Release|Any CPU.Build.0 = Release|Any CPU
{3553BE3C-C188-460A-AC4C-D3D82DC0922A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3553BE3C-C188-460A-AC4C-D3D82DC0922A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3553BE3C-C188-460A-AC4C-D3D82DC0922A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3553BE3C-C188-460A-AC4C-D3D82DC0922A}.Release|Any CPU.Build.0 = Release|Any CPU
{A6AA3F39-AB3E-4475-B3E2-D53549CBDA49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6AA3F39-AB3E-4475-B3E2-D53549CBDA49}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6AA3F39-AB3E-4475-B3E2-D53549CBDA49}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6AA3F39-AB3E-4475-B3E2-D53549CBDA49}.Release|Any CPU.Build.0 = Release|Any CPU
{97A47B0B-9D3B-4CF0-A62C-650F2F211A59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{97A47B0B-9D3B-4CF0-A62C-650F2F211A59}.Debug|Any CPU.Build.0 = Debug|Any CPU
{97A47B0B-9D3B-4CF0-A62C-650F2F211A59}.Release|Any CPU.ActiveCfg = Release|Any CPU
{97A47B0B-9D3B-4CF0-A62C-650F2F211A59}.Release|Any CPU.Build.0 = Release|Any CPU
{5BB15C36-BAF7-44F6-BF85-C533B8B47862}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5BB15C36-BAF7-44F6-BF85-C533B8B47862}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5BB15C36-BAF7-44F6-BF85-C533B8B47862}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5BB15C36-BAF7-44F6-BF85-C533B8B47862}.Release|Any CPU.Build.0 = Release|Any CPU
{6CD90C22-0F79-4D61-8DCE-5BE22C1304C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6CD90C22-0F79-4D61-8DCE-5BE22C1304C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6CD90C22-0F79-4D61-8DCE-5BE22C1304C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6CD90C22-0F79-4D61-8DCE-5BE22C1304C4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -455,7 +561,6 @@ Global
{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}
{CDB47863-BEBD-4841-A807-46D868962521} = {DD020B34-460F-455F-8D17-CF4A949F100B}
@ -471,12 +576,32 @@ Global
{00359961-0C50-4BB1-A794-8B06DE991639} = {BF3ED6BF-ADF3-4D25-8E89-02FB8D945CA9}
{4E04EB35-7FD2-4FDB-B09A-F75CE24053B9} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{0EAE36A1-B578-4F13-A113-7A477ECA1BDA} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{290D1278-F613-4DF3-9DF5-F37E38CDC363} = {0EF6EA64-D7C3-420D-9890-EAE8D54A57E6}
{C8BB6A85-A7EA-40C0-893D-F36F317829B3} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{BF9828E9-5597-4D42-AA6E-6E6C12214204} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{D9697361-232F-465D-A136-4561E0E88488} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}
{9CAF360E-5AD3-4C4F-89A0-327EEB70D673} = {D9697361-232F-465D-A136-4561E0E88488}
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{55A7D436-CC8C-47E6-B43A-DFE32E0FE38C} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{CE0D5FEB-F6DB-4EB8-B8A9-6A4A32944539} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{28B87C37-4B52-400F-B84D-64F134931BDC} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{CADEAE45-8981-4723-B641-9C28251C7D3B} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{E49C822C-E921-48DF-897B-3E603CA596D2} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{A2C0F203-11FF-4B7F-A94F-B9FD873573FE} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{7E23E229-6823-4D84-AF3A-AE14CEAEF52A} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{160EFFA0-F6B9-49E4-B62B-68C0D53DB425} = {27C5D71D-0721-4221-9286-B94AB07B58CF}
{B508EBD6-0F14-480C-A446-45A09052733B} = {DD020B34-460F-455F-8D17-CF4A949F100B}
{6843B5B3-9E95-4022-B792-8A1DE6BFEFEC} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}
{097D5F6F-D26F-4BFB-9074-FA52577EB442} = {6843B5B3-9E95-4022-B792-8A1DE6BFEFEC}
{442E80E5-8040-4123-B88A-26FD36BA95D9} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}
{E070F694-335D-4D96-8951-F41D0A5F2A8B} = {442E80E5-8040-4123-B88A-26FD36BA95D9}
{5764B1AA-66B8-43AE-9E0D-0B3B71714B92} = {BF3ED6BF-ADF3-4D25-8E89-02FB8D945CA9}
{953D770B-2DE8-4D1B-B1D4-ED46F4F5F31A} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}
{55E08C7F-81C8-4D0B-AB18-87C89B261477} = {953D770B-2DE8-4D1B-B1D4-ED46F4F5F31A}
{3553BE3C-C188-460A-AC4C-D3D82DC0922A} = {55E08C7F-81C8-4D0B-AB18-87C89B261477}
{A6AA3F39-AB3E-4475-B3E2-D53549CBDA49} = {55E08C7F-81C8-4D0B-AB18-87C89B261477}
{97A47B0B-9D3B-4CF0-A62C-650F2F211A59} = {55E08C7F-81C8-4D0B-AB18-87C89B261477}
{5BB15C36-BAF7-44F6-BF85-C533B8B47862} = {55E08C7F-81C8-4D0B-AB18-87C89B261477}
{6CD90C22-0F79-4D61-8DCE-5BE22C1304C4} = {55E08C7F-81C8-4D0B-AB18-87C89B261477}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40}

View File

@ -101,17 +101,8 @@ squashing the PR locally and resubmitting to ensure that the sign-off statement
# Languages, Tools and Processes
All source code in the Dapr .NET SDK is written in C# and targets the latest language version available to the earliest
supported .NET SDK. As of v1.15, this means that because .NET 6 is still supported, the latest language version available
is [C# version 10](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-10).
As of v1.15, the following versions of .NET are supported:
| Version | Notes |
| --- |-----------------------------------------------------------------|
| .NET 6 | Will be discontinued in v1.16 |
| .NET 7 | Only supported in Dapr.Workflows, will be discontinued in v1.16 |
| .NET 8 | Will continue to be supported in v1.16 |
| .NET 9 | Will continue to be supported in v1.16 |
supported .NET SDK. As of v1.16, this means that both .NET 8 and .NET 9 are supported. The latest language version available
is [C# version 12](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-12)
Contributors are welcome to use whatever IDE they're most comfortable developing in, but please do not submit
IDE-specific preference files along with your contributions as these will be rejected.

View File

@ -16,17 +16,9 @@ Dapr offers a variety of packages to help with the development of .NET applicati
## Prerequisites
- [Dapr CLI]({{% ref install-dapr-cli.md %}}) installed
- Initialized [Dapr environment]({{% ref install-dapr-selfhost.md %}})
- [.NET 6](https://dotnet.microsoft.com/download), [.NET 8](https://dotnet.microsoft.com/download) or [.NET 9](https://dotnet.microsoft.com/download) installed
{{% alert title="Note" color="primary" %}}
Note that while .NET 6 is generally supported as the minimum .NET requirement across the Dapr .NET SDK packages
and .NET 7 is the minimally supported version of .NET by Dapr.Workflows in Dapr v1.15, only .NET 8 and .NET 9 will
continue to be supported by Dapr in v1.16 and later.
{{% /alert %}}
- [Dapr CLI]({{< ref install-dapr-cli.md >}}) installed
- Initialized [Dapr environment]({{< ref install-dapr-selfhost.md >}})
- [.NET 8](https://dotnet.microsoft.com/download) or [.NET 9](https://dotnet.microsoft.com/download) installed
## Installation

View File

@ -43,17 +43,9 @@ This project contains the implementation of the actor client which calls MyActor
## Prerequisites
- [Dapr CLI]({{% ref install-dapr-cli.md %}}) installed.
- Initialized [Dapr environment]({{% ref install-dapr-selfhost.md %}}).
- [.NET 6](https://dotnet.microsoft.com/download), [.NET 8](https://dotnet.microsoft.com/download) or [.NET 9](https://dotnet.microsoft.com/download) installed
{{% alert title="Note" color="primary" %}}
Note that while .NET 6 is generally supported as the minimum .NET requirement across the Dapr .NET SDK packages
and .NET 7 is the minimally supported version of .NET by Dapr.Workflows in Dapr v1.15, only .NET 8 and .NET 9 will
continue to be supported by Dapr in v1.16 and later.
{{% /alert %}}
- [Dapr CLI]({{< ref install-dapr-cli.md >}}) installed.
- Initialized [Dapr environment]({{< ref install-dapr-selfhost.md >}}).
- [.NET 8](https://dotnet.microsoft.com/download) or [.NET 9](https://dotnet.microsoft.com/download) installed
## Step 0: Prepare

View File

@ -3,7 +3,7 @@ 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
description: Necessary steps to serialize your types remoted and non-remoted Actors in .NET
---
# Actor Serialization
@ -254,6 +254,36 @@ a complex versioning scheme for our existing enum values in the state.
{"event": "Conference", "season": "fall"}
```
### Polymorphic Serialization
When working with polymorphic types in Dapr Actor clients, it is essential to handle serialization and deserialization correctly to ensure that the appropriate
derived types are instantiated. Polymorphic serialization allows you to serialize objects of a base type while preserving the specific derived type information.
To enable polymorphic deserialization, you must use the `[JsonPolymorphic]` attribute on your base type. Additionally,
it is crucial to include the `[AllowOutOfOrderMetadataProperties]` attribute to ensure that metadata properties, such as `$type`
can be processed correctly by System.Text.Json even if they are not the first properties in the JSON object.
#### Example
```cs
[JsonPolymorphic]
[AllowOutOfOrderMetadataProperties]
public abstract class SampleValueBase
{
public string CommonProperty { get; set; }
}
public class DerivedSampleValue : SampleValueBase
{
public string SpecificProperty { get; set; }
}
```
In this example, the `SampleValueBase` class is marked with both `[JsonPolymorphic]` and `[AllowOutOfOrderMetadataProperties]`
attributes. This setup ensures that the `$type` metadata property can be correctly identified and processed during
deserialization, regardless of its position in the JSON object.
By following this approach, you can effectively manage polymorphic serialization and deserialization in your Dapr Actor
clients, ensuring that the correct derived types are instantiated and used.
## Strongly-typed Dapr Actor client
In this section, you will learn how to configure your classes and records so they are properly serialized and deserialized at runtime when using a strongly-typed actor client. These clients are implemented using .NET interfaces and are <u>not</u> compatible with Dapr Actors written using other languages.

View File

@ -7,17 +7,10 @@ description: Learn how to create and use the Dapr Conversational AI client using
---
## Prerequisites
- [.NET 6](https://dotnet.microsoft.com/download/dotnet/6.0), [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0), or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed
- [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0), or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed
- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/)
- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost)
{{% alert title="Note" color="primary" %}}
.NET 6 is supported as the minimum required for the Dapr .NET SDK packages in this release. Only .NET 8 and .NET 9
will be supported in Dapr v1.16 and later releases.
{{% /alert %}}
## Installation
To get started with the Dapr AI .NET SDK client, install the [Dapr.AI package](https://www.nuget.org/packages/Dapr.AI) from NuGet:
@ -84,7 +77,3 @@ Put the Dapr AI .NET SDK to the test. Walk through the samples to see Dapr in ac
This part of the .NET SDK allows you to interface with the Conversations API to send and receive messages from
large language models.
### Send messages

View File

@ -17,7 +17,7 @@ It maintains access to networking resources in the form of TCP sockets used to c
For best performance, create a single long-lived instance of `DaprConversationClient` and provide access to that shared
instance throughout your application. `DaprConversationClient` instances are thread-safe and intended to be shared.
This can be aided by utilizing the dependency injection functionality. The registration method supports registration using
This can be aided by utilizing the dependency injection functionality. The registration method supports registration
as a singleton, a scoped instance or as transient (meaning it's recreated every time it's injected), but also enables
registration to utilize values from an `IConfiguration` or other injected service in a way that's impractical when
creating the client from scratch in each of your classes.

View File

@ -40,7 +40,7 @@ using var client = new DaprClientBuilder().
// Invokes a POST method named "deposit" that takes input of type "Transaction"
var data = new { id = "17", amount = 99m };
var account = await client.InvokeMethodAsync<object, Account>("routing", "deposit", data, cancellationToken);
var account = await client.InvokeMethodAsync<Account>("routing", "deposit", data, cancellationToken);
Console.WriteLine("Returned: id:{0} | Balance:{1}", account.Id, account.Balance);
```
{{% /tab %}}

View File

@ -0,0 +1,82 @@
---
type: docs
title: "Overview of Dapr source code analysis"
linkTitle: "Code Analysis"
weight: 70000
description: Code analyzers and fixes for common Dapr issues
no_list: true
---
Dapr supports a growing collection of optional Roslyn analyzers and code fix providers that inspect your code for
code quality issues. Starting with the release of v1.16, developers have the opportunity to install additional projects
from NuGet alongside each of the standard capability packages to enable these analyzers in their solutions.
{{% alert title="Note" color="primary" %}}
A future release of the Dapr .NET SDK will include these analyzers by default without necessitating a separate package
install.
{{% /alert %}}
Rule violations will typically be marked as `Info` or `Warning` so that if the analyzer identifies an issue, it won't
necessarily break builds. All code analysis violations appear with the prefix "DAPR" and are uniquely distinguished
by a number following this prefix.
{{% alert title="Note" color="primary" %}}
At this time, the first two digits of the diagnostic identifier map one-to-one to distinct Dapr packages, but this
is subject to change in the future as more analyzers are developed.
{{% /alert %}}
## Install and configure analyzers
The following packages will be available via NuGet following the v1.16 Dapr release:
- Dapr.Actors.Analyzers
- Dapr.Jobs.Analyzers
- Dapr.Workflow.Analyzers
Install each NuGet package on every project where you want the analyzers to run. The package will be installed as a
project dependency and analyzers will run as you write your code or as part of a CI/CD build. The analyzers will flag
issues in your existing code and warn you about new issues as you build your project.
Many of our analyzers have associated code fixes that can be applied to automatically correct the problem. If your IDE
supports this capability, any available code fixes will show up as an inline menu option in your code.
Further, most of our analyzers should also report a specific line and column number in your code of the syntax that's
been identified as a key aspect of the rule. If your IDE supports it, double clicking any of the analyzer warnings
should jump directly to the part of your code responsible for the violating the analyzer's rule.
### Suppress specific analyzers
If you wish to keep an analyzer from firing against some particular piece of your project, their outputs can be
individually targeted for suppression through a number of ways. Read more about suppressing analyzers in projects
or files in the associated [.NET documentation](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/suppress-warnings#use-the-suppressmessageattribute).
### Disable all analyzers
If you wish to disable all analyzers in your project without removing any packages providing them, set
the `EnableNETAnalyzers` property to `false` in your csproj file.
## Available Analyzers
| Diagnostic ID | Dapr Package | Category | Severity | Version Added | Description | Code Fix Available |
| -- | -- |------------------|--------------|-----------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------| -- |
| DAPR1301 | Dapr.Workflow | Usage | Warning | 1.16 | The workflow type is not registered with the dependency injection provider | Yes |
| DAPR1302 | Dapr.Workflow | Usage | Warning | 1.16 | The workflow activity type is not registered with the dependency injection provider | Yes |
| DAPR1401 | Dapr.Actors | Usage | Warning | 1.16 | Actor timer method invocations require the named callback method to exist on type | No |
| DAPR1402 | Dapr.Actors | Usage | Warning | The actor type is not registered with dependency injection | Yes |
| DAPR1403 | Dapr.Actors | Interoperability | Info | Set options.UseJsonSerialization to true to support interoperability with non-.NET actors | Yes |
| DAPR1404 | Dapr.Actors | Usage | Warning | Call app.MapActorsHandlers to map endpoints for Dapr actors | Yes |
| DAPR1501 | Dapr.Jobs | Usage | Warning | Job invocations require the MapDaprScheduledJobHandler to be set and configured for each anticipated job on IEndpointRouteBuilder | No |
## Analyzer Categories
The following are each of the eligible categories that an analyzer can be assigned to and are modeled after the
standard categories used by the.NET analyzers:
- Design
- Documentation
- Globalization
- Interoperability
- Maintainability
- Naming
- Performance
- Reliability
- Security
- Usage

View File

@ -0,0 +1,12 @@
---
type: docs
title: "Dapr Cryptography .NET SDK"
linkTitle: "Cryptography"
weight: 51000
description: Get up and running with the Dapr Cryptography .NET SDK
---
With the Dapr Cryptography package, you can perform high-performance encryption and decryption operations with Dapr.
To get started with this functionality, walk through the [Dapr Cryptography({{< ref dotnet-cryptography-howto.md >}})
how-to guide.

View File

@ -0,0 +1,74 @@
---
type: docs
title: "How to: Create an use Dapr Cryptography in the .NET SDK"
linkTitle: "How to: Use the Cryptography client"
weight: 510100
description: Learn how to create and use the Dapr Cryptography client using the .NET SDK
---
## Prerequisites
- [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0), or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed
- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/)
- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost)
## Installation
To get started with the Dapr Cryptography client, install the [Dapr.Cryptography package](https://www.nuget.org/packages/Dapr.Cryptography) from NuGet:
```sh
dotnet add package Dapr.Cryptography
```
A `DaprEncryptionClient` maintains access to networking resources in the form of TCP sockets used to communicate with
the Dapr sidecar.
### Dependency Injection
The `AddDaprEncryptionClient()` method will register the Dapr client with dependency injection and is the recommended approach
for using this package. This method accepts an optional options delegate for configuring the `DaprEncryptionClient` and a
`ServiceLifetime` argument, allowing you to specify a different lifetime for the registered services instead of the default `Singleton`
value.
The following example assumes all default values are acceptable and is sufficient to register the `DaprEncryptionClient`:
```csharp
services.AddDaprEncryptionClient();
```
The optional configuration delegate is used to configure the `DaprEncryptionClient` by specifying options on the
`DaprEncryptionClientBuilder` as in the following example:
```csharp
services.AddSingleton<DefaultOptionsProvider>();
services.AddDaprEncryptionClient((serviceProvider, clientBuilder) => {
//Inject a service to source a value from
var optionsProvider = serviceProvider.GetRequiredService<DefaultOptionsProvider>();
var standardTimeout = optionsProvider.GetStandardTimeout();
//Configure the value on the client builder
clientBuilder.UseTimeout(standardTimeout);
});
```
### Manual Instantiation
Rather than using dependency injection, a `DaprEncryptionClient` can also be built using the static client builder.
For best performance, create a single long-lived instance of `DaprEncryptionClient` and provide access to that shared instance throughout
your application. `DaprEncryptionClient` instances are thread-safe and intended to be shared.
Avoid creating a `DaprEncryptionClient` per-operation.
A `DaprEncryptionClient` can be configured by invoking methods on the `DaprEncryptionClientBuilder` class before calling `.Build()`
to create the client. The settings for each `DaprEncryptionClient` are separate and cannot be changed after calling `.Build()`.
```csharp
var daprEncryptionClient = new DaprEncryptionClientBuilder()
.UseJsonSerializerSettings( ... ) //Configure JSON serializer
.Build();
```
See the .NET [documentation here]({{< ref dotnet-client >}}) for more information about the options available when configuring the Dapr client via the builder.
## Try it out
Put the Dapr AI .NET SDK to the test. Walk through the samples to see Dapr in action:
| SDK Samples | Description |
|-------------------------------------------------------------------------------------| ----------- |
| [SDK samples](https://github.com/dapr/dotnet-sdk/tree/master/examples/Cryptography) | Clone the SDK repo to try out some examples and get started. |

View File

@ -0,0 +1,131 @@
---
type: docs
title: "Dapr Cryptography Client"
linkTitle: "Cryptography client"
weight: 510005
description: Learn how to create Dapr Crytography clients
---
The Dapr Cryptography package allows you to perform encryption and decryption operations provided by the Dapr sidecar.
## Lifetime management
A `DaprEncryptionClient` is a version of the Dapr client that is dedicated to interacting with the Dapr Cryptography API.
It can be registered alongside a `DaprClient` and other Dapr clients without issue.
It maintains access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar.
For best performance, create a single long-lived instance of `DaprEncryptionClient` and provide access to that shared
instance throughout your application. `DaprEncryptionClient` instances are thread-safe and intended to be shared.
This can be aided by utilizing the dependency injection functionality. The registration method supports registration
as a singleton, a scoped instance, or as a transient (meaning it's recreated every time it's injected), but also enables
registration to utilize values from an `IConfiguration` or other injected service in a way that's impractical when creating
the client from scratch in each of your classes.
Avoid creating a `DaprEncryptionClient` for each operation.
## Configuring `DaprEncryptionClient` via `DaprEncryptionClientBuilder`
A `DaprCryptographyClient` can be configured by invoking methods on the `DaprEncryptionClientBuilder` class before calling
`.Build()` to create the client itself. The settings for each `DaprEncryptionClientBuilder` are separate can cannot be
changed after calling `.Build()`.
```cs
var daprEncryptionClient = new DaprEncryptionClientBuilder()
.UseDaprApiToken("abc123") //Specify the API token used to authenticate to the Dapr sidecar
.Build();
```
The `DaprEncryptionClientBuilder` contains settings for:
- The HTTP endpoint of the Dapr sidecar
- The gRPC endpoint of the Dapr sidecar
- The `JsonSerializerOptions` object used to configure JSON serialization
- The `GrpcChannelOptions` object used to configure gRPC
- The API token used to authenticate requests to the sidecar
- The factory method used to create the `HttpClient` instance used by the SDK
- The timeout used for the `HttpClient` instance when making requests to the sidecar
The SDK will read the following environment variables to configure the default values:
- `DAPR_HTTP_ENDPOINT`: used to find the HTTP endpoint of the Dapr sidecar, example: `https://dapr-api.mycompany.com`
- `DAPR_GRPC_ENDPOINT`: used to find the gRPC endpoint of the Dapr sidecar, example: `https://dapr-grpc-api.mycompany.com`
- `DAPR_HTTP_PORT`: if `DAPR_HTTP_ENDPOINT` is not set, this is used to find the HTTP local endpoint of the Dapr sidecar
- `DAPR_GRPC_PORT`: if `DAPR_GRPC_ENDPOINT` is not set, this is used to find the gRPC local endpoint of the Dapr sidecar
- `DAPR_API_TOKEN`: used to set the API token
### Configuring gRPC channel options
Dapr's use of `CancellationToken` for cancellation relies on the configuration of the gRPC channel options. If you need
to configure these options yourself, make sure to enable the [ThrowOperationCanceledOnCancellation setting](https://grpc.github.io/grpc/csharp-dotnet/api/Grpc.Net.Client.GrpcChannelOptions.html#Grpc_Net_Client_GrpcChannelOptions_ThrowOperationCanceledOnCancellation).
```cs
var daprEncryptionClient = new DaprEncryptionClientBuilder()
.UseGrpcChannelOptions(new GrpcChannelOptions { .. ThrowOperationCanceledOnCancellation = true })
.Build();
```
## Using cancellation with `DaprEncryptionClient`
The APIs on `DaprEncryptionClient` perform asynchronous operations and accept an optional `CancellationToken` parameter. This
follows a standard .NET practice for cancellable operations. Note that when cancellation occurs, there is no guarantee that
the remote endpoint stops processing the request, only that the client has stopped waiting for completion.
When an operation is cancelled, it will throw an `OperationCancelledException`.
## Configuring `DaprEncryptionClient` via dependency injection
Using the built-in extension methods for registering the `DaprEncryptionClient` in a dependency injection container can
provide the benefit of registering the long-lived service a single time, centralize complex configuration and improve
performance by ensuring similarly long-lived resources are re-purposed when possible (e.g. `HttpClient` instances).
There are three overloads available to give the developer the greatest flexibility in configuring the client for their
scenario. Each of these will register the `IHttpClientFactory` on your behalf if not already registered, and configure
the `DaprEncryptionClientBuilder` to use it when creating the `HttpClient` instance in order to re-use the same instance as
much as possible and avoid socket exhaustion and other issues.
In the first approach, there's no configuration done by the developer and the `DaprEncryptionClient` is configured with the
default settings.
```cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprEncryptionClent(); //Registers the `DaprEncryptionClient` to be injected as needed
var app = builder.Build();
```
Sometimes the developer will need to configure the created client using the various configuration options detailed
above. This is done through an overload that passes in the `DaprEncryptionClientBuiler` and exposes methods for configuring
the necessary options.
```cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprEncryptionClient((_, daprEncrpyptionClientBuilder) => {
//Set the API token
daprEncryptionClientBuilder.UseDaprApiToken("abc123");
//Specify a non-standard HTTP endpoint
daprEncryptionClientBuilder.UseHttpEndpoint("http://dapr.my-company.com");
});
var app = builder.Build();
```
Finally, it's possible that the developer may need to retrieve information from another service in order to populate
these configuration values. That value may be provided from a `DaprClient` instance, a vendor-specific SDK or some
local service, but as long as it's also registered in DI, it can be injected into this configuration operation via the
last overload:
```cs
var builder = WebApplication.CreateBuilder(args);
//Register a fictional service that retrieves secrets from somewhere
builder.Services.AddSingleton<SecretService>();
builder.Services.AddDaprEncryptionClient((serviceProvider, daprEncryptionClientBuilder) => {
//Retrieve an instance of the `SecretService` from the service provider
var secretService = serviceProvider.GetRequiredService<SecretService>();
var daprApiToken = secretService.GetSecret("DaprApiToken").Value;
//Configure the `DaprEncryptionClientBuilder`
daprEncryptionClientBuilder.UseDaprApiToken(daprApiToken);
});
var app = builder.Build();
```

View File

@ -23,14 +23,15 @@ While Aspire also assists with deployment of your application to various cloud h
Amazon AWS, deployment is currently outside the scope of this guide. More information can be found in Aspire's
documentation [here](https://learn.microsoft.com/en-us/dotnet/aspire/deployment/overview).
An end-to-end demonstration featuring the following and demonstrating service invocation between multiple Dapr-enabled
services can be found [here](https://github.com/dapr/dotnet-sdk/tree/master/examples/Hosting/Aspire/ServiceInvocationDemo).
## Prerequisites
- While the Dapr .NET SDK is compatible with [.NET 6](https://dotnet.microsoft.com/download/dotnet/6.0),
[.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0),
.NET Aspire is only compatible with [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) or
[.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0).
- Both the Dapr .NET SDK and .NET Aspire are compatible with [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0)
or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0)
- An OCI compliant container runtime such as [Docker Desktop](https://www.docker.com/products/docker-desktop) or
[Podman](https://podman.io/)
- Install and initialize Dapr v1.13 or later
- Install and initialize Dapr v1.16 or later
## Using .NET Aspire via CLI
@ -59,17 +60,18 @@ resilience, service discovery and telemetry capabilities offered by Aspire (thes
offered in Dapr itself).
- `aspiredemo.sln` is the file that maintains the layout of your current solution
We'll next create a project that'll serve as our Dapr application. From the same directory, use the following
to create an empty ASP.NET Core project called `MyApp`. This will be created relative to your current directory in
`MyApp\MyApp.csproj`.
We'll next create twp projects that'll serve as our Dapr application and demonstrate Dapr functionality. From the same
directory, use the following to create an empty ASP.NET Core project called `FrontEndApp` and another called
'BackEndApp'. Either one will be created relative to your current directory in
`FrontEndApp\FrontEndApp.csproj` and `BackEndApp\BackEndApp.csproj`, respectively.
```sh
dotnet new web MyApp
dotnet new web --name FrontEndApp
```
Next we'll configure the AppHost project to add the necessary package to support local Dapr development. Navigate
into the AppHost directory with the following and install the `CommunityToolkit.Aspire.Hosting.Dapr` package from NuGet into the project.
We'll also add a reference to our `MyApp` project so we can reference it during the registration process.
We'll also add a reference to our `FrontEndApp` project so we can reference it during the registration process.
{{% alert color="primary" %}}
@ -80,7 +82,8 @@ This package was previously called `Aspire.Hosting.Dapr`, which has been [marked
```sh
cd aspiredemo.AppHost
dotnet add package CommunityToolkit.Aspire.Hosting.Dapr
dotnet add reference ../MyApp/
dotnet add reference ../FrontEndApp/
dotnet add reference ../BackEndApp/
```
Next, we need to configure Dapr as a resource to be loaded alongside your project. Open the `Program.cs` file in that
@ -99,8 +102,12 @@ Because we've already added a project reference to `MyApp`, we need to start by
as well. Add the following before the `builder.Build().Run()` line:
```csharp
var myApp = builder
.AddProject<Projects.MyApp>("myapp")
var backEndApp = builder
.AddProject<Projects.BackEndApp>("be")
.WithDaprSidecar();
var frontEndApp = builder
.AddProject<Projects.FrontEndApp>("fe")
.WithDaprSidecar();
```
@ -116,7 +123,7 @@ the following example:
```csharp
DaprSidecarOptions sidecarOptions = new()
{
AppId = "my-other-app",
AppId = "how-dapr-identifies-your-app",
AppPort = 8080, //Note that this argument is required if you intend to configure pubsub, actors or workflows as of Aspire v9.0
DaprGrpcPort = 50001,
DaprHttpPort = 3500,
@ -124,7 +131,7 @@ DaprSidecarOptions sidecarOptions = new()
};
builder
.AddProject<Projects.MyOtherApp>("myotherapp")
.AddProject<Projects.BackEndApp>("be")
.WithReference(myApp)
.WithDaprSidecar(sidecarOptions);
```
@ -138,6 +145,9 @@ change in a future release as a fix has been merged and can be tracked [here](ht
{{% /alert %}}
Finally, let's add an endpoint to the back-end app that we can invoke using Dapr's service invocation to display to a
page to demonstrate that Dapr is working as expected.
When you open the solution in your IDE, ensure that the `aspiredemo.AppHost` is configured as your startup project, but
when you launch it in a debug configuration, you'll note that your integrated console should reflect your expected Dapr
logs and it will be available to your application.

View File

@ -0,0 +1,138 @@
---
type: docs
title: "Dapr .NET SDK Development with Dapr CLI"
linkTitle: "Experimental Attributes"
weight: 61000
description: Learn about local development with the Dapr CLI
---
## Experimental Attributes
### Introduction to Experimental Attributes
With the release of .NET 8, C# 12 introduced the `[Experimental]` attribute, which provides a standardized way to mark
APIs that are still in development or experimental. This attribute is defined in the `System.Diagnostics.CodeAnalysis`
namespace and requires a diagnostic ID parameter used to generate compiler warnings when the experimental API
is used.
In the Dapr .NET SDK, we now use the `[Experimental]` attribute instead of `[Obsolete]` to mark building blocks and
components that have not yet passed the stable lifecycle certification. This approach provides a clearer distinction
between:
1. **Experimental APIs** - Features that are available but still evolving and have not yet been certified as stable
according to the [Dapr Component Certification Lifecycle](https://docs.dapr.io/operations/components/certification-lifecycle/).
2. **Obsolete APIs** - Features that are truly deprecated and will be removed in a future release.
### Usage in the Dapr .NET SDK
In the Dapr .NET SDK, we apply the `[Experimental]` attribute at the class level for building blocks that are still in
the Alpha or Beta stages of the [Component Certification Lifecycle](https://docs.dapr.io/operations/components/certification-lifecycle/).
The attribute includes:
- A diagnostic ID that identifies the experimental building block
- A URL that points to the relevant documentation for that block
For example:
```csharp
using System.Diagnostics.CodeAnalysis;
namespace Dapr.Cryptography.Encryption
{
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public class DaprEncryptionClient
{
// Implementation
}
}
```
The diagnostic IDs follow a naming convention of `DAPR_[BUILDING_BLOCK_NAME]`, such as:
- `DAPR_CONVERSATION` - For the Conversation building block
- `DAPR_CRYPTOGRAPHY` - For the Cryptography building block
- `DAPR_JOBS` - For the Jobs building block
- `DAPR_DISTRIBUTEDLOCK` - For the Distributed Lock building block
### Suppressing Experimental Warnings
When you use APIs marked with the `[Experimental]` attribute, the compiler will generate errors.
To build your solution without marking your own code as experimental, you will need to suppress these errors. Here are
several approaches to do this:
#### Option 1: Using #pragma directive
You can use the `#pragma warning` directive to suppress the warning for specific sections of code:
```csharp
// Disable experimental warning
#pragma warning disable DAPR_CRYPTOGRAPHY
// Your code using the experimental API
var client = new DaprEncryptionClient();
// Re-enable the warning
#pragma warning restore DAPR_CRYPTOGRAPHY
```
This approach is useful when you want to suppress warnings only for specific sections of your code.
#### Option 2: Project-level suppression
To suppress warnings for an entire project, add the following to your `.csproj` file.
file.
```xml
<PropertyGroup>
<NoWarn>$(NoWarn);DAPR_CRYPTOGRAPHY</NoWarn>
</PropertyGroup>
```
You can include multiple diagnostic IDs separated by semicolons:
```xml
<PropertyGroup>
<NoWarn>$(NoWarn);DAPR_CONVERSATION;DAPR_JOBS;DAPR_DISTRIBUTEDLOCK;DAPR_CRYPTOGRAPHY</NoWarn>
</PropertyGroup>
```
This approach is particularly useful for test projects that need to use experimental APIs.
#### Option 3: Directory-level suppression
For suppressing warnings across multiple projects in a directory, add a `Directory.Build.props` file:
```xml
<PropertyGroup>
<NoWarn>$(NoWarn);DAPR_CONVERSATION;DAPR_JOBS;DAPR_DISTRIBUTEDLOCK;DAPR_CRYPTOGRAPHY</NoWarn>
</PropertyGroup>
```
This file should be placed in the root directory of your test projects. You can learn more about using
`Directory.Build.props` files in the
[MSBuild documentation](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory).
### Lifecycle of Experimental APIs
As building blocks move through the certification lifecycle and reach the "Stable" stage, the `[Experimental]` attribute will be removed. No migration or code changes will be required from users when this happens, except for the removal of any warning suppressions if they were added.
Conversely, the `[Obsolete]` attribute will now be reserved exclusively for APIs that are truly deprecated and scheduled for removal. When you see a method or class marked with `[Obsolete]`, you should plan to migrate away from it according to the migration guidance provided in the attribute message.
### Best Practices
1. **In application code:**
- Be cautious when using experimental APIs, as they may change in future releases
- Consider isolating usage of experimental APIs to make future updates easier
- Document your use of experimental APIs for team awareness
2. **In test code:**
- Use project-level suppression to avoid cluttering test code with warning suppressions
- Regularly review which experimental APIs you're using and check if they've been stabilized
3. **When contributing to the SDK:**
- Use `[Experimental]` for new building blocks that haven't completed certification
- Use `[Obsolete]` only for truly deprecated APIs
- Provide clear documentation links in the `UrlFormat` parameter
### Additional Resources
- [Dapr Component Certification Lifecycle](https://docs.dapr.io/operations/components/certification-lifecycle/)
- [C# Experimental Attribute Documentation](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-12.0/experimental-attribute)

View File

@ -1,67 +0,0 @@
---
type: docs
title: "Dapr .NET SDK Development with Project Tye"
linkTitle: "Project Tye"
weight: 50000
description: Learn about local development with Project Tye
---
## Project Tye
[.NET Project Tye](https://github.com/dotnet/tye/) is a microservices development tool designed to make running many .NET services easy. Tye enables you to store a configuration of multiple .NET services, processes, and container images as a runnable application.
Tye is advantageous for a .NET Dapr developer because:
- Tye has the ability to automate the dapr CLI built-in
- Tye understands .NET's conventions and requires almost no configuration for .NET services
- Tye can manage the lifetime of your dependencies in containers
Pros/cons:
- **Pro:** Tye can automate all of the steps described above. You no longer need to think about concepts like ports or app-ids.
- **Pro:** Since Tye can also manage containers for you, you can make those part of the application definition and stop the long-running containers on your machine.
### Using Tye
Follow the [Tye Getting Started](https://github.com/dotnet/tye/blob/master/docs/getting_started.md) to install the `tye` CLI and create a `tye.yaml` for your application.
Next follow the steps in the [Tye Dapr recipe](https://github.com/dotnet/tye/blob/master/docs/recipes/dapr.md) to add Dapr. Make sure to specify the relative path to your components folder with `components-path` in `tye.yaml`.
Next add any additional container dependencies and add component definitions to the folder you created earlier.
You should end up with something like this:
```yaml
name: store-application
extensions:
# Configuration for dapr goes here.
- name: dapr
components-path: <components-path>
# Services to run go here.
services:
# The name will be used as the app-id. For a .NET project, Tye only needs the path to the project file.
- name: orders
project: orders/orders.csproj
- name: products
project: products/products.csproj
- name: store
project: store/store.csproj
# Containers you want to run need an image name and set of ports to expose.
- name: redis
image: redis
bindings:
- port: 6973
```
Checkin `tye.yaml` in source control with the application code.
You can now use `tye run` to launch the whole application from one terminal. When running, Tye has a dashboard at `http://localhost:8000` to view application status and logs.
### Next steps
Tye runs your services locally as normal .NET process. If you need to debug, then use the attach feature of your debugger to attach to one of the running processes. Since Tye is .NET aware, it has the ability to [start a process suspended](https://github.com/dotnet/tye/blob/master/docs/reference/commandline/tye-run.md#options) for startup debugging.
Tye also has an [option](https://github.com/dotnet/tye/blob/master/docs/reference/commandline/tye-run.md#options) to run your services in containers if you wish to test locally in containers.

View File

@ -18,15 +18,9 @@ In the .NET example project:
## Prerequisites
- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/)
- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost)
- [.NET 6](https://dotnet.microsoft.com/download/dotnet/6.0), [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed
- [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed
- [Dapr.Jobs](https://www.nuget.org/packages/Dapr.Jobs) NuGet package installed to your project
{{% alert title="Note" color="primary" %}}
Note that while .NET 6 is the minimum support version of .NET in Dapr v1.15, only .NET 8 and .NET 9 will continue to be supported by Dapr in v1.16 and later.
{{% /alert %}}
## Set up the environment
Clone the [.NET SDK repo](https://github.com/dapr/dotnet-sdk).

View File

@ -17,15 +17,9 @@ runtime and which do not require an endpoint to be pre-configured. In this guide
## Prerequisites
- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/)
- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost)
- [.NET 6](https://dotnet.microsoft.com/download/dotnet/6.0), [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed
- [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed
- [Dapr.Messaging](https://www.nuget.org/packages/Dapr.Messaging) NuGet package installed to your project
{{% alert title="Note" color="primary" %}}
Note that while .NET 6 is the minimum support version of .NET in Dapr v1.15, only .NET 8 and .NET 9 will continue to be supported by Dapr in v1.16 and later.
{{% /alert %}}
## Set up the environment
Clone the [.NET SDK repo](https://github.com/dapr/dotnet-sdk).

View File

@ -20,14 +20,7 @@ In the .NET example project:
- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/)
- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/)
- [.NET 7](https://dotnet.microsoft.com/download/dotnet/7.0), [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed
{{% alert title="Note" color="primary" %}}
Dapr.Workflows supports .NET 7 or newer in v1.15. However, following the release of Dapr v1.16, only
.NET 8 and .NET 9 will be supported.
{{% /alert %}}
- [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed
## Set up the environment

View File

@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -2,12 +2,11 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Dapr.Actors\Dapr.Actors.csproj" />
<ProjectReference Include="..\IDemoActor\IDemoActor.csproj" />
<ProjectReference Include="..\DemoActor.Interfaces\DemoActor.Interfaces.csproj" />
</ItemGroup>
</Project>

View File

@ -11,146 +11,123 @@
// limitations under the License.
// ------------------------------------------------------------------------
using Dapr.Actors.Communication;
using System;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Actors;
using Dapr.Actors.Client;
using IDemoActor;
namespace ActorClient
var data = new MyData("ValueA", "ValueB");
// Create an actor Id.
var actorId = new ActorId("abc");
// Make strongly typed Actor calls with Remoting.
// DemoActor is the type registered with Dapr runtime in the service.
var proxy = ActorProxy.Create<IDemoActor.IDemoActor>(actorId, "DemoActor");
Console.WriteLine("Making call using actor proxy to save 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}.");
// Making some more calls to test methods.
try
{
using System;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Actors;
using Dapr.Actors.Client;
Console.WriteLine("Making calls to an actor method which has no argument and no return type.");
await proxy.TestNoArgumentNoReturnType();
}
catch (Exception ex)
{
Console.WriteLine($"ERROR: Got exception while making call to method with No Argument & No Return Type. Exception: {ex}");
}
/// <summary>
/// Actor Client class.
/// </summary>
public class Program
try
{
await proxy.TestThrowException();
}
catch (ActorMethodInvocationException ex)
{
if (ex.InnerException is ActorInvokeException invokeEx && invokeEx.ActualExceptionType is "System.NotImplementedException")
{
/// <summary>
/// Entry point.
/// </summary>
/// <param name="args">Arguments.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task Main(string[] args)
{
var data = new MyData()
{
PropertyA = "ValueA",
PropertyB = "ValueB",
};
// Create an actor Id.
var actorId = new ActorId("abc");
// Make strongly typed Actor calls with Remoting.
// DemoActor is the type registered with Dapr runtime in the service.
var proxy = ActorProxy.Create<IDemoActor.IDemoActor>(actorId, "DemoActor");
Console.WriteLine("Making call using actor proxy to save 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}.");
// Making some more calls to test methods.
try
{
Console.WriteLine("Making calls to an actor method which has no argument and no return type.");
await proxy.TestNoArgumentNoReturnType();
}
catch (Exception ex)
{
Console.WriteLine($"ERROR: Got exception while making call to method with No Argument & No Return Type. Exception: {ex}");
}
try
{
await proxy.TestThrowException();
}
catch (ActorMethodInvocationException ex)
{
if (ex.InnerException is ActorInvokeException invokeEx && invokeEx.ActualExceptionType is "System.NotImplementedException")
{
Console.WriteLine($"Got Correct Exception from actor method invocation.");
}
else
{
Console.WriteLine($"Got Incorrect Exception from actor method invocation. Exception {ex.InnerException}");
}
}
// Making calls without Remoting, this shows method invocation using InvokeMethodAsync methods, the method name and its payload is provided as arguments to InvokeMethodAsync methods.
Console.WriteLine("Making calls without Remoting.");
var nonRemotingProxy = ActorProxy.Create(actorId, "DemoActor");
await nonRemotingProxy.InvokeMethodAsync("TestNoArgumentNoReturnType");
await nonRemotingProxy.InvokeMethodAsync("SaveData", data);
await nonRemotingProxy.InvokeMethodAsync<MyData>("GetData");
Console.WriteLine("Registering the timer and reminder");
await proxy.RegisterTimer();
await proxy.RegisterReminder();
Console.WriteLine("Waiting so the timer and reminder can be triggered");
await Task.Delay(6000);
Console.WriteLine("Making call using actor proxy to get data after timer and reminder triggered");
receivedData = await proxy.GetData();
Console.WriteLine($"Received data is {receivedData}.");
Console.WriteLine("Getting details of the registered reminder");
var reminder = await proxy.GetReminder();
Console.WriteLine($"Received reminder is {reminder}.");
Console.WriteLine("Deregistering timer. Timers would any way stop if the actor is deactivated as part of Dapr garbage collection.");
await proxy.UnregisterTimer();
Console.WriteLine("Deregistering reminder. Reminders are durable and would not stop until an explicit deregistration or the actor is deleted.");
await proxy.UnregisterReminder();
Console.WriteLine("Registering reminder with repetitions - The reminder will repeat 3 times.");
await proxy.RegisterReminderWithRepetitions(3);
Console.WriteLine("Waiting so the reminder can be triggered");
await Task.Delay(5000);
Console.WriteLine("Getting details of the registered reminder");
reminder = await proxy.GetReminder();
Console.WriteLine($"Received reminder is {reminder?.ToString() ?? "None"} (expecting None).");
Console.WriteLine("Registering reminder with ttl and repetitions, i.e. reminder stops when either condition is met - The reminder will repeat 2 times.");
await proxy.RegisterReminderWithTtlAndRepetitions(TimeSpan.FromSeconds(5), 2);
Console.WriteLine("Getting details of the registered reminder");
reminder = await proxy.GetReminder();
Console.WriteLine($"Received reminder is {reminder}.");
Console.WriteLine("Deregistering reminder. Reminders are durable and would not stop until an explicit deregistration or the actor is deleted.");
await proxy.UnregisterReminder();
Console.WriteLine("Registering reminder and Timer with TTL - The reminder will self delete after 10 seconds.");
await proxy.RegisterReminderWithTtl(TimeSpan.FromSeconds(10));
await proxy.RegisterTimerWithTtl(TimeSpan.FromSeconds(10));
Console.WriteLine("Getting details of the registered reminder");
reminder = await proxy.GetReminder();
Console.WriteLine($"Received reminder is {reminder}.");
// Track the reminder.
var timer = new Timer(async state => Console.WriteLine($"Received data: {await proxy.GetData()}"), null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
await Task.Delay(TimeSpan.FromSeconds(21));
await timer.DisposeAsync();
Console.WriteLine("Creating a Bank Actor");
var bank = ActorProxy.Create<IBankActor>(ActorId.CreateRandom(), "DemoActor");
while (true)
{
var balance = await bank.GetAccountBalance();
Console.WriteLine($"Balance for account '{balance.AccountId}' is '{balance.Balance:c}'.");
Console.WriteLine($"Withdrawing '{10m:c}'...");
try
{
await bank.Withdraw(new WithdrawRequest() { Amount = 10m, });
}
catch (ActorMethodInvocationException ex)
{
Console.WriteLine("Overdraft: " + ex.Message);
break;
}
}
}
Console.WriteLine($"Got Correct Exception from actor method invocation.");
}
else
{
Console.WriteLine($"Got Incorrect Exception from actor method invocation. Exception {ex.InnerException}");
}
}
// Making calls without Remoting, this shows method invocation using InvokeMethodAsync methods, the method name and its payload is provided as arguments to InvokeMethodAsync methods.
Console.WriteLine("Making calls without Remoting.");
var nonRemotingProxy = ActorProxy.Create(actorId, "DemoActor");
await nonRemotingProxy.InvokeMethodAsync("TestNoArgumentNoReturnType");
await nonRemotingProxy.InvokeMethodAsync("SaveData", data);
await nonRemotingProxy.InvokeMethodAsync<MyData>("GetData");
Console.WriteLine("Registering the timer and reminder");
await proxy.RegisterTimer();
await proxy.RegisterReminder();
Console.WriteLine("Waiting so the timer and reminder can be triggered");
await Task.Delay(6000);
Console.WriteLine("Making call using actor proxy to get data after timer and reminder triggered");
receivedData = await proxy.GetData();
Console.WriteLine($"Received data is {receivedData}.");
Console.WriteLine("Getting details of the registered reminder");
var reminder = await proxy.GetReminder();
Console.WriteLine($"Received reminder is {reminder}.");
Console.WriteLine("Deregistering timer. Timers would any way stop if the actor is deactivated as part of Dapr garbage collection.");
await proxy.UnregisterTimer();
Console.WriteLine("Deregistering reminder. Reminders are durable and would not stop until an explicit deregistration or the actor is deleted.");
await proxy.UnregisterReminder();
Console.WriteLine("Registering reminder with repetitions - The reminder will repeat 3 times.");
await proxy.RegisterReminderWithRepetitions(3);
Console.WriteLine("Waiting so the reminder can be triggered");
await Task.Delay(5000);
Console.WriteLine("Getting details of the registered reminder");
reminder = await proxy.GetReminder();
Console.WriteLine($"Received reminder is {reminder?.ToString() ?? "None"} (expecting None).");
Console.WriteLine("Registering reminder with ttl and repetitions, i.e. reminder stops when either condition is met - The reminder will repeat 2 times.");
await proxy.RegisterReminderWithTtlAndRepetitions(TimeSpan.FromSeconds(5), 2);
Console.WriteLine("Getting details of the registered reminder");
reminder = await proxy.GetReminder();
Console.WriteLine($"Received reminder is {reminder}.");
Console.WriteLine("Deregistering reminder. Reminders are durable and would not stop until an explicit deregistration or the actor is deleted.");
await proxy.UnregisterReminder();
Console.WriteLine("Registering reminder and Timer with TTL - The reminder will self delete after 10 seconds.");
await proxy.RegisterReminderWithTtl(TimeSpan.FromSeconds(10));
await proxy.RegisterTimerWithTtl(TimeSpan.FromSeconds(10));
Console.WriteLine("Getting details of the registered reminder");
reminder = await proxy.GetReminder();
Console.WriteLine($"Received reminder is {reminder}.");
// Track the reminder.
var timer = new Timer(async state => Console.WriteLine($"Received data: {await proxy.GetData()}"), null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
await Task.Delay(TimeSpan.FromSeconds(21));
await timer.DisposeAsync();
Console.WriteLine("Creating a Bank Actor");
var bank = ActorProxy.Create<IBankActor>(ActorId.CreateRandom(), "DemoActor");
while (true)
{
var balance = await bank.GetAccountBalance();
Console.WriteLine($"Balance for account '{balance.AccountId}' is '{balance.Balance:c}'.");
Console.WriteLine($"Withdrawing '{10m:c}'...");
try
{
await bank.Withdraw(new WithdrawRequest(10m));
}
catch (ActorMethodInvocationException ex)
{
Console.WriteLine($"Overdraft: {ex.Message}");
break;
}
}

View File

@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<RootNamespace>IDemoActor</RootNamespace>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>

View File

@ -15,32 +15,18 @@ using System;
using System.Threading.Tasks;
using Dapr.Actors;
namespace IDemoActor
namespace IDemoActor;
public interface IBankActor : IActor
{
public interface IBankActor : IActor
{
Task<AccountBalance> GetAccountBalance();
Task<AccountBalance> GetAccountBalance();
Task Withdraw(WithdrawRequest withdraw);
}
public class AccountBalance
{
public string AccountId { get; set; }
public decimal Balance { get; set; }
}
public class WithdrawRequest
{
public decimal Amount { get; set; }
}
public class OverdraftException : Exception
{
public OverdraftException(decimal balance, decimal amount)
: base($"Your current balance is {balance:c} - that's not enough to withdraw {amount:c}.")
{
}
}
Task Withdraw(WithdrawRequest withdraw);
}
public sealed record AccountBalance(string AccountId, decimal Balance);
public sealed record WithdrawRequest(decimal Amount);
public class OverdraftException(decimal balance, decimal amount)
: Exception($"Your current balance is {balance:c} - that's not enough to withdraw {amount:c}.");

View File

@ -0,0 +1,129 @@
// ------------------------------------------------------------------------
// 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.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
using System.Threading.Tasks;
using Dapr.Actors;
namespace IDemoActor;
/// <summary>
/// Interface for Actor method.
/// </summary>
public interface IDemoActor : IActor
{
/// <summary>
/// Method to save data.
/// </summary>
/// <param name="data">DAta to save.</param>
/// <param name="ttl">TTL of state key.</param>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task SaveData(MyData data, TimeSpan ttl);
/// <summary>
/// Method to get data.
/// </summary>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task<MyData> GetData();
/// <summary>
/// A test method which throws exception.
/// </summary>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task TestThrowException();
/// <summary>
/// A test method which validates calls for methods with no arguments and no return types.
/// </summary>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task TestNoArgumentNoReturnType();
/// <summary>
/// Registers a reminder.
/// </summary>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task RegisterReminder();
/// <summary>
/// Registers a reminder.
/// </summary>
/// <param name="ttl">TimeSpan that dictates when the reminder expires.</param>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task RegisterReminderWithTtl(TimeSpan ttl);
/// <summary>
/// Unregisters the registered reminder.
/// </summary>
/// <returns>Task representing the operation.</returns>
Task UnregisterReminder();
/// <summary>
/// Registers a timer.
/// </summary>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task RegisterTimer();
/// <summary>
/// Registers a timer.
/// </summary>
/// <param name="ttl">Optional TimeSpan that dictates when the timer expires.</param>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task RegisterTimerWithTtl(TimeSpan ttl);
/// <summary>
/// Registers a reminder with repetitions.
/// </summary>
/// <param name="repetitions">The number of repetitions for which the reminder should be invoked.</param>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task RegisterReminderWithRepetitions(int repetitions);
/// <summary>
/// Registers a reminder with ttl and repetitions.
/// </summary>
/// <param name="ttl">TimeSpan that dictates when the timer expires.</param>
/// <param name="repetitions">The number of repetitions for which the reminder should be invoked.</param>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task RegisterReminderWithTtlAndRepetitions(TimeSpan ttl, int repetitions);
/// <summary>
/// Gets the registered reminder.
/// </summary>
/// <returns>A task that returns the reminder after completion.</returns>
Task<ActorReminderData?> GetReminder();
/// <summary>
/// Unregisters the registered timer.
/// </summary>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task UnregisterTimer();
}
/// <summary>
/// Data Used by the Sample Actor.
/// </summary>
public sealed record MyData(string? PropertyA, string? PropertyB)
{
/// <inheritdoc/>
public override string ToString() => $"PropertyA: {PropertyA ?? "null"}, PropertyB: {PropertyB ?? "null"}";
}
public class ActorReminderData
{
public string? Name { get; set; }
public TimeSpan DueTime { get; set; }
public TimeSpan Period { get; set; }
public override string ToString() => $"Name: {this.Name}, DueTime: {this.DueTime}, Period: {this.Period}";
}

View File

@ -13,24 +13,23 @@
using IDemoActor;
namespace DemoActor
namespace DemoActor;
public sealed class BankService
{
public class BankService
// Allow overdraft of up to 50 (of whatever currency).
private const decimal OverdraftThreshold = -50m;
public decimal Withdraw(decimal balance, decimal amount)
{
// Allow overdraft of up to 50 (of whatever currency).
private readonly decimal OverdraftThreshold = -50m;
// Imagine putting some complex auditing logic here in addition to the basics.
public decimal Withdraw(decimal balance, decimal amount)
var updated = balance - amount;
if (updated < OverdraftThreshold)
{
// Imagine putting some complex auditing logic here in addition to the basics.
var updated = balance - amount;
if (updated < OverdraftThreshold)
{
throw new OverdraftException(balance, amount);
}
return updated;
throw new OverdraftException(balance, amount);
}
return updated;
}
}

View File

@ -17,195 +17,182 @@ using System.Threading.Tasks;
using Dapr.Actors.Runtime;
using IDemoActor;
namespace DemoActor
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.
// In this case the actor interfaces are DemoActor.Interfaces and IBankActor.
//
// 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(ActorHost host, BankService bank) : Actor(host), IDemoActor.IDemoActor, IBankActor, IRemindable
{
// 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.
// In this case the actor interfaces are IDemoActor and IBankActor.
//
// 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.IDemoActor, IBankActor, IRemindable
private const string StateName = "my_data";
public async Task SaveData(MyData data, TimeSpan ttl)
{
private const string StateName = "my_data";
Console.WriteLine($"This is Actor id {this.Id} with data {data}.");
private readonly BankService bank;
// Set State using StateManager, state is saved after the method execution.
await this.StateManager.SetStateAsync(StateName, data, ttl);
}
public DemoActor(ActorHost host, BankService bank)
: base(host)
{
// BankService is provided by dependency injection.
// See Program.cs
this.bank = bank;
}
public Task<MyData> GetData()
{
// Get state using StateManager.
return this.StateManager.GetStateAsync<MyData>(StateName);
}
public async Task SaveData(MyData data, TimeSpan ttl)
{
Console.WriteLine($"This is Actor id {this.Id} with data {data}.");
public Task TestThrowException()
{
throw new NotImplementedException();
}
// Set State using StateManager, state is saved after the method execution.
await this.StateManager.SetStateAsync<MyData>(StateName, data, ttl);
}
public Task TestNoArgumentNoReturnType()
{
return Task.CompletedTask;
}
public Task<MyData> GetData()
{
// Get state using StateManager.
return this.StateManager.GetStateAsync<MyData>(StateName);
}
public async Task RegisterReminder()
{
await this.RegisterReminderAsync("TestReminder", null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
}
public Task TestThrowException()
{
throw new NotImplementedException();
}
public Task TestNoArgumentNoReturnType()
{
return Task.CompletedTask;
}
public async Task RegisterReminder()
{
await this.RegisterReminderAsync("TestReminder", null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
}
public async Task RegisterReminderWithTtl(TimeSpan ttl)
{
await this.RegisterReminderAsync("TestReminder", null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5), ttl);
}
public async Task RegisterReminderWithTtl(TimeSpan ttl)
{
await this.RegisterReminderAsync("TestReminder", null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5), ttl);
}
public async Task RegisterReminderWithRepetitions(int repetitions)
{
await this.RegisterReminderAsync("TestReminder", null, TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1), repetitions);
}
public async Task RegisterReminderWithRepetitions(int repetitions)
{
await this.RegisterReminderAsync("TestReminder", null, TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1), repetitions);
}
public async Task RegisterReminderWithTtlAndRepetitions(TimeSpan ttl, int repetitions)
{
await this.RegisterReminderAsync("TestReminder", null, TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1), repetitions, ttl);
}
public async Task RegisterReminderWithTtlAndRepetitions(TimeSpan ttl, int repetitions)
{
await this.RegisterReminderAsync("TestReminder", null, TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1), repetitions, ttl);
}
public async Task<ActorReminderData> GetReminder()
{
var reminder = await this.GetReminderAsync("TestReminder");
public async Task<ActorReminderData?> GetReminder()
{
var reminder = await this.GetReminderAsync("TestReminder");
return reminder is not null
? new ActorReminderData
{
Name = reminder.Name,
Period = reminder.Period,
DueTime = reminder.DueTime
}
: null;
}
return reminder is not null
? new ActorReminderData
{
Name = reminder.Name,
Period = reminder.Period,
DueTime = reminder.DueTime
}
: null;
}
public Task UnregisterReminder()
public Task UnregisterReminder()
{
return this.UnregisterReminderAsync("TestReminder");
}
public async Task ReceiveReminderAsync(string reminderName, byte[] state, TimeSpan dueTime, TimeSpan period)
{
// This method is invoked when an actor reminder is fired.
var actorState = await this.StateManager.GetStateAsync<MyData>(StateName);
var updatedActorState = actorState with
{
return this.UnregisterReminderAsync("TestReminder");
}
PropertyB = $"Reminder triggered at '{DateTime.Now:yyyy-MM-ddTHH:mm:ss}'"
};
await this.StateManager.SetStateAsync<MyData>(StateName, updatedActorState, ttl: TimeSpan.FromMinutes(5));
}
public async Task ReceiveReminderAsync(string reminderName, byte[] state, TimeSpan dueTime, TimeSpan period)
class TimerParams
{
public int IntParam { get; set; }
public string? StringParam { get; set; }
}
/// <inheritdoc/>
public Task RegisterTimer()
{
var timerParams = new TimerParams
{
// This method is invoked when an actor reminder is fired.
var actorState = await this.StateManager.GetStateAsync<MyData>(StateName);
actorState.PropertyB = $"Reminder triggered at '{DateTime.Now:yyyy-MM-ddTHH:mm:ss}'";
await this.StateManager.SetStateAsync<MyData>(StateName, actorState, ttl: TimeSpan.FromMinutes(5));
}
IntParam = 100,
StringParam = "timer test",
};
class TimerParams
var serializedTimerParams = JsonSerializer.SerializeToUtf8Bytes(timerParams);
return this.RegisterTimerAsync("TestTimer", nameof(this.TimerCallback), serializedTimerParams, TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3));
}
public Task RegisterTimerWithTtl(TimeSpan ttl)
{
var timerParams = new TimerParams
{
public int IntParam { get; set; }
public string StringParam { get; set; }
}
IntParam = 100,
StringParam = "timer test",
};
/// <inheritdoc/>
public Task RegisterTimer()
var serializedTimerParams = JsonSerializer.SerializeToUtf8Bytes(timerParams);
return this.RegisterTimerAsync("TestTimer", nameof(this.TimerCallback), serializedTimerParams, TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3), ttl);
}
public Task UnregisterTimer()
{
return this.UnregisterTimerAsync("TestTimer");
}
// This method is called whenever an actor is activated.
// An actor is activated the first time any of its methods are invoked.
protected override Task OnActivateAsync()
{
// Provides opportunity to perform some optional setup.
return Task.CompletedTask;
}
// This method is called whenever an actor is deactivated after a period of inactivity.
protected override Task OnDeactivateAsync()
{
// Provides Opportunity to perform optional cleanup.
return Task.CompletedTask;
}
/// <summary>
/// This method is called when the timer is triggered based on its registration.
/// It updates the PropertyA value.
/// </summary>
/// <param name="data">Timer input data.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task TimerCallback(byte[] data)
{
var state = await this.StateManager.GetStateAsync<MyData>(StateName);
var updatedState = state with { PropertyA = $"Timer triggered at '{DateTime.Now:yyyyy-MM-ddTHH:mm:s}'" };
await this.StateManager.SetStateAsync<MyData>(StateName, updatedState, ttl: TimeSpan.FromMinutes(5));
var timerParams = JsonSerializer.Deserialize<TimerParams>(data);
if (timerParams != null)
{
var timerParams = new TimerParams
{
IntParam = 100,
StringParam = "timer test",
};
var serializedTimerParams = JsonSerializer.SerializeToUtf8Bytes(timerParams);
return this.RegisterTimerAsync("TestTimer", nameof(this.TimerCallback), serializedTimerParams, TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3));
}
public Task RegisterTimerWithTtl(TimeSpan ttl)
{
var timerParams = new TimerParams
{
IntParam = 100,
StringParam = "timer test",
};
var serializedTimerParams = JsonSerializer.SerializeToUtf8Bytes(timerParams);
return this.RegisterTimerAsync("TestTimer", nameof(this.TimerCallback), serializedTimerParams, TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(3), ttl);
}
public Task UnregisterTimer()
{
return this.UnregisterTimerAsync("TestTimer");
}
// This method is called whenever an actor is activated.
// An actor is activated the first time any of its methods are invoked.
protected override Task OnActivateAsync()
{
// Provides opportunity to perform some optional setup.
return Task.CompletedTask;
}
// This method is called whenever an actor is deactivated after a period of inactivity.
protected override Task OnDeactivateAsync()
{
// Provides Opportunity to perform optional cleanup.
return Task.CompletedTask;
}
/// <summary>
/// This method is called when the timer is triggered based on its registration.
/// It updates the PropertyA value.
/// </summary>
/// <param name="data">Timer input data.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task TimerCallback(byte[] data)
{
var state = await this.StateManager.GetStateAsync<MyData>(StateName);
state.PropertyA = $"Timer triggered at '{DateTime.Now:yyyyy-MM-ddTHH:mm:s}'";
await this.StateManager.SetStateAsync<MyData>(StateName, state, ttl: TimeSpan.FromMinutes(5));
var timerParams = JsonSerializer.Deserialize<TimerParams>(data);
Console.WriteLine("Timer parameter1: " + timerParams.IntParam);
Console.WriteLine("Timer parameter2: " + timerParams.StringParam);
}
public async Task<AccountBalance> GetAccountBalance()
{
var starting = new AccountBalance()
{
AccountId = this.Id.GetId(),
Balance = 100m, // Start new accounts with 100, we're pretty generous.
};
var balance = await this.StateManager.GetOrAddStateAsync<AccountBalance>("balance", starting);
return balance;
}
public async Task Withdraw(WithdrawRequest withdraw)
{
var starting = new AccountBalance()
{
AccountId = this.Id.GetId(),
Balance = 100m, // Start new accounts with 100, we're pretty generous.
};
var balance = await this.StateManager.GetOrAddStateAsync<AccountBalance>("balance", starting);
// Throws Overdraft exception if the account doesn't have enough money.
var updated = this.bank.Withdraw(balance.Balance, withdraw.Amount);
balance.Balance = updated;
await this.StateManager.SetStateAsync("balance", balance);
Console.WriteLine($"Timer parameter1: {timerParams.IntParam}");
Console.WriteLine($"Timer parameter2: {timerParams.StringParam ?? ""}");
}
}
public async Task<AccountBalance> GetAccountBalance()
{
var starting = new AccountBalance(this.Id.GetId(), 100m); // Start new accounts with 100 million; we're pretty generous
var balance = await this.StateManager.GetOrAddStateAsync<AccountBalance>("balance", starting);
return balance;
}
public async Task Withdraw(WithdrawRequest withdraw)
{
var starting = new AccountBalance(this.Id.GetId(), 100m); // Start new accounts with 100 million; we're pretty generous.
var balance = await this.StateManager.GetOrAddStateAsync<AccountBalance>("balance", starting);
// Throws Overdraft exception if the account doesn't have enough money.
var updated = bank.Withdraw(balance.Balance, withdraw.Amount);
balance = balance with { Balance = updated };
await this.StateManager.SetStateAsync("balance", balance);
}
}

View File

@ -1,13 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<IsPublishable>true</IsPublishable>
<EnableSdkContainerSupport>true</EnableSdkContainerSupport>
<ContainerRepository>demo-actor</ContainerRepository>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
@ -18,7 +15,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\src\Dapr.Actors.AspNetCore\Dapr.Actors.AspNetCore.csproj" />
<ProjectReference Include="..\..\..\src\Dapr.Actors\Dapr.Actors.csproj" />
<ProjectReference Include="..\IDemoActor\IDemoActor.csproj" />
<ProjectReference Include="..\DemoActor.Interfaces\DemoActor.Interfaces.csproj" />
</ItemGroup>
</Project>

View File

@ -11,23 +11,23 @@
// limitations under the License.
// ------------------------------------------------------------------------
using Microsoft.AspNetCore.Hosting;
using DemoActor;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace DemoActor
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<BankService>();
builder.Services.AddActors(options =>
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
options.Actors.RegisterActor<DemoActor.DemoActor>();
});
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.MapActorsHandlers();

View File

@ -1,59 +0,0 @@
// ------------------------------------------------------------------------
// 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.
// 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.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)
{
this.Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<BankService>();
services.AddActors(options =>
{
options.Actors.RegisterActor<DemoActor>();
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapActorsHandlers();
});
}
}
}

View File

@ -1,149 +0,0 @@
// ------------------------------------------------------------------------
// 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.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System;
using System.Threading.Tasks;
using Dapr.Actors;
namespace IDemoActor
{
/// <summary>
/// Interface for Actor method.
/// </summary>
public interface IDemoActor : IActor
{
/// <summary>
/// Method to save data.
/// </summary>
/// <param name="data">DAta to save.</param>
/// <param name="ttl">TTL of state key.</param>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task SaveData(MyData data, TimeSpan ttl);
/// <summary>
/// Method to get data.
/// </summary>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task<MyData> GetData();
/// <summary>
/// A test method which throws exception.
/// </summary>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task TestThrowException();
/// <summary>
/// A test method which validates calls for methods with no arguments and no return types.
/// </summary>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task TestNoArgumentNoReturnType();
/// <summary>
/// Registers a reminder.
/// </summary>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task RegisterReminder();
/// <summary>
/// Registers a reminder.
/// </summary>
/// <param name="ttl">TimeSpan that dictates when the reminder expires.</param>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task RegisterReminderWithTtl(TimeSpan ttl);
/// <summary>
/// Unregisters the registered reminder.
/// </summary>
/// <returns>Task representing the operation.</returns>
Task UnregisterReminder();
/// <summary>
/// Registers a timer.
/// </summary>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task RegisterTimer();
/// <summary>
/// Registers a timer.
/// </summary>
/// <param name="ttl">Optional TimeSpan that dictates when the timer expires.</param>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task RegisterTimerWithTtl(TimeSpan ttl);
/// <summary>
/// Registers a reminder with repetitions.
/// </summary>
/// <param name="repetitions">The number of repetitions for which the reminder should be invoked.</param>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task RegisterReminderWithRepetitions(int repetitions);
/// <summary>
/// Registers a reminder with ttl and repetitions.
/// </summary>
/// <param name="ttl">TimeSpan that dictates when the timer expires.</param>
/// <param name="repetitions">The number of repetitions for which the reminder should be invoked.</param>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task RegisterReminderWithTtlAndRepetitions(TimeSpan ttl, int repetitions);
/// <summary>
/// Gets the registered reminder.
/// </summary>
/// <param name="reminderName">The name of the reminder.</param>
/// <returns>A task that returns the reminder after completion.</returns>
Task<ActorReminderData> GetReminder();
/// <summary>
/// Unregisters the registered timer.
/// </summary>
/// <returns>A task that represents the asynchronous save operation.</returns>
Task UnregisterTimer();
}
/// <summary>
/// Data Used by the Sample Actor.
/// </summary>
public class MyData
{
/// <summary>
/// Gets or sets the value for PropertyA.
/// </summary>
public string PropertyA { get; set; }
/// <summary>
/// Gets or sets the value for PropertyB.
/// </summary>
public string PropertyB { get; set; }
/// <inheritdoc/>
public override string ToString()
{
var propAValue = this.PropertyA ?? "null";
var propBValue = this.PropertyB ?? "null";
return $"PropertyA: {propAValue}, PropertyB: {propBValue}";
}
}
public class ActorReminderData
{
public string Name { get; set; }
public TimeSpan DueTime { get; set; }
public TimeSpan Period { get; set; }
public override string ToString()
{
return $"Name: {this.Name}, DueTime: {this.DueTime}, Period: {this.Period}";
}
}
}

View File

@ -4,7 +4,7 @@ The Actor example shows how to create a virtual actor (`DemoActor`) and invoke i
## Prerequisites
- [.NET 6+](https://dotnet.microsoft.com/download) installed
- [.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/install-dapr-selfhost/)
- [Dapr .NET SDK](https://github.com/dapr/dotnet-sdk/)

View File

@ -11,21 +11,20 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace ControllerSample
namespace ControllerSample;
/// <summary>
/// Class representing an Account for samples.
/// </summary>
public class Account
{
/// <summary>
/// Class representing an Account for samples.
/// Gets or sets account id.
/// </summary>
public class Account
{
/// <summary>
/// Gets or sets account id.
/// </summary>
public string Id { get; set; }
public string Id { get; set; }
/// <summary>
/// Gets or sets account balance.
/// </summary>
public decimal Balance { get; set; }
}
/// <summary>
/// Gets or sets account balance.
/// </summary>
public decimal Balance { get; set; }
}

View File

@ -1,9 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Dapr.AspNetCore\Dapr.AspNetCore.csproj" />
</ItemGroup>

View File

@ -13,286 +13,285 @@
using System.Linq;
namespace ControllerSample.Controllers
namespace ControllerSample.Controllers;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Dapr;
using Dapr.AspNetCore;
using Dapr.Client;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
/// <summary>
/// Sample showing Dapr integration with controller.
/// </summary>
[ApiController]
public class SampleController : ControllerBase
{
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Dapr;
using Dapr.AspNetCore;
using Dapr.Client;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
/// <summary>
/// SampleController Constructor with logger injection
/// </summary>
/// <param name="logger"></param>
public SampleController(ILogger<SampleController> logger)
{
this.logger = logger;
}
/// <summary>
/// Sample showing Dapr integration with controller.
/// State store name.
/// </summary>
[ApiController]
public class SampleController : ControllerBase
public const string StoreName = "statestore";
private readonly ILogger<SampleController> logger;
/// <summary>
/// Gets the account information as specified by the id.
/// </summary>
/// <param name="account">Account information for the id from Dapr state store.</param>
/// <returns>Account information.</returns>
[HttpGet("{account}")]
public ActionResult<Account> Get([FromState(StoreName)] StateEntry<Account> account)
{
/// <summary>
/// SampleController Constructor with logger injection
/// </summary>
/// <param name="logger"></param>
public SampleController(ILogger<SampleController> logger)
if (account.Value is null)
{
this.logger = logger;
return this.NotFound();
}
/// <summary>
/// State store name.
/// </summary>
public const string StoreName = "statestore";
return account.Value;
}
private readonly ILogger<SampleController> logger;
/// <summary>
/// Method for depositing to account as specified in transaction.
/// </summary>
/// <param name="transaction">Transaction info.</param>
/// <param name="daprClient">State client to interact with Dapr runtime.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
/// "pubsub", the first parameter into the Topic attribute, is name of the default pub/sub configured by the Dapr CLI.
[Topic("pubsub", "deposit", "amountDeadLetterTopic", false)]
[HttpPost("deposit")]
public async Task<ActionResult<Account>> 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));
/// <summary>
/// Gets the account information as specified by the id.
/// </summary>
/// <param name="account">Account information for the id from Dapr state store.</param>
/// <returns>Account information.</returns>
[HttpGet("{account}")]
public ActionResult<Account> Get([FromState(StoreName)] StateEntry<Account> account)
logger.LogInformation(headerEntries);
logger.LogInformation("Enter deposit");
var state = await daprClient.GetStateEntryAsync<Account>(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);
if (transaction.Amount < 0m)
{
if (account.Value is null)
{
return this.NotFound();
}
return account.Value;
}
/// <summary>
/// Method for depositing to account as specified in transaction.
/// </summary>
/// <param name="transaction">Transaction info.</param>
/// <param name="daprClient">State client to interact with Dapr runtime.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
/// "pubsub", the first parameter into the Topic attribute, is name of the default pub/sub configured by the Dapr CLI.
[Topic("pubsub", "deposit", "amountDeadLetterTopic", false)]
[HttpPost("deposit")]
public async Task<ActionResult<Account>> 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<Account>(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);
if (transaction.Amount < 0m)
{
return BadRequest(new { statusCode = 400, message = "bad request" });
}
state.Value.Balance += transaction.Amount;
logger.LogInformation("Balance for Id {0} is {1}", state.Value.Id, state.Value.Balance);
await state.SaveAsync();
return state.Value;
}
/// <summary>
/// Method for depositing multiple times to the account as specified in transaction.
/// </summary>
/// <param name="bulkMessage">List of entries of type BulkMessageModel received from dapr.</param>
/// <param name="daprClient">State client to interact with Dapr runtime.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
/// "pubsub", the first parameter into the Topic attribute, is name of the default pub/sub configured by the Dapr CLI.
[Topic("pubsub", "multideposit", "amountDeadLetterTopic", false)]
[BulkSubscribe("multideposit", 500, 2000)]
[HttpPost("multideposit")]
public async Task<ActionResult<BulkSubscribeAppResponse>> MultiDeposit([FromBody]
BulkSubscribeMessage<BulkMessageModel<Transaction>>
bulkMessage, [FromServices] DaprClient daprClient)
{
logger.LogInformation("Enter bulk deposit");
List<BulkSubscribeAppResponseEntry> entries = new List<BulkSubscribeAppResponseEntry>();
foreach (var entry in bulkMessage.Entries)
{
try
{
var transaction = entry.Event.Data;
var state = await daprClient.GetStateEntryAsync<Account>(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);
if (transaction.Amount < 0m)
{
return BadRequest(new { statusCode = 400, message = "bad request" });
}
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)
{
logger.LogError(e.Message);
entries.Add(new BulkSubscribeAppResponseEntry(entry.EntryId, BulkSubscribeAppResponseStatus.RETRY));
}
}
return new BulkSubscribeAppResponse(entries);
}
/// <summary>
/// Method for viewing the error message when the deposit/withdrawal amounts
/// are negative.
/// </summary>
/// <param name="transaction">Transaction info.</param>
[Topic("pubsub", "amountDeadLetterTopic")]
[HttpPost("deadLetterTopicRoute")]
public ActionResult<Account> ViewErrorMessage(Transaction transaction)
{
logger.LogInformation("The amount cannot be negative: {0}", transaction.Amount);
return Ok();
}
/// <summary>
/// Method for withdrawing from account as specified in transaction.
/// </summary>
/// <param name="transaction">Transaction info.</param>
/// <param name="daprClient">State client to interact with Dapr runtime.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
/// "pubsub", the first parameter into the Topic attribute, is name of the default pub/sub configured by the Dapr CLI.
[Topic("pubsub", "withdraw", "amountDeadLetterTopic", false)]
[HttpPost("withdraw")]
public async Task<ActionResult<Account>> Withdraw(Transaction transaction, [FromServices] DaprClient daprClient)
{
logger.LogInformation("Enter withdraw method...");
var state = await daprClient.GetStateEntryAsync<Account>(StoreName, transaction.Id);
logger.LogInformation("Id is {0}, the amount to be withdrawn is {1}", transaction.Id, transaction.Amount);
if (state.Value == null)
{
return this.NotFound();
}
if (transaction.Amount < 0m)
{
return BadRequest(new { statusCode = 400, message = "bad request" });
}
state.Value.Balance -= transaction.Amount;
logger.LogInformation("Balance is {0}", state.Value.Balance);
await state.SaveAsync();
return state.Value;
}
/// <summary>
/// Method for withdrawing from account as specified in transaction.
/// </summary>
/// <param name="transaction">Transaction info.</param>
/// <param name="daprClient">State client to interact with Dapr runtime.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
/// "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<ActionResult<Account>> WithdrawV2(TransactionV2 transaction,
[FromServices] DaprClient daprClient)
{
logger.LogInformation("Enter withdraw.v2");
if (transaction.Channel == "mobile" && transaction.Amount > 10000)
{
return this.Unauthorized("mobile transactions for large amounts are not permitted.");
}
var state = await daprClient.GetStateEntryAsync<Account>(StoreName, transaction.Id);
if (state.Value == null)
{
return this.NotFound();
}
state.Value.Balance -= transaction.Amount;
await state.SaveAsync();
return state.Value;
}
/// <summary>
/// Method for depositing to account as specified in transaction via a raw message.
/// </summary>
/// <param name="transaction">Transaction info.</param>
/// <param name="daprClient">State client to interact with Dapr runtime.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
/// "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<ActionResult<Account>> 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))}");
var transactionJson = JsonSerializer.Deserialize<JsonDocument>(Convert.FromBase64String(transactionString));
var transaction =
JsonSerializer.Deserialize<Transaction>(transactionJson.RootElement.GetProperty("data").GetRawText());
var state = await daprClient.GetStateEntryAsync<Account>(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);
if (transaction.Amount < 0m)
{
return BadRequest(new { statusCode = 400, message = "bad request" });
}
state.Value.Balance += transaction.Amount;
logger.LogInformation("Balance is {0}", state.Value.Balance);
await state.SaveAsync();
return state.Value;
}
/// <summary>
/// Method for returning a BadRequest result which will cause Dapr sidecar to throw an RpcException
/// </summary>
[HttpPost("throwException")]
public async Task<ActionResult<Account>> ThrowException(Transaction transaction,
[FromServices] DaprClient daprClient)
{
logger.LogInformation("Enter ThrowException");
var task = Task.Delay(10);
await task;
return BadRequest(new { statusCode = 400, message = "bad request" });
}
/// <summary>
/// <para>
/// Method which uses <see cref="CustomTopicAttribute" /> for binding this endpoint to a subscription.
/// </para>
/// <para>
/// This endpoint will be bound to a subscription where the topic name is the value of the environment variable 'CUSTOM_TOPIC'
/// and the pubsub name is the value of the environment variable 'CUSTOM_PUBSUB'.
/// </para>
/// </summary>
[CustomTopic("%CUSTOM_PUBSUB%", "%CUSTOM_TOPIC%")]
[HttpPost("exampleCustomTopic")]
public ActionResult<Account> ExampleCustomTopic(Transaction transaction)
state.Value.Balance += transaction.Amount;
logger.LogInformation("Balance for Id {0} is {1}", state.Value.Id, state.Value.Balance);
await state.SaveAsync();
return state.Value;
}
/// <summary>
/// Method for depositing multiple times to the account as specified in transaction.
/// </summary>
/// <param name="bulkMessage">List of entries of type BulkMessageModel received from dapr.</param>
/// <param name="daprClient">State client to interact with Dapr runtime.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
/// "pubsub", the first parameter into the Topic attribute, is name of the default pub/sub configured by the Dapr CLI.
[Topic("pubsub", "multideposit", "amountDeadLetterTopic", false)]
[BulkSubscribe("multideposit", 500, 2000)]
[HttpPost("multideposit")]
public async Task<ActionResult<BulkSubscribeAppResponse>> MultiDeposit([FromBody]
BulkSubscribeMessage<BulkMessageModel<Transaction>>
bulkMessage, [FromServices] DaprClient daprClient)
{
logger.LogInformation("Enter bulk deposit");
List<BulkSubscribeAppResponseEntry> entries = new List<BulkSubscribeAppResponseEntry>();
foreach (var entry in bulkMessage.Entries)
{
return Ok();
try
{
var transaction = entry.Event.Data;
var state = await daprClient.GetStateEntryAsync<Account>(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);
if (transaction.Amount < 0m)
{
return BadRequest(new { statusCode = 400, message = "bad request" });
}
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)
{
logger.LogError(e.Message);
entries.Add(new BulkSubscribeAppResponseEntry(entry.EntryId, BulkSubscribeAppResponseStatus.RETRY));
}
}
/// <summary>
/// Method which uses <see cref="TopicMetadataAttribute" /> for binding this endpoint to a subscription and adds routingkey metadata.
/// </summary>
/// <param name="transaction"></param>
/// <returns></returns>
[Topic("pubsub", "topicmetadata")]
[TopicMetadata("routingKey", "keyA")]
[HttpPost("examplecustomtopicmetadata")]
public ActionResult<Account> ExampleCustomTopicMetadata(Transaction transaction)
{
return Ok();
}
return new BulkSubscribeAppResponse(entries);
}
}
/// <summary>
/// Method for viewing the error message when the deposit/withdrawal amounts
/// are negative.
/// </summary>
/// <param name="transaction">Transaction info.</param>
[Topic("pubsub", "amountDeadLetterTopic")]
[HttpPost("deadLetterTopicRoute")]
public ActionResult<Account> ViewErrorMessage(Transaction transaction)
{
logger.LogInformation("The amount cannot be negative: {0}", transaction.Amount);
return Ok();
}
/// <summary>
/// Method for withdrawing from account as specified in transaction.
/// </summary>
/// <param name="transaction">Transaction info.</param>
/// <param name="daprClient">State client to interact with Dapr runtime.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
/// "pubsub", the first parameter into the Topic attribute, is name of the default pub/sub configured by the Dapr CLI.
[Topic("pubsub", "withdraw", "amountDeadLetterTopic", false)]
[HttpPost("withdraw")]
public async Task<ActionResult<Account>> Withdraw(Transaction transaction, [FromServices] DaprClient daprClient)
{
logger.LogInformation("Enter withdraw method...");
var state = await daprClient.GetStateEntryAsync<Account>(StoreName, transaction.Id);
logger.LogInformation("Id is {0}, the amount to be withdrawn is {1}", transaction.Id, transaction.Amount);
if (state.Value == null)
{
return this.NotFound();
}
if (transaction.Amount < 0m)
{
return BadRequest(new { statusCode = 400, message = "bad request" });
}
state.Value.Balance -= transaction.Amount;
logger.LogInformation("Balance is {0}", state.Value.Balance);
await state.SaveAsync();
return state.Value;
}
/// <summary>
/// Method for withdrawing from account as specified in transaction.
/// </summary>
/// <param name="transaction">Transaction info.</param>
/// <param name="daprClient">State client to interact with Dapr runtime.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
/// "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<ActionResult<Account>> WithdrawV2(TransactionV2 transaction,
[FromServices] DaprClient daprClient)
{
logger.LogInformation("Enter withdraw.v2");
if (transaction.Channel == "mobile" && transaction.Amount > 10000)
{
return this.Unauthorized("mobile transactions for large amounts are not permitted.");
}
var state = await daprClient.GetStateEntryAsync<Account>(StoreName, transaction.Id);
if (state.Value == null)
{
return this.NotFound();
}
state.Value.Balance -= transaction.Amount;
await state.SaveAsync();
return state.Value;
}
/// <summary>
/// Method for depositing to account as specified in transaction via a raw message.
/// </summary>
/// <param name="transaction">Transaction info.</param>
/// <param name="daprClient">State client to interact with Dapr runtime.</param>
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
/// "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<ActionResult<Account>> 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))}");
var transactionJson = JsonSerializer.Deserialize<JsonDocument>(Convert.FromBase64String(transactionString));
var transaction =
JsonSerializer.Deserialize<Transaction>(transactionJson.RootElement.GetProperty("data").GetRawText());
var state = await daprClient.GetStateEntryAsync<Account>(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);
if (transaction.Amount < 0m)
{
return BadRequest(new { statusCode = 400, message = "bad request" });
}
state.Value.Balance += transaction.Amount;
logger.LogInformation("Balance is {0}", state.Value.Balance);
await state.SaveAsync();
return state.Value;
}
/// <summary>
/// Method for returning a BadRequest result which will cause Dapr sidecar to throw an RpcException
/// </summary>
[HttpPost("throwException")]
public async Task<ActionResult<Account>> ThrowException(Transaction transaction,
[FromServices] DaprClient daprClient)
{
logger.LogInformation("Enter ThrowException");
var task = Task.Delay(10);
await task;
return BadRequest(new { statusCode = 400, message = "bad request" });
}
/// <summary>
/// <para>
/// Method which uses <see cref="CustomTopicAttribute" /> for binding this endpoint to a subscription.
/// </para>
/// <para>
/// This endpoint will be bound to a subscription where the topic name is the value of the environment variable 'CUSTOM_TOPIC'
/// and the pubsub name is the value of the environment variable 'CUSTOM_PUBSUB'.
/// </para>
/// </summary>
[CustomTopic("%CUSTOM_PUBSUB%", "%CUSTOM_TOPIC%")]
[HttpPost("exampleCustomTopic")]
public ActionResult<Account> ExampleCustomTopic(Transaction transaction)
{
return Ok();
}
/// <summary>
/// Method which uses <see cref="TopicMetadataAttribute" /> for binding this endpoint to a subscription and adds routingkey metadata.
/// </summary>
/// <param name="transaction"></param>
/// <returns></returns>
[Topic("pubsub", "topicmetadata")]
[TopicMetadata("routingKey", "keyA")]
[HttpPost("examplecustomtopicmetadata")]
public ActionResult<Account> ExampleCustomTopicMetadata(Transaction transaction)
{
return Ok();
}
}

View File

@ -13,33 +13,32 @@
using Dapr.AspNetCore;
namespace ControllerSample
namespace ControllerSample;
using System;
using Dapr;
/// <summary>
/// Sample custom <see cref="ITopicMetadata" /> implementation that returns topic metadata from environment variables.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CustomTopicAttribute : Attribute, ITopicMetadata
{
using System;
using Dapr;
/// <summary>
/// Sample custom <see cref="ITopicMetadata" /> implementation that returns topic metadata from environment variables.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CustomTopicAttribute : Attribute, ITopicMetadata
public CustomTopicAttribute(string pubsubName, string name)
{
public CustomTopicAttribute(string pubsubName, string name)
{
this.PubsubName = Environment.ExpandEnvironmentVariables(pubsubName);
this.Name = Environment.ExpandEnvironmentVariables(name);
}
/// <inheritdoc/>
public string PubsubName { get; }
/// <inheritdoc/>
public string Name { get; }
/// <inheritdoc/>
public new string Match { get; }
/// <inheritdoc/>
public int Priority { get; }
this.PubsubName = Environment.ExpandEnvironmentVariables(pubsubName);
this.Name = Environment.ExpandEnvironmentVariables(name);
}
}
/// <inheritdoc/>
public string PubsubName { get; }
/// <inheritdoc/>
public string Name { get; }
/// <inheritdoc/>
public new string Match { get; }
/// <inheritdoc/>
public int Priority { get; }
}

View File

@ -11,35 +11,34 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace ControllerSample
namespace ControllerSample;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
/// <summary>
/// Controller Sample.
/// </summary>
public class Program
{
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
/// <summary>
/// Main for Controller Sample.
/// </summary>
/// <param name="args">Arguments.</param>
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
/// <summary>
/// Controller Sample.
/// Creates WebHost Builder.
/// </summary>
public class Program
{
/// <summary>
/// Main for Controller Sample.
/// </summary>
/// <param name="args">Arguments.</param>
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
/// <summary>
/// Creates WebHost Builder.
/// </summary>
/// <param name="args">Arguments.</param>
/// <returns>Returns IHostbuilder.</returns>
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
/// <param name="args">Arguments.</param>
/// <returns>Returns IHostbuilder.</returns>
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}

View File

@ -12,7 +12,7 @@ The application also registers for pub/sub with the `deposit`, `multideposit` an
## Prerequisitess
- [.NET 6+](https://dotnet.microsoft.com/download) installed
- [.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/install-dapr-selfhost/)
- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/)

View File

@ -16,68 +16,67 @@ using Dapr;
using Dapr.AspNetCore;
namespace ControllerSample
namespace ControllerSample;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
/// <summary>
/// Startup class.
/// </summary>
public class Startup
{
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
/// <summary>
/// Initializes a new instance of the <see cref="Startup"/> class.
/// </summary>
/// <param name="configuration">Configuration.</param>
public Startup(IConfiguration configuration)
{
this.Configuration = configuration;
}
/// <summary>
/// Startup class.
/// Gets the configuration.
/// </summary>
public class Startup
public IConfiguration Configuration { get; }
/// <summary>
/// Configures Services.
/// </summary>
/// <param name="services">Service Collection.</param>
public void ConfigureServices(IServiceCollection services)
{
/// <summary>
/// Initializes a new instance of the <see cref="Startup"/> class.
/// </summary>
/// <param name="configuration">Configuration.</param>
public Startup(IConfiguration configuration)
{
this.Configuration = configuration;
}
/// <summary>
/// Gets the configuration.
/// </summary>
public IConfiguration Configuration { get; }
/// <summary>
/// Configures Services.
/// </summary>
/// <param name="services">Service Collection.</param>
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddDapr();
}
/// <summary>
/// Configures Application Builder and WebHost environment.
/// </summary>
/// <param name="app">Application builder.</param>
/// <param name="env">Webhost environment.</param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseCloudEvents(new CloudEventsMiddlewareOptions
{
ForwardCloudEventPropertiesAsHeaders = true
});
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapSubscribeHandler();
endpoints.MapControllers();
});
}
services.AddControllers().AddDapr();
}
}
/// <summary>
/// Configures Application Builder and WebHost environment.
/// </summary>
/// <param name="app">Application builder.</param>
/// <param name="env">Webhost environment.</param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseCloudEvents(new CloudEventsMiddlewareOptions
{
ForwardCloudEventPropertiesAsHeaders = true
});
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapSubscribeHandler();
endpoints.MapControllers();
});
}
}

View File

@ -11,24 +11,23 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace ControllerSample
namespace ControllerSample;
using System.ComponentModel.DataAnnotations;
/// <summary>
/// Represents a transaction used by sample code.
/// </summary>
public class Transaction
{
using System.ComponentModel.DataAnnotations;
/// <summary>
/// Gets or sets account id for the transaction.
/// </summary>
[Required]
public string Id { get; set; }
/// <summary>
/// Represents a transaction used by sample code.
/// </summary>
public class Transaction
{
/// <summary>
/// Gets or sets account id for the transaction.
/// </summary>
[Required]
public string Id { get; set; }
/// <summary>
/// Gets or sets amount for the transaction.
/// </summary
public decimal Amount { get; set; }
}
/// Gets or sets amount for the transaction.
/// </summary
public decimal Amount { get; set; }
}

View File

@ -11,31 +11,30 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace ControllerSample
namespace ControllerSample;
using System.ComponentModel.DataAnnotations;
/// <summary>
/// Represents a transaction used by sample code.
/// </summary>
public class TransactionV2
{
using System.ComponentModel.DataAnnotations;
/// <summary>
/// Gets or sets account id for the transaction.
/// </summary>
[Required]
public string Id { get; set; }
/// <summary>
/// Represents a transaction used by sample code.
/// Gets or sets amount for the transaction.
/// </summary>
public class TransactionV2
{
/// <summary>
/// Gets or sets account id for the transaction.
/// </summary>
[Required]
public string Id { get; set; }
[Range(0, double.MaxValue)]
public decimal Amount { get; set; }
/// <summary>
/// Gets or sets amount for the transaction.
/// </summary>
[Range(0, double.MaxValue)]
public decimal Amount { get; set; }
/// <summary>
/// Gets or sets channel from which this transaction was received.
/// </summary>
[Required]
public string Channel { get; set; }
}
/// <summary>
/// Gets or sets channel from which this transaction was received.
/// </summary>
[Required]
public string Channel { get; set; }
}

View File

@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
</PropertyGroup>

View File

@ -11,21 +11,20 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace GrpcServiceSample.Models
namespace GrpcServiceSample.Models;
/// <summary>
/// Class representing an Account for samples.
/// </summary>
public class Account
{
/// <summary>
/// Class representing an Account for samples.
/// Gets or sets account id.
/// </summary>
public class Account
{
/// <summary>
/// Gets or sets account id.
/// </summary>
public string Id { get; set; }
public string Id { get; set; }
/// <summary>
/// Gets or sets account balance.
/// </summary>
public decimal Balance { get; set; }
}
}
/// <summary>
/// Gets or sets account balance.
/// </summary>
public decimal Balance { get; set; }
}

View File

@ -11,16 +11,15 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace GrpcServiceSample.Models
namespace GrpcServiceSample.Models;
/// <summary>
/// BankService GetAccount input model
/// </summary>
public class GetAccountInput
{
/// <summary>
/// BankService GetAccount input model
/// Id of account
/// </summary>
public class GetAccountInput
{
/// <summary>
/// Id of account
/// </summary>
public string Id { get; set; }
}
}
public string Id { get; set; }
}

View File

@ -13,23 +13,22 @@
using System.ComponentModel.DataAnnotations;
namespace GrpcServiceSample.Models
namespace GrpcServiceSample.Models;
/// <summary>
/// Represents a transaction used by sample code.
/// </summary>
public class Transaction
{
/// <summary>
/// Represents a transaction used by sample code.
/// Gets or sets account id for the transaction.
/// </summary>
public class Transaction
{
/// <summary>
/// Gets or sets account id for the transaction.
/// </summary>
[Required]
public string Id { get; set; }
[Required]
public string Id { get; set; }
/// <summary>
/// Gets or sets amount for the transaction.
/// </summary>
[Range(0, double.MaxValue)]
public decimal Amount { get; set; }
}
}
/// <summary>
/// Gets or sets amount for the transaction.
/// </summary>
[Range(0, double.MaxValue)]
public decimal Amount { get; set; }
}

View File

@ -15,39 +15,38 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Hosting;
namespace GrpcServiceSample
namespace GrpcServiceSample;
/// <summary>
/// GrpcService Sample
/// </summary>
public class Program
{
/// <summary>
/// GrpcService Sample
/// Entry point
/// </summary>
public class Program
/// <param name="args"></param>
public static void Main(string[] args)
{
/// <summary>
/// Entry point
/// </summary>
/// <param name="args"></param>
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
/// <summary>
/// Creates WebHost Builder.
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
// Setup a HTTP/2 endpoint without TLS.
options.ListenLocalhost(5050, o => o.Protocols =
HttpProtocols.Http2);
});
webBuilder.UseStartup<Startup>();
});
CreateHostBuilder(args).Build().Run();
}
}
/// <summary>
/// Creates WebHost Builder.
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
// Setup a HTTP/2 endpoint without TLS.
options.ListenLocalhost(5050, o => o.Protocols =
HttpProtocols.Http2);
});
webBuilder.UseStartup<Startup>();
});
}

View File

@ -11,7 +11,7 @@ The application also registers for pub/sub with the `deposit` and `withdraw` top
## Prerequisitess
- [.NET 6+](https://dotnet.microsoft.com/download) installed
- [.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/install-dapr-selfhost/)
- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/)

View File

@ -22,158 +22,157 @@ using Grpc.Core;
using GrpcServiceSample.Generated;
using Microsoft.Extensions.Logging;
namespace GrpcServiceSample.Services
namespace GrpcServiceSample.Services;
/// <summary>
/// BankAccount gRPC service
/// </summary>
public class BankingService : AppCallback.AppCallbackBase
{
/// <summary>
/// BankAccount gRPC service
/// State store name.
/// </summary>
public class BankingService : AppCallback.AppCallbackBase
public const string StoreName = "statestore";
private readonly ILogger<BankingService> _logger;
private readonly DaprClient _daprClient;
/// <summary>
/// Constructor
/// </summary>
/// <param name="daprClient"></param>
/// <param name="logger"></param>
public BankingService(DaprClient daprClient, ILogger<BankingService> logger)
{
/// <summary>
/// State store name.
/// </summary>
public const string StoreName = "statestore";
private readonly ILogger<BankingService> _logger;
private readonly DaprClient _daprClient;
/// <summary>
/// Constructor
/// </summary>
/// <param name="daprClient"></param>
/// <param name="logger"></param>
public BankingService(DaprClient daprClient, ILogger<BankingService> logger)
{
_daprClient = daprClient;
_logger = logger;
}
readonly JsonSerializerOptions jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
/// <summary>
/// implement OnIvoke to support getaccount, deposit and withdraw
/// </summary>
/// <param name="request"></param>
/// <param name="context"></param>
/// <returns></returns>
public override async Task<InvokeResponse> OnInvoke(InvokeRequest request, ServerCallContext context)
{
var response = new InvokeResponse();
switch (request.Method)
{
case "getaccount":
var input = request.Data.Unpack<GrpcServiceSample.Generated.GetAccountRequest>();
var output = await GetAccount(input, context);
response.Data = Any.Pack(output);
break;
case "deposit":
case "withdraw":
var transaction = request.Data.Unpack<GrpcServiceSample.Generated.Transaction>();
var account = request.Method == "deposit" ?
await Deposit(transaction, context) :
await Withdraw(transaction, context);
response.Data = Any.Pack(account);
break;
default:
break;
}
return response;
}
/// <summary>
/// implement ListTopicSubscriptions to register deposit and withdraw subscriber
/// </summary>
/// <param name="request"></param>
/// <param name="context"></param>
/// <returns></returns>
public override Task<ListTopicSubscriptionsResponse> ListTopicSubscriptions(Empty request, ServerCallContext context)
{
var result = new ListTopicSubscriptionsResponse();
result.Subscriptions.Add(new TopicSubscription
{
PubsubName = "pubsub",
Topic = "deposit"
});
result.Subscriptions.Add(new TopicSubscription
{
PubsubName = "pubsub",
Topic = "withdraw"
});
return Task.FromResult(result);
}
/// <summary>
/// implement OnTopicEvent to handle deposit and withdraw event
/// </summary>
/// <param name="request"></param>
/// <param name="context"></param>
/// <returns></returns>
public override async Task<TopicEventResponse> OnTopicEvent(TopicEventRequest request, ServerCallContext context)
{
if (request.PubsubName == "pubsub")
{
var input = JsonSerializer.Deserialize<Models.Transaction>(request.Data.ToStringUtf8(), this.jsonOptions);
var transaction = new GrpcServiceSample.Generated.Transaction() { Id = input.Id, Amount = (int)input.Amount, };
if (request.Topic == "deposit")
{
await Deposit(transaction, context);
}
else
{
await Withdraw(transaction, context);
}
}
return new TopicEventResponse();
}
/// <summary>
/// GetAccount
/// </summary>
/// <param name="input"></param>
/// <param name="context"></param>
/// <returns></returns>
public async Task<GrpcServiceSample.Generated.Account> GetAccount(GetAccountRequest input, ServerCallContext context)
{
var state = await _daprClient.GetStateEntryAsync<Models.Account>(StoreName, input.Id);
return new GrpcServiceSample.Generated.Account() { Id = state.Value.Id, Balance = (int)state.Value.Balance, };
}
/// <summary>
/// Deposit
/// </summary>
/// <param name="transaction"></param>
/// <param name="context"></param>
/// <returns></returns>
public async Task<GrpcServiceSample.Generated.Account> Deposit(GrpcServiceSample.Generated.Transaction transaction, ServerCallContext context)
{
_logger.LogDebug("Enter deposit");
var state = await _daprClient.GetStateEntryAsync<Models.Account>(StoreName, transaction.Id);
state.Value ??= new Models.Account() { Id = transaction.Id, };
state.Value.Balance += transaction.Amount;
await state.SaveAsync();
return new GrpcServiceSample.Generated.Account() { Id = state.Value.Id, Balance = (int)state.Value.Balance, };
}
/// <summary>
/// Withdraw
/// </summary>
/// <param name="transaction"></param>
/// <param name="context"></param>
/// <returns></returns>
public async Task<GrpcServiceSample.Generated.Account> Withdraw(GrpcServiceSample.Generated.Transaction transaction, ServerCallContext context)
{
_logger.LogDebug("Enter withdraw");
var state = await _daprClient.GetStateEntryAsync<Models.Account>(StoreName, transaction.Id);
if (state.Value == null)
{
throw new Exception($"NotFound: {transaction.Id}");
}
state.Value.Balance -= transaction.Amount;
await state.SaveAsync();
return new GrpcServiceSample.Generated.Account() { Id = state.Value.Id, Balance = (int)state.Value.Balance, };
}
_daprClient = daprClient;
_logger = logger;
}
}
readonly JsonSerializerOptions jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
/// <summary>
/// implement OnIvoke to support getaccount, deposit and withdraw
/// </summary>
/// <param name="request"></param>
/// <param name="context"></param>
/// <returns></returns>
public override async Task<InvokeResponse> OnInvoke(InvokeRequest request, ServerCallContext context)
{
var response = new InvokeResponse();
switch (request.Method)
{
case "getaccount":
var input = request.Data.Unpack<GrpcServiceSample.Generated.GetAccountRequest>();
var output = await GetAccount(input, context);
response.Data = Any.Pack(output);
break;
case "deposit":
case "withdraw":
var transaction = request.Data.Unpack<GrpcServiceSample.Generated.Transaction>();
var account = request.Method == "deposit" ?
await Deposit(transaction, context) :
await Withdraw(transaction, context);
response.Data = Any.Pack(account);
break;
default:
break;
}
return response;
}
/// <summary>
/// implement ListTopicSubscriptions to register deposit and withdraw subscriber
/// </summary>
/// <param name="request"></param>
/// <param name="context"></param>
/// <returns></returns>
public override Task<ListTopicSubscriptionsResponse> ListTopicSubscriptions(Empty request, ServerCallContext context)
{
var result = new ListTopicSubscriptionsResponse();
result.Subscriptions.Add(new TopicSubscription
{
PubsubName = "pubsub",
Topic = "deposit"
});
result.Subscriptions.Add(new TopicSubscription
{
PubsubName = "pubsub",
Topic = "withdraw"
});
return Task.FromResult(result);
}
/// <summary>
/// implement OnTopicEvent to handle deposit and withdraw event
/// </summary>
/// <param name="request"></param>
/// <param name="context"></param>
/// <returns></returns>
public override async Task<TopicEventResponse> OnTopicEvent(TopicEventRequest request, ServerCallContext context)
{
if (request.PubsubName == "pubsub")
{
var input = JsonSerializer.Deserialize<Models.Transaction>(request.Data.ToStringUtf8(), this.jsonOptions);
var transaction = new GrpcServiceSample.Generated.Transaction() { Id = input.Id, Amount = (int)input.Amount, };
if (request.Topic == "deposit")
{
await Deposit(transaction, context);
}
else
{
await Withdraw(transaction, context);
}
}
return new TopicEventResponse();
}
/// <summary>
/// GetAccount
/// </summary>
/// <param name="input"></param>
/// <param name="context"></param>
/// <returns></returns>
public async Task<GrpcServiceSample.Generated.Account> GetAccount(GetAccountRequest input, ServerCallContext context)
{
var state = await _daprClient.GetStateEntryAsync<Models.Account>(StoreName, input.Id);
return new GrpcServiceSample.Generated.Account() { Id = state.Value.Id, Balance = (int)state.Value.Balance, };
}
/// <summary>
/// Deposit
/// </summary>
/// <param name="transaction"></param>
/// <param name="context"></param>
/// <returns></returns>
public async Task<GrpcServiceSample.Generated.Account> Deposit(GrpcServiceSample.Generated.Transaction transaction, ServerCallContext context)
{
_logger.LogDebug("Enter deposit");
var state = await _daprClient.GetStateEntryAsync<Models.Account>(StoreName, transaction.Id);
state.Value ??= new Models.Account() { Id = transaction.Id, };
state.Value.Balance += transaction.Amount;
await state.SaveAsync();
return new GrpcServiceSample.Generated.Account() { Id = state.Value.Id, Balance = (int)state.Value.Balance, };
}
/// <summary>
/// Withdraw
/// </summary>
/// <param name="transaction"></param>
/// <param name="context"></param>
/// <returns></returns>
public async Task<GrpcServiceSample.Generated.Account> Withdraw(GrpcServiceSample.Generated.Transaction transaction, ServerCallContext context)
{
_logger.LogDebug("Enter withdraw");
var state = await _daprClient.GetStateEntryAsync<Models.Account>(StoreName, transaction.Id);
if (state.Value == null)
{
throw new Exception($"NotFound: {transaction.Id}");
}
state.Value.Balance -= transaction.Amount;
await state.SaveAsync();
return new GrpcServiceSample.Generated.Account() { Id = state.Value.Id, Balance = (int)state.Value.Balance, };
}
}

View File

@ -19,47 +19,46 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace GrpcServiceSample
namespace GrpcServiceSample;
/// <summary>
/// Startup class.
/// </summary>
public class Startup
{
/// <summary>
/// Startup class.
/// Configure Services
/// </summary>
public class Startup
/// <param name="services"></param>
public void ConfigureServices(IServiceCollection services)
{
/// <summary>
/// Configure Services
/// </summary>
/// <param name="services"></param>
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddGrpc();
services.AddDaprClient();
}
/// <summary>
/// Configure app
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<BankingService>();
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});
}
services.AddDaprClient();
}
}
/// <summary>
/// Configure app
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<BankingService>();
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});
}
}

View File

@ -11,21 +11,20 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace RoutingSample
namespace RoutingSample;
/// <summary>
/// Class representing an Account for samples.
/// </summary>
public class Account
{
/// <summary>
/// Class representing an Account for samples.
/// Gets or sets account id.
/// </summary>
public class Account
{
/// <summary>
/// Gets or sets account id.
/// </summary>
public string Id { get; set; }
public string Id { get; set; }
/// <summary>
/// Gets or sets account balance.
/// </summary>
public decimal Balance { get; set; }
}
/// <summary>
/// Gets or sets account balance.
/// </summary>
public decimal Balance { get; set; }
}

View File

@ -11,35 +11,34 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace RoutingSample
namespace RoutingSample;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
/// <summary>
/// Controller Sample.
/// </summary>
public static class Program
{
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
/// <summary>
/// Main for Controller Sample.
/// </summary>
/// <param name="args">Arguments.</param>
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
/// <summary>
/// Controller Sample.
/// Creates WebHost Builder.
/// </summary>
public static class Program
{
/// <summary>
/// Main for Controller Sample.
/// </summary>
/// <param name="args">Arguments.</param>
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
/// <summary>
/// Creates WebHost Builder.
/// </summary>
/// <param name="args">Arguments.</param>
/// <returns>Returns IHostbuilder.</returns>
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
/// <param name="args">Arguments.</param>
/// <returns>Returns IHostbuilder.</returns>
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}

View File

@ -12,7 +12,7 @@ The application also registers for pub/sub with the `deposit`, `multideposit`, a
## Prerequisites
- [.NET 6+](https://dotnet.microsoft.com/download) installed
- [.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/install-dapr-selfhost/)
- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/)

View File

@ -1,9 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Dapr.AspNetCore\Dapr.AspNetCore.csproj" />
</ItemGroup>

View File

@ -11,254 +11,253 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace RoutingSample
namespace RoutingSample;
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Dapr;
using Dapr.AspNetCore;
using Dapr.Client;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
/// <summary>
/// Startup class.
/// </summary>
public class Startup
{
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using Dapr;
using Dapr.AspNetCore;
using Dapr.Client;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
/// <summary>
/// State store name.
/// </summary>
public const string StoreName = "statestore";
/// <summary>
/// Startup class.
/// Pubsub component name. "pubsub" is name of the default pub/sub configured by the Dapr CLI.
/// </summary>
public class Startup
public const string PubsubName = "pubsub";
/// <summary>
/// Initializes a new instance of the <see cref="Startup"/> class.
/// </summary>
/// <param name="configuration">Configuration.</param>
public Startup(IConfiguration configuration)
{
/// <summary>
/// State store name.
/// </summary>
public const string StoreName = "statestore";
this.Configuration = configuration;
}
/// <summary>
/// Pubsub component name. "pubsub" is name of the default pub/sub configured by the Dapr CLI.
/// </summary>
public const string PubsubName = "pubsub";
/// <summary>
/// Gets the configuration.
/// </summary>
public IConfiguration Configuration { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Startup"/> class.
/// </summary>
/// <param name="configuration">Configuration.</param>
public Startup(IConfiguration configuration)
/// <summary>
/// Configures Services.
/// </summary>
/// <param name="services">Service Collection.</param>
public void ConfigureServices(IServiceCollection services)
{
services.AddDaprClient();
services.AddSingleton(new JsonSerializerOptions()
{
this.Configuration = configuration;
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
});
}
/// <summary>
/// Configures Application Builder and WebHost environment.
/// </summary>
/// <param name="app">Application builder.</param>
/// <param name="env">Webhost environment.</param>
/// <param name="serializerOptions">Options for JSON serialization.</param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, JsonSerializerOptions serializerOptions,
ILogger<Startup> logger)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
/// <summary>
/// Gets the configuration.
/// </summary>
public IConfiguration Configuration { get; }
app.UseRouting();
/// <summary>
/// Configures Services.
/// </summary>
/// <param name="services">Service Collection.</param>
public void ConfigureServices(IServiceCollection services)
app.UseCloudEvents();
app.UseEndpoints(endpoints =>
{
services.AddDaprClient();
endpoints.MapSubscribeHandler();
services.AddSingleton(new JsonSerializerOptions()
var depositTopicOptions = new TopicOptions();
depositTopicOptions.PubsubName = PubsubName;
depositTopicOptions.Name = "deposit";
depositTopicOptions.DeadLetterTopic = "amountDeadLetterTopic";
var withdrawTopicOptions = new TopicOptions();
withdrawTopicOptions.PubsubName = PubsubName;
withdrawTopicOptions.Name = "withdraw";
withdrawTopicOptions.DeadLetterTopic = "amountDeadLetterTopic";
var multiDepositTopicOptions = new TopicOptions { PubsubName = PubsubName, Name = "multideposit" };
var bulkSubscribeTopicOptions = new BulkSubscribeTopicOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
});
}
TopicName = "multideposit", MaxMessagesCount = 250, MaxAwaitDurationMs = 1000
};
/// <summary>
/// Configures Application Builder and WebHost environment.
/// </summary>
/// <param name="app">Application builder.</param>
/// <param name="env">Webhost environment.</param>
/// <param name="serializerOptions">Options for JSON serialization.</param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, JsonSerializerOptions serializerOptions,
ILogger<Startup> logger)
endpoints.MapGet("{id}", Balance);
endpoints.MapPost("deposit", Deposit).WithTopic(depositTopicOptions);
endpoints.MapPost("multideposit", MultiDeposit).WithTopic(multiDepositTopicOptions).WithBulkSubscribe(bulkSubscribeTopicOptions);
endpoints.MapPost("deadLetterTopicRoute", ViewErrorMessage).WithTopic(PubsubName, "amountDeadLetterTopic");
endpoints.MapPost("withdraw", Withdraw).WithTopic(withdrawTopicOptions);
});
async Task Balance(HttpContext context)
{
if (env.IsDevelopment())
logger.LogInformation("Enter Balance");
var client = context.RequestServices.GetRequiredService<DaprClient>();
var id = (string)context.Request.RouteValues["id"];
logger.LogInformation("id is {0}", id);
var account = await client.GetStateAsync<Account>(StoreName, id);
if (account == null)
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseCloudEvents();
app.UseEndpoints(endpoints =>
{
endpoints.MapSubscribeHandler();
var depositTopicOptions = new TopicOptions();
depositTopicOptions.PubsubName = PubsubName;
depositTopicOptions.Name = "deposit";
depositTopicOptions.DeadLetterTopic = "amountDeadLetterTopic";
var withdrawTopicOptions = new TopicOptions();
withdrawTopicOptions.PubsubName = PubsubName;
withdrawTopicOptions.Name = "withdraw";
withdrawTopicOptions.DeadLetterTopic = "amountDeadLetterTopic";
var multiDepositTopicOptions = new TopicOptions { PubsubName = PubsubName, Name = "multideposit" };
var bulkSubscribeTopicOptions = new BulkSubscribeTopicOptions
{
TopicName = "multideposit", MaxMessagesCount = 250, MaxAwaitDurationMs = 1000
};
endpoints.MapGet("{id}", Balance);
endpoints.MapPost("deposit", Deposit).WithTopic(depositTopicOptions);
endpoints.MapPost("multideposit", MultiDeposit).WithTopic(multiDepositTopicOptions).WithBulkSubscribe(bulkSubscribeTopicOptions);
endpoints.MapPost("deadLetterTopicRoute", ViewErrorMessage).WithTopic(PubsubName, "amountDeadLetterTopic");
endpoints.MapPost("withdraw", Withdraw).WithTopic(withdrawTopicOptions);
});
async Task Balance(HttpContext context)
{
logger.LogInformation("Enter Balance");
var client = context.RequestServices.GetRequiredService<DaprClient>();
var id = (string)context.Request.RouteValues["id"];
logger.LogInformation("id is {0}", id);
var account = await client.GetStateAsync<Account>(StoreName, id);
if (account == null)
{
logger.LogInformation("Account not found");
context.Response.StatusCode = 404;
return;
}
logger.LogInformation("Account balance is {0}", account.Balance);
context.Response.ContentType = "application/json";
await JsonSerializer.SerializeAsync(context.Response.Body, account, serializerOptions);
}
async Task Deposit(HttpContext context)
{
logger.LogInformation("Enter Deposit");
var client = context.RequestServices.GetRequiredService<DaprClient>();
var transaction = await JsonSerializer.DeserializeAsync<Transaction>(context.Request.Body, serializerOptions);
logger.LogInformation("Id is {0}, Amount is {1}", transaction.Id, transaction.Amount);
var account = await client.GetStateAsync<Account>(StoreName, transaction.Id);
if (account == null)
{
account = new Account() { Id = transaction.Id, };
}
if (transaction.Amount < 0m)
{
logger.LogInformation("Invalid amount");
context.Response.StatusCode = 400;
return;
}
account.Balance += transaction.Amount;
await client.SaveStateAsync(StoreName, transaction.Id, account);
logger.LogInformation("Balance is {0}", account.Balance);
context.Response.ContentType = "application/json";
await JsonSerializer.SerializeAsync(context.Response.Body, account, serializerOptions);
}
async Task MultiDeposit(HttpContext context)
{
logger.LogInformation("Enter bulk deposit");
var client = context.RequestServices.GetRequiredService<DaprClient>();
var bulkMessage = await JsonSerializer.DeserializeAsync<BulkSubscribeMessage<BulkMessageModel<Transaction>>>(
context.Request.Body, serializerOptions);
List<BulkSubscribeAppResponseEntry> entries = new List<BulkSubscribeAppResponseEntry>();
if (bulkMessage != null)
{
foreach (var entry in bulkMessage.Entries)
{
try
{
var transaction = entry.Event.Data;
var state = await client.GetStateEntryAsync<Account>(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);
if (transaction.Amount < 0m)
{
logger.LogInformation("Invalid amount");
context.Response.StatusCode = 400;
return;
}
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)
{
logger.LogError(e.Message);
entries.Add(new BulkSubscribeAppResponseEntry(entry.EntryId,
BulkSubscribeAppResponseStatus.RETRY));
}
}
}
await JsonSerializer.SerializeAsync(context.Response.Body,
new BulkSubscribeAppResponse(entries), serializerOptions);
}
async Task ViewErrorMessage(HttpContext context)
{
var transaction = await JsonSerializer.DeserializeAsync<Transaction>(context.Request.Body, serializerOptions);
logger.LogInformation("The amount cannot be negative: {0}", transaction.Amount);
logger.LogInformation("Account not found");
context.Response.StatusCode = 404;
return;
}
async Task Withdraw(HttpContext context)
logger.LogInformation("Account balance is {0}", account.Balance);
context.Response.ContentType = "application/json";
await JsonSerializer.SerializeAsync(context.Response.Body, account, serializerOptions);
}
async Task Deposit(HttpContext context)
{
logger.LogInformation("Enter Deposit");
var client = context.RequestServices.GetRequiredService<DaprClient>();
var transaction = await JsonSerializer.DeserializeAsync<Transaction>(context.Request.Body, serializerOptions);
logger.LogInformation("Id is {0}, Amount is {1}", transaction.Id, transaction.Amount);
var account = await client.GetStateAsync<Account>(StoreName, transaction.Id);
if (account == null)
{
logger.LogInformation("Enter Withdraw");
var client = context.RequestServices.GetRequiredService<DaprClient>();
var transaction = await JsonSerializer.DeserializeAsync<Transaction>(context.Request.Body, serializerOptions);
logger.LogInformation("Id is {0}, Amount is {1}", transaction.Id, transaction.Amount);
var account = await client.GetStateAsync<Account>(StoreName, transaction.Id);
if (account == null)
{
logger.LogInformation("Account not found");
context.Response.StatusCode = 404;
return;
}
if (transaction.Amount < 0m)
{
logger.LogInformation("Invalid amount");
context.Response.StatusCode = 400;
return;
}
account.Balance -= transaction.Amount;
await client.SaveStateAsync(StoreName, transaction.Id, account);
logger.LogInformation("Balance is {0}", account.Balance);
context.Response.ContentType = "application/json";
await JsonSerializer.SerializeAsync(context.Response.Body, account, serializerOptions);
account = new Account() { Id = transaction.Id, };
}
if (transaction.Amount < 0m)
{
logger.LogInformation("Invalid amount");
context.Response.StatusCode = 400;
return;
}
account.Balance += transaction.Amount;
await client.SaveStateAsync(StoreName, transaction.Id, account);
logger.LogInformation("Balance is {0}", account.Balance);
context.Response.ContentType = "application/json";
await JsonSerializer.SerializeAsync(context.Response.Body, account, serializerOptions);
}
async Task MultiDeposit(HttpContext context)
{
logger.LogInformation("Enter bulk deposit");
var client = context.RequestServices.GetRequiredService<DaprClient>();
var bulkMessage = await JsonSerializer.DeserializeAsync<BulkSubscribeMessage<BulkMessageModel<Transaction>>>(
context.Request.Body, serializerOptions);
List<BulkSubscribeAppResponseEntry> entries = new List<BulkSubscribeAppResponseEntry>();
if (bulkMessage != null)
{
foreach (var entry in bulkMessage.Entries)
{
try
{
var transaction = entry.Event.Data;
var state = await client.GetStateEntryAsync<Account>(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);
if (transaction.Amount < 0m)
{
logger.LogInformation("Invalid amount");
context.Response.StatusCode = 400;
return;
}
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)
{
logger.LogError(e.Message);
entries.Add(new BulkSubscribeAppResponseEntry(entry.EntryId,
BulkSubscribeAppResponseStatus.RETRY));
}
}
}
await JsonSerializer.SerializeAsync(context.Response.Body,
new BulkSubscribeAppResponse(entries), serializerOptions);
}
async Task ViewErrorMessage(HttpContext context)
{
var transaction = await JsonSerializer.DeserializeAsync<Transaction>(context.Request.Body, serializerOptions);
logger.LogInformation("The amount cannot be negative: {0}", transaction.Amount);
return;
}
async Task Withdraw(HttpContext context)
{
logger.LogInformation("Enter Withdraw");
var client = context.RequestServices.GetRequiredService<DaprClient>();
var transaction = await JsonSerializer.DeserializeAsync<Transaction>(context.Request.Body, serializerOptions);
logger.LogInformation("Id is {0}, Amount is {1}", transaction.Id, transaction.Amount);
var account = await client.GetStateAsync<Account>(StoreName, transaction.Id);
if (account == null)
{
logger.LogInformation("Account not found");
context.Response.StatusCode = 404;
return;
}
if (transaction.Amount < 0m)
{
logger.LogInformation("Invalid amount");
context.Response.StatusCode = 400;
return;
}
account.Balance -= transaction.Amount;
await client.SaveStateAsync(StoreName, transaction.Id, account);
logger.LogInformation("Balance is {0}", account.Balance);
context.Response.ContentType = "application/json";
await JsonSerializer.SerializeAsync(context.Response.Body, account, serializerOptions);
}
}
}
}

View File

@ -11,21 +11,20 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace RoutingSample
namespace RoutingSample;
/// <summary>
/// Represents a transaction used by sample code.
/// </summary>
public class Transaction
{
/// <summary>
/// Represents a transaction used by sample code.
/// Gets or sets account id for the transaction.
/// </summary>
public class Transaction
{
/// <summary>
/// Gets or sets account id for the transaction.
/// </summary>
public string Id { get; set; }
public string Id { get; set; }
/// <summary>
/// Gets or sets amount for the transaction.
/// </summary>
public decimal Amount { get; set; }
}
/// <summary>
/// Gets or sets amount for the transaction.
/// </summary>
public decimal Amount { get; set; }
}

View File

@ -11,64 +11,63 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace SecretStoreConfigurationProviderSample
namespace SecretStoreConfigurationProviderSample;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Dapr.Client;
using Dapr.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
/// <summary>
/// Secret Store Configuration Provider Sample.
/// </summary>
public class Program
{
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Dapr.Client;
using Dapr.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
/// <summary>
/// Main for Secret Store Configuration Provider Sample.
/// </summary>
/// <param name="args">Arguments.</param>
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
/// <summary>
/// Secret Store Configuration Provider Sample.
/// Creates WebHost Builder.
/// </summary>
public class Program
/// <param name="args">Arguments.</param>
/// <returns>Returns IHostbuilder.</returns>
public static IHostBuilder CreateHostBuilder(string[] args)
{
/// <summary>
/// Main for Secret Store Configuration Provider Sample.
/// </summary>
/// <param name="args">Arguments.</param>
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
// Create Dapr Client
var client = new DaprClientBuilder()
.Build();
/// <summary>
/// Creates WebHost Builder.
/// </summary>
/// <param name="args">Arguments.</param>
/// <returns>Returns IHostbuilder.</returns>
public static IHostBuilder CreateHostBuilder(string[] args)
{
// Create Dapr Client
var client = new DaprClientBuilder()
.Build();
return Host.CreateDefaultBuilder(args)
.ConfigureServices((services) =>
{
// Add the DaprClient to DI.
services.AddSingleton<DaprClient>(client);
})
.ConfigureAppConfiguration((configBuilder) =>
{
// To retrive specific secrets use secretDescriptors
// Create descriptors for the secrets you want to rerieve from the Dapr Secret Store.
// var secretDescriptors = new DaprSecretDescriptor[]
// {
// new DaprSecretDescriptor("super-secret")
// };
// configBuilder.AddDaprSecretStore("demosecrets", secretDescriptors, client);
return Host.CreateDefaultBuilder(args)
.ConfigureServices((services) =>
{
// Add the DaprClient to DI.
services.AddSingleton<DaprClient>(client);
})
.ConfigureAppConfiguration((configBuilder) =>
{
// To retrive specific secrets use secretDescriptors
// Create descriptors for the secrets you want to rerieve from the Dapr Secret Store.
// var secretDescriptors = new DaprSecretDescriptor[]
// {
// new DaprSecretDescriptor("super-secret")
// };
// configBuilder.AddDaprSecretStore("demosecrets", secretDescriptors, client);
// Add the secret store Configuration Provider to the configuration builder.
// Including a TimeSpan allows us to dictate how long we should wait for the Sidecar to start.
configBuilder.AddDaprSecretStore("demosecrets", client, TimeSpan.FromSeconds(10));
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
// Add the secret store Configuration Provider to the configuration builder.
// Including a TimeSpan allows us to dictate how long we should wait for the Sidecar to start.
configBuilder.AddDaprSecretStore("demosecrets", client, TimeSpan.FromSeconds(10));
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@ -2,7 +2,7 @@
## Prerequisites
- [.NET 6+](https://dotnet.microsoft.com/download) installed
- [.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/install-dapr-selfhost/)
- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/)

View File

@ -2,7 +2,6 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6</TargetFramework>
</PropertyGroup>
<ItemGroup>

View File

@ -11,72 +11,71 @@
// limitations under the License.
// ------------------------------------------------------------------------
namespace SecretStoreConfigurationProviderSample
namespace SecretStoreConfigurationProviderSample;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Startup class.
/// </summary>
public class Startup
{
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Initializes a new instance of the <see cref="Startup"/> class.
/// </summary>
/// <param name="configuration">Configuration.</param>
public Startup(IConfiguration configuration)
{
this.Configuration = configuration;
}
/// <summary>
/// Startup class.
/// Gets the configuration.
/// </summary>
public class Startup
public IConfiguration Configuration { get; }
/// <summary>
/// Configures Services.
/// </summary>
/// <param name="services">Service Collection.</param>
public void ConfigureServices(IServiceCollection services)
{
/// <summary>
/// Initializes a new instance of the <see cref="Startup"/> class.
/// </summary>
/// <param name="configuration">Configuration.</param>
public Startup(IConfiguration configuration)
}
/// <summary>
/// Configures Application Builder and WebHost environment.
/// </summary>
/// <param name="app">Application builder.</param>
/// <param name="env">Webhost environment.</param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
this.Configuration = configuration;
app.UseDeveloperExceptionPage();
}
/// <summary>
/// Gets the configuration.
/// </summary>
public IConfiguration Configuration { get; }
app.UseRouting();
/// <summary>
/// Configures Services.
/// </summary>
/// <param name="services">Service Collection.</param>
public void ConfigureServices(IServiceCollection services)
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("secret", Secret);
});
}
/// <summary>
/// Configures Application Builder and WebHost environment.
/// </summary>
/// <param name="app">Application builder.</param>
/// <param name="env">Webhost environment.</param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
async Task Secret(HttpContext context)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Get secret from configuration!!!
var secretValue = Configuration["super-secret"];
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("secret", Secret);
});
async Task Secret(HttpContext context)
{
// Get secret from configuration!!!
var secretValue = Configuration["super-secret"];
context.Response.ContentType = "application/json";
await JsonSerializer.SerializeAsync(context.Response.Body, secretValue);
}
context.Response.ContentType = "application/json";
await JsonSerializer.SerializeAsync(context.Response.Body, secretValue);
}
}
}

View File

@ -1,10 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Dapr.Client\Dapr.Client.csproj" />
<ProjectReference Include="..\..\..\src\Dapr.AspNetCore\Dapr.AspNetCore.csproj" />

View File

@ -7,81 +7,80 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace ConfigurationApi.Controllers
namespace ConfigurationApi.Controllers;
[ApiController]
[Route("configuration")]
public class ConfigurationController : ControllerBase
{
[ApiController]
[Route("configuration")]
public class ConfigurationController : ControllerBase
private ILogger<ConfigurationController> logger;
private IConfiguration configuration;
private DaprClient client;
public ConfigurationController(ILogger<ConfigurationController> logger, IConfiguration configuration, [FromServices] DaprClient client)
{
private ILogger<ConfigurationController> logger;
private IConfiguration configuration;
private DaprClient client;
this.logger = logger;
this.configuration = configuration;
this.client = client;
}
public ConfigurationController(ILogger<ConfigurationController> logger, IConfiguration configuration, [FromServices] DaprClient client)
[HttpGet("get/{configStore}/{queryKey}")]
public async Task GetConfiguration([FromRoute] string configStore, [FromRoute] string queryKey)
{
logger.LogInformation($"Querying Configuration with key: {queryKey}");
var configItems = await client.GetConfiguration(configStore, new List<string>() { queryKey });
if (configItems.Items.Count == 0)
{
this.logger = logger;
this.configuration = configuration;
this.client = client;
logger.LogInformation($"No configuration item found for key: {queryKey}");
}
[HttpGet("get/{configStore}/{queryKey}")]
public async Task GetConfiguration([FromRoute] string configStore, [FromRoute] string queryKey)
foreach (var item in configItems.Items)
{
logger.LogInformation($"Querying Configuration with key: {queryKey}");
var configItems = await client.GetConfiguration(configStore, new List<string>() { queryKey });
if (configItems.Items.Count == 0)
{
logger.LogInformation($"No configuration item found for key: {queryKey}");
}
foreach (var item in configItems.Items)
{
logger.LogInformation($"Got configuration item:\nKey: {item.Key}\nValue: {item.Value.Value}\nVersion: {item.Value.Version}");
}
logger.LogInformation($"Got configuration item:\nKey: {item.Key}\nValue: {item.Value.Value}\nVersion: {item.Value.Version}");
}
}
[HttpGet("extension")]
public Task SubscribeAndWatchConfiguration()
{
logger.LogInformation($"Getting values from Configuration Extension, watched values ['withdrawVersion', 'source'].");
[HttpGet("extension")]
public Task SubscribeAndWatchConfiguration()
{
logger.LogInformation($"Getting values from Configuration Extension, watched values ['withdrawVersion', 'source'].");
logger.LogInformation($"'withdrawVersion' from extension: {configuration["withdrawVersion"]}");
logger.LogInformation($"'source' from extension: {configuration["source"]}");
logger.LogInformation($"'withdrawVersion' from extension: {configuration["withdrawVersion"]}");
logger.LogInformation($"'source' from extension: {configuration["source"]}");
return Task.CompletedTask;
}
return Task.CompletedTask;
}
#nullable enable
[HttpPost("withdraw")]
public async Task<ActionResult<Account>> CreateAccountHandler(Transaction transaction)
[HttpPost("withdraw")]
public async Task<ActionResult<Account>> CreateAccountHandler(Transaction transaction)
{
// Check if the V2 method is enabled.
if (configuration["withdrawVersion"] == "v2")
{
// Check if the V2 method is enabled.
if (configuration["withdrawVersion"] == "v2")
var source = !string.IsNullOrEmpty(configuration["source"]) ? configuration["source"] : "local";
var transactionV2 = new TransactionV2
{
var source = !string.IsNullOrEmpty(configuration["source"]) ? configuration["source"] : "local";
var transactionV2 = new TransactionV2
{
Id = transaction.Id,
Amount = transaction.Amount,
Channel = source
};
logger.LogInformation($"Calling V2 Withdraw API - Id: {transactionV2.Id} Amount: {transactionV2.Amount} Channel: {transactionV2.Channel}");
try
{
return await this.client.InvokeMethodAsync<TransactionV2, Account>("controller", "withdraw.v2", transactionV2);
}
catch (DaprException ex)
{
logger.LogError($"Error executing withdrawal: {ex.Message}");
return BadRequest();
}
Id = transaction.Id,
Amount = transaction.Amount,
Channel = source
};
logger.LogInformation($"Calling V2 Withdraw API - Id: {transactionV2.Id} Amount: {transactionV2.Amount} Channel: {transactionV2.Channel}");
try
{
return await this.client.InvokeMethodAsync<TransactionV2, Account>("controller", "withdraw.v2", transactionV2);
}
catch (DaprException ex)
{
logger.LogError($"Error executing withdrawal: {ex.Message}");
return BadRequest();
}
// Default to the original method.
logger.LogInformation($"Calling V1 Withdraw API: {transaction}");
return await this.client.InvokeMethodAsync<Transaction, Account>("controller", "withdraw", transaction);
}
#nullable disable
// Default to the original method.
logger.LogInformation($"Calling V1 Withdraw API: {transaction}");
return await this.client.InvokeMethodAsync<Transaction, Account>("controller", "withdraw", transaction);
}
}
#nullable disable
}

View File

@ -5,37 +5,36 @@ using Dapr.Client;
using Dapr.Extensions.Configuration;
using System.Collections.Generic;
namespace ConfigurationApi
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Starting application.");
CreateHostBuilder(args).Build().Run();
Console.WriteLine("Closing application.");
}
namespace ConfigurationApi;
/// <summary>
/// Creates WebHost Builder.
/// </summary>
/// <param name="args">Arguments.</param>
/// <returns>Returns IHostbuilder.</returns>
public static IHostBuilder CreateHostBuilder(string[] args)
{
var client = new DaprClientBuilder().Build();
return Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(config =>
{
// Get the initial value and continue to watch it for changes.
config.AddDaprConfigurationStore("redisconfig", new List<string>() { "withdrawVersion" }, client, TimeSpan.FromSeconds(20));
config.AddStreamingDaprConfigurationStore("redisconfig", new List<string>() { "withdrawVersion", "source" }, client, TimeSpan.FromSeconds(20));
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Starting application.");
CreateHostBuilder(args).Build().Run();
Console.WriteLine("Closing application.");
}
}
/// <summary>
/// Creates WebHost Builder.
/// </summary>
/// <param name="args">Arguments.</param>
/// <returns>Returns IHostbuilder.</returns>
public static IHostBuilder CreateHostBuilder(string[] args)
{
var client = new DaprClientBuilder().Build();
return Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(config =>
{
// Get the initial value and continue to watch it for changes.
config.AddDaprConfigurationStore("redisconfig", new List<string>() { "withdrawVersion" }, client, TimeSpan.FromSeconds(20));
config.AddStreamingDaprConfigurationStore("redisconfig", new List<string>() { "withdrawVersion", "source" }, client, TimeSpan.FromSeconds(20));
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@ -9,7 +9,7 @@ It demonstrates the following APIs:
## Prerequisites
- [.NET 6+](https://dotnet.microsoft.com/download) installed
- [.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/install-dapr-selfhost/)
- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/)

View File

@ -4,51 +4,50 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace ConfigurationApi
namespace ConfigurationApi;
public class Startup
{
public class Startup
/// <summary>
/// Initializes a new instance of the <see cref="Startup"/> class.
/// </summary>
/// <param name="configuration">Configuration.</param>
public Startup(IConfiguration configuration)
{
/// <summary>
/// Initializes a new instance of the <see cref="Startup"/> class.
/// </summary>
/// <param name="configuration">Configuration.</param>
public Startup(IConfiguration configuration)
{
this.Configuration = configuration;
}
/// <summary>
/// Gets the configuration.
/// </summary>
public IConfiguration Configuration { get; }
/// <summary>
/// Configures Services.
/// </summary>
/// <param name="services">Service Collection.</param>
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddDapr();
}
/// <summary>
/// Configures Application Builder and WebHost environment.
/// </summary>
/// <param name="app">Application builder.</param>
/// <param name="env">Webhost environment.</param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
this.Configuration = configuration;
}
}
/// <summary>
/// Gets the configuration.
/// </summary>
public IConfiguration Configuration { get; }
/// <summary>
/// Configures Services.
/// </summary>
/// <param name="services">Service Collection.</param>
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddDapr();
}
/// <summary>
/// Configures Application Builder and WebHost environment.
/// </summary>
/// <param name="app">Application builder.</param>
/// <param name="env">Webhost environment.</param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}

View File

@ -1,43 +0,0 @@
// ------------------------------------------------------------------------
// 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(string componentName, string keyName) : 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 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, cancellationToken);
Console.WriteLine($"Decrypted string: '{Encoding.UTF8.GetString(decryptedBytes.ToArray())}'");
}
}
}

View File

@ -1,49 +0,0 @@
// ------------------------------------------------------------------------
// 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 const string ComponentName = "localstorage";
private const string KeyName = "rsa-private-key.pem"; //This should match the name of your generated key - this sample expects an RSA symmetrical key.
private static readonly Example[] Examples = new Example[]
{
new EncryptDecryptStringExample(ComponentName, KeyName),
new EncryptDecryptFileStreamExample(ComponentName, KeyName)
};
static async Task<int> 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 by passing your selection's number into the arguments, e.g. 'dotnet run 0':");
for (var i = 0; i < Examples.Length; i++)
{
Console.WriteLine($"{i}: {Examples[i].DisplayName}");
}
Console.WriteLine();
return 1;
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
@ -8,100 +9,86 @@ using DistributedLock.Model;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace DistributedLock.Controllers
namespace DistributedLock.Controllers;
[ApiController]
[Experimental("DAPR_DISTRIBUTEDLOCK", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/")]
public class BindingController(DaprClient client, ILogger<BindingController> logger) : ControllerBase
{
[ApiController]
public class BindingController : ControllerBase
private string appId = Environment.GetEnvironmentVariable("APP_ID");
[HttpPost("cronbinding")]
public async Task<IActionResult> HandleBindingEvent()
{
private DaprClient client;
private ILogger<BindingController> logger;
private string appId;
logger.LogInformation("Received binding event on {appId}, scanning for work.", appId);
public BindingController(DaprClient client, ILogger<BindingController> logger)
var request = new BindingRequest("localstorage", "list");
var result = client.InvokeBindingAsync(request);
var rawData = result.Result.Data.ToArray();
var files = JsonSerializer.Deserialize<string[]>(rawData);
if (files != null)
{
this.client = client;
this.logger = logger;
this.appId = Environment.GetEnvironmentVariable("APP_ID");
}
[HttpPost("cronbinding")]
[Obsolete]
public async Task<IActionResult> HandleBindingEvent()
{
logger.LogInformation($"Received binding event on {appId}, scanning for work.");
var request = new BindingRequest("localstorage", "list");
var result = client.InvokeBindingAsync(request);
var rawData = result.Result.Data.ToArray();
var files = JsonSerializer.Deserialize<string[]>(rawData);
if (files != null)
foreach (var file in files.Select(file => file.Split("/").Last()).OrderBy(file => file))
{
foreach (var file in files.Select(file => file.Split("/").Last()).OrderBy(file => file))
{
await AttemptToProcessFile(file);
}
await AttemptToProcessFile(file);
}
}
return Ok();
}
return Ok();
}
[Obsolete]
private async Task AttemptToProcessFile(string fileName)
private async Task AttemptToProcessFile(string fileName)
{
// Locks are Disposable and will automatically unlock at the end of a 'using' statement.
logger.LogInformation("Attempting to lock: {fileName}", fileName);
await using var fileLock = await client.Lock("redislock", fileName, appId, 60);
if (fileLock.Success)
{
// Locks are Disposable and will automatically unlock at the end of a 'using' statement.
logger.LogInformation($"Attempting to lock: {fileName}");
await using (var fileLock = await client.Lock("redislock", fileName, appId, 60))
logger.LogInformation("Successfully locked file: {fileName}", fileName);
// Get the file after we've locked it, we're safe here because of the lock.
var fileState = await GetFile(fileName);
if (fileState == null)
{
if (fileLock.Success)
{
logger.LogInformation($"Successfully locked file: {fileName}");
// Get the file after we've locked it, we're safe here because of the lock.
var fileState = await GetFile(fileName);
if (fileState == null)
{
logger.LogWarning($"File {fileName} has already been processed!");
return;
}
// "Analyze" the file before committing it to our remote storage.
fileState.Analysis = fileState.Number > 50 ? "High" : "Low";
// Save it to remote storage.
await client.SaveStateAsync("redisstore", fileName, fileState);
// Remove it from local storage.
var bindingDeleteRequest = new BindingRequest("localstorage", "delete");
bindingDeleteRequest.Metadata["fileName"] = fileName;
await client.InvokeBindingAsync(bindingDeleteRequest);
logger.LogInformation($"Done processing {fileName}");
}
else
{
logger.LogWarning($"Failed to lock {fileName}.");
}
logger.LogWarning("File {fileName} has already been processed!", fileName);
return;
}
// "Analyze" the file before committing it to our remote storage.
fileState.Analysis = fileState.Number > 50 ? "High" : "Low";
// Save it to remote storage.
await client.SaveStateAsync("redisstore", fileName, fileState);
// Remove it from local storage.
var bindingDeleteRequest = new BindingRequest("localstorage", "delete");
bindingDeleteRequest.Metadata["fileName"] = fileName;
await client.InvokeBindingAsync(bindingDeleteRequest);
logger.LogInformation("Done processing {fileName}", fileName);
}
private async Task<StateData> GetFile(string fileName)
else
{
try
{
var bindingGetRequest = new BindingRequest("localstorage", "get");
bindingGetRequest.Metadata["fileName"] = fileName;
var bindingResponse = await client.InvokeBindingAsync(bindingGetRequest);
return JsonSerializer.Deserialize<StateData>(bindingResponse.Data.ToArray());
}
catch (DaprException)
{
return null;
}
logger.LogWarning("Failed to lock {fileName}.", fileName);
}
}
private async Task<StateData> GetFile(string fileName)
{
try
{
var bindingGetRequest = new BindingRequest("localstorage", "get");
bindingGetRequest.Metadata["fileName"] = fileName;
var bindingResponse = await client.InvokeBindingAsync(bindingGetRequest);
return JsonSerializer.Deserialize<StateData>(bindingResponse.Data.ToArray());
}
catch (DaprException)
{
return null;
}
}
}

View File

@ -1,14 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<ItemGroup>
<ProjectReference Include="..\..\..\src\Dapr.AspNetCore\Dapr.AspNetCore.csproj" />
<ProjectReference Include="..\..\..\src\Dapr.Client\Dapr.Client.csproj" />
<ProjectReference Include="..\..\..\src\Dapr.Common\Dapr.Common.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -1,15 +1,13 @@
namespace DistributedLock.Model
{
namespace DistributedLock.Model;
#nullable enable
public class StateData
{
public int Number { get; }
public string? Analysis { get; set; }
public class StateData
{
public int Number { get; }
public string? Analysis { get; set; }
public StateData(int number, string? analysis = null)
{
Number = number;
Analysis = analysis;
}
public StateData(int number, string? analysis = null)
{
Number = number;
Analysis = analysis;
}
}
}

View File

@ -2,22 +2,21 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace DistributedLock
{
public class Program
{
static string DEFAULT_APP_PORT = "22222";
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
namespace DistributedLock;
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>()
.UseUrls($"http://localhost:{Environment.GetEnvironmentVariable("APP_PORT") ?? DEFAULT_APP_PORT}");
});
public class Program
{
static string DEFAULT_APP_PORT = "22222";
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>()
.UseUrls($"http://localhost:{Environment.GetEnvironmentVariable("APP_PORT") ?? DEFAULT_APP_PORT}");
});
}

View File

@ -2,13 +2,13 @@
## Prerequisites
- [.NET 6+](https://dotnet.microsoft.com/download) installed
- [.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/install-dapr-selfhost/)
- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/)
## Distributed Lock API
Dapr 1.8 introduces the Distributed Lock API. This API can be used to prevent multiple processes from accessing the same resource. In Dapr, locks are scoped to a specific App ID.
Dapr 1.8 introduced the Distributed Lock API. This API can be used to prevent multiple processes from accessing the same resource. In Dapr, locks are scoped to a specific App ID.
For this example, we will be running multiple instances of the same application to demonstrate an event driven consumer pattern. This example also includes a simple generator that creates some data that can be processed.

View File

@ -3,30 +3,29 @@ using System.Threading;
using Dapr.Client;
using DistributedLock.Model;
namespace DistributedLock.Services
namespace DistributedLock.Services;
public class GeneratorService
{
public class GeneratorService
Timer generateDataTimer;
public GeneratorService()
{
Timer generateDataTimer;
public GeneratorService()
// Generate some data every second.
if (Environment.GetEnvironmentVariable("APP_ID") == "generator")
{
// Generate some data every second.
if (Environment.GetEnvironmentVariable("APP_ID") == "generator")
{
generateDataTimer = new Timer(GenerateData, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10));
}
}
public async void GenerateData(Object stateInfo)
{
using (var client = new DaprClientBuilder().Build())
{
var rand = new Random();
var state = new StateData(rand.Next(100));
await client.InvokeBindingAsync("localstorage", "create", state);
}
generateDataTimer = new Timer(GenerateData, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10));
}
}
}
public async void GenerateData(Object stateInfo)
{
using (var client = new DaprClientBuilder().Build())
{
var rand = new Random();
var state = new StateData(rand.Next(100));
await client.InvokeBindingAsync("localstorage", "create", state);
}
}
}

View File

@ -1,46 +1,45 @@
using Dapr.AspNetCore;
using DistributedLock.Services;
using DistributedLock.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace DistributedLock
namespace DistributedLock;
public class Startup
{
public class Startup
public Startup(IConfiguration configuration)
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddDapr();
services.AddLogging();
services.AddSingleton(new GeneratorService());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Configuration = configuration;
}
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddDapr();
services.AddLogging();
services.AddSingleton(new GeneratorService());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}

View File

@ -17,46 +17,45 @@ using System.Threading;
using System.Threading.Tasks;
using Dapr.Client;
namespace Samples.Client
namespace Samples.Client;
public sealed class BulkPublishEventExample : Example
{
public class BulkPublishEventExample : Example
{
private const string PubsubName = "pubsub";
private const string TopicName = "deposit";
private const string PubsubName = "pubsub";
private const string TopicName = "deposit";
IReadOnlyList<object> BulkPublishData = new List<object>() {
new { Id = "17", Amount = 10m },
new { Id = "18", Amount = 20m },
new { Id = "19", Amount = 30m }
};
IReadOnlyList<object> BulkPublishData = new List<object>() {
new { Id = "17", Amount = 10m },
new { Id = "18", Amount = 20m },
new { Id = "19", Amount = 30m }
};
public override string DisplayName => "Bulk Publishing Events";
public override string DisplayName => "Bulk Publishing Events";
public override async Task RunAsync(CancellationToken cancellationToken)
{
using var client = new DaprClientBuilder().Build();
public override async Task RunAsync(CancellationToken cancellationToken)
{
using var client = new DaprClientBuilder().Build();
var res = await client.BulkPublishEventAsync(PubsubName, TopicName,
BulkPublishData);
var res = await client.BulkPublishEventAsync(PubsubName, TopicName,
BulkPublishData, cancellationToken: cancellationToken);
if (res != null) {
if (res.FailedEntries.Count > 0)
{
Console.WriteLine("Some events failed to be published!");
if (res != null) {
if (res.FailedEntries.Count > 0)
{
Console.WriteLine("Some events failed to be published!");
foreach (var failedEntry in res.FailedEntries)
{
Console.WriteLine("EntryId : " + failedEntry.Entry.EntryId + " Error message : " +
failedEntry.ErrorMessage);
}
}
else
foreach (var failedEntry in res.FailedEntries)
{
Console.WriteLine("Published multiple deposit events!");
Console.WriteLine("EntryId : " + failedEntry.Entry.EntryId + " Error message : " +
failedEntry.ErrorMessage);
}
} else {
throw new Exception("null response from dapr");
}
else
{
Console.WriteLine("Published multiple deposit events!");
}
} else {
throw new Exception("null response from dapr");
}
}
}

View File

@ -2,7 +2,6 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6</TargetFramework>
<RootNamespace>Samples.Client</RootNamespace>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -14,12 +14,11 @@
using System.Threading;
using System.Threading.Tasks;
namespace Samples.Client
{
public abstract class Example
{
public abstract string DisplayName { get; }
namespace Samples.Client;
public abstract Task RunAsync(CancellationToken cancellationToken);
}
}
public abstract class Example
{
public abstract string DisplayName { get; }
public abstract Task RunAsync(CancellationToken cancellationToken);
}

View File

@ -13,35 +13,26 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Samples.Client;
namespace Samples.Client
Example[] Examples =
[
new BulkPublishEventExample()
];
if (args.Length > 0 && int.TryParse(args[0], out var index) && index >= 0 && index < Examples.Length)
{
class Program
{
private static readonly Example[] Examples = new Example[]
{
new BulkPublishEventExample(),
};
var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, _) => cts.Cancel();
static async Task<int> 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 0;
}
}
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 0;

View File

@ -2,7 +2,7 @@
## Prerequisites
- [.NET 6+](https://dotnet.microsoft.com/download) installed
- [.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/install-dapr-selfhost/)
- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/)

View File

@ -14,14 +14,13 @@
using System.Threading;
using System.Threading.Tasks;
namespace Samples.Client
namespace Samples.Client;
public abstract class Example
{
public abstract class Example
{
protected static readonly string pubsubName = "pubsub";
protected static readonly string pubsubName = "pubsub";
public abstract string DisplayName { get; }
public abstract string DisplayName { get; }
public abstract Task RunAsync(CancellationToken cancellationToken);
}
}
public abstract Task RunAsync(CancellationToken cancellationToken);
}

View File

@ -15,34 +15,33 @@ using System;
using System.Threading;
using System.Threading.Tasks;
namespace Samples.Client
namespace Samples.Client;
class Program
{
class Program
private static readonly Example[] Examples = new Example[]
{
private static readonly Example[] Examples = new Example[]
new PublishEventExample(),
new PublishBytesExample(),
};
static async Task<int> Main(string[] args)
{
if (args.Length > 0 && int.TryParse(args[0], out var index) && index >= 0 && index < Examples.Length)
{
new PublishEventExample(),
new PublishBytesExample(),
};
var cts = new CancellationTokenSource();
Console.CancelKeyPress += (object? sender, ConsoleCancelEventArgs e) => cts.Cancel();
static async Task<int> 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();
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 0;
}
}
}

View File

@ -19,21 +19,20 @@ using System.Threading;
using System.Threading.Tasks;
using Dapr.Client;
namespace Samples.Client
namespace Samples.Client;
public class PublishBytesExample : Example
{
public class PublishBytesExample : Example
public override string DisplayName => "Publish Bytes";
public async override Task RunAsync(CancellationToken cancellationToken)
{
public override string DisplayName => "Publish Bytes";
using var client = new DaprClientBuilder().Build();
public async override Task RunAsync(CancellationToken cancellationToken)
{
using var client = new DaprClientBuilder().Build();
var transaction = new { Id = "17", Amount = 30m };
var content = JsonSerializer.SerializeToUtf8Bytes(transaction);
var transaction = new { Id = "17", Amount = 30m };
var content = JsonSerializer.SerializeToUtf8Bytes(transaction);
await client.PublishByteEventAsync(pubsubName, "deposit", content.AsMemory(), MediaTypeNames.Application.Json, new Dictionary<string, string> { }, cancellationToken);
Console.WriteLine("Published deposit event!");
}
await client.PublishByteEventAsync(pubsubName, "deposit", content.AsMemory(), MediaTypeNames.Application.Json, new Dictionary<string, string> { }, cancellationToken);
Console.WriteLine("Published deposit event!");
}
}
}

View File

@ -16,19 +16,18 @@ using System.Threading;
using System.Threading.Tasks;
using Dapr.Client;
namespace Samples.Client
namespace Samples.Client;
public class PublishEventExample : Example
{
public class PublishEventExample : Example
public override string DisplayName => "Publishing Events";
public override async Task RunAsync(CancellationToken cancellationToken)
{
public override string DisplayName => "Publishing Events";
using var client = new DaprClientBuilder().Build();
public override async Task RunAsync(CancellationToken cancellationToken)
{
using var client = new DaprClientBuilder().Build();
var eventData = new { Id = "17", Amount = 10m, };
await client.PublishEventAsync(pubsubName, "deposit", eventData, cancellationToken);
Console.WriteLine("Published deposit event!");
}
var eventData = new { Id = "17", Amount = 10m, };
await client.PublishEventAsync(pubsubName, "deposit", eventData, cancellationToken);
Console.WriteLine("Published deposit event!");
}
}
}

View File

@ -2,7 +2,6 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6</TargetFramework>
<RootNamespace>Samples.Client</RootNamespace>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -2,7 +2,7 @@
## Prerequisites
- [.NET 6+](https://dotnet.microsoft.com/download) installed
- [.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/install-dapr-selfhost/)
- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/)

View File

@ -14,12 +14,11 @@
using System.Threading;
using System.Threading.Tasks;
namespace Samples.Client
{
public abstract class Example
{
public abstract string DisplayName { get; }
namespace Samples.Client;
public abstract Task RunAsync(CancellationToken cancellationToken);
}
}
public abstract class Example
{
public abstract string DisplayName { get; }
public abstract Task RunAsync(CancellationToken cancellationToken);
}

View File

@ -17,32 +17,31 @@ using System.Threading.Tasks;
using Dapr.Client;
using GrpcServiceSample.Generated;
namespace Samples.Client
namespace Samples.Client;
public class InvokeServiceGrpcExample : Example
{
public class InvokeServiceGrpcExample : Example
public override string DisplayName => "Invoking a gRPC service with gRPC semantics and Protobuf with DaprClient";
// Note: the data types used in this sample are generated from data.proto in GrpcServiceSample
public override async Task RunAsync(CancellationToken cancellationToken)
{
public override string DisplayName => "Invoking a gRPC service with gRPC semantics and Protobuf with DaprClient";
using var client = new DaprClientBuilder().Build();
// Note: the data types used in this sample are generated from data.proto in GrpcServiceSample
public override async Task RunAsync(CancellationToken cancellationToken)
{
using var client = new DaprClientBuilder().Build();
Console.WriteLine("Invoking grpc deposit");
var deposit = new GrpcServiceSample.Generated.Transaction() { Id = "17", Amount = 99 };
var account = await client.InvokeMethodGrpcAsync<GrpcServiceSample.Generated.Transaction, Account>("grpcsample", "deposit", deposit, cancellationToken);
Console.WriteLine("Returned: id:{0} | Balance:{1}", account.Id, account.Balance);
Console.WriteLine("Completed grpc deposit");
Console.WriteLine("Invoking grpc deposit");
var deposit = new GrpcServiceSample.Generated.Transaction() { Id = "17", Amount = 99 };
var account = await client.InvokeMethodGrpcAsync<GrpcServiceSample.Generated.Transaction, Account>("grpcsample", "deposit", deposit, cancellationToken);
Console.WriteLine("Returned: id:{0} | Balance:{1}", account.Id, account.Balance);
Console.WriteLine("Completed grpc deposit");
Console.WriteLine("Invoking grpc withdraw");
var withdraw = new GrpcServiceSample.Generated.Transaction() { Id = "17", Amount = 10, };
await client.InvokeMethodGrpcAsync("grpcsample", "withdraw", withdraw, cancellationToken);
Console.WriteLine("Completed grpc withdraw");
Console.WriteLine("Invoking grpc withdraw");
var withdraw = new GrpcServiceSample.Generated.Transaction() { Id = "17", Amount = 10, };
await client.InvokeMethodGrpcAsync("grpcsample", "withdraw", withdraw, cancellationToken);
Console.WriteLine("Completed grpc withdraw");
Console.WriteLine("Invoking grpc balance");
var request = new GetAccountRequest() { Id = "17", };
account = await client.InvokeMethodGrpcAsync<GetAccountRequest, Account>("grpcsample", "getaccount", request, cancellationToken);
Console.WriteLine($"Received grpc balance {account.Balance}");
}
Console.WriteLine("Invoking grpc balance");
var request = new GetAccountRequest() { Id = "17", };
account = await client.InvokeMethodGrpcAsync<GetAccountRequest, Account>("grpcsample", "getaccount", request, cancellationToken);
Console.WriteLine($"Received grpc balance {account.Balance}");
}
}
}

View File

@ -17,41 +17,40 @@ using System.Threading;
using System.Threading.Tasks;
using Dapr.Client;
namespace Samples.Client
namespace Samples.Client;
public class InvokeServiceHttpClientExample : Example
{
public class InvokeServiceHttpClientExample : Example
public override string DisplayName => "Invoking an HTTP service with HttpClient";
public override async Task RunAsync(CancellationToken cancellationToken)
{
public override string DisplayName => "Invoking an HTTP service with HttpClient";
var client = DaprClient.CreateInvokeHttpClient(appId: "routing");
public override async Task RunAsync(CancellationToken cancellationToken)
{
var client = DaprClient.CreateInvokeHttpClient(appId: "routing");
var deposit = new Transaction { Id = "17", Amount = 99m };
var response = await client.PostAsJsonAsync("/deposit", deposit, cancellationToken);
var account = await response.Content.ReadFromJsonAsync<Account>(cancellationToken: cancellationToken);
Console.WriteLine("Returned: id:{0} | Balance:{1}", account?.Id, account?.Balance);
var deposit = new Transaction { Id = "17", Amount = 99m };
var response = await client.PostAsJsonAsync("/deposit", deposit, cancellationToken);
var account = await response.Content.ReadFromJsonAsync<Account>(cancellationToken: cancellationToken);
Console.WriteLine("Returned: id:{0} | Balance:{1}", account?.Id, account?.Balance);
var withdraw = new Transaction { Id = "17", Amount = 10m, };
response = await client.PostAsJsonAsync("/withdraw", withdraw, cancellationToken);
response.EnsureSuccessStatusCode();
var withdraw = new Transaction { Id = "17", Amount = 10m, };
response = await client.PostAsJsonAsync("/withdraw", withdraw, cancellationToken);
response.EnsureSuccessStatusCode();
account = await client.GetFromJsonAsync<Account>("/17", cancellationToken);
Console.WriteLine($"Received balance {account?.Balance}");
}
internal class Transaction
{
public string? Id { get; set; }
public decimal? Amount { get; set; }
}
internal class Account
{
public string? Id { get; set; }
public decimal? Balance { get; set; }
}
account = await client.GetFromJsonAsync<Account>("/17", cancellationToken);
Console.WriteLine($"Received balance {account?.Balance}");
}
}
internal class Transaction
{
public string? Id { get; set; }
public decimal? Amount { get; set; }
}
internal class Account
{
public string? Id { get; set; }
public decimal? Balance { get; set; }
}
}

View File

@ -17,46 +17,45 @@ using System.Threading;
using System.Threading.Tasks;
using Dapr.Client;
namespace Samples.Client
namespace Samples.Client;
public class InvokeServiceHttpExample : Example
{
public class InvokeServiceHttpExample : Example
public override string DisplayName => "Invoking an HTTP service with DaprClient";
public override async Task RunAsync(CancellationToken cancellationToken)
{
public override string DisplayName => "Invoking an HTTP service with DaprClient";
using var client = new DaprClientBuilder().Build();
public override async Task RunAsync(CancellationToken cancellationToken)
{
using var client = new DaprClientBuilder().Build();
// Invokes a POST method named "deposit" that takes input of type "Transaction" as define in the RoutingSample.
Console.WriteLine("Invoking deposit");
var data = new { id = "17", amount = 99m };
var account = await client.InvokeMethodAsync<object, Account>("routing", "deposit", data, cancellationToken);
Console.WriteLine("Returned: id:{0} | Balance:{1}", account.Id, account.Balance);
// Invokes a POST method named "deposit" that takes input of type "Transaction" as define in the RoutingSample.
Console.WriteLine("Invoking deposit");
var data = new { id = "17", amount = 99m };
var account = await client.InvokeMethodAsync<object, Account>("routing", "deposit", data, cancellationToken);
Console.WriteLine("Returned: id:{0} | Balance:{1}", account.Id, account.Balance);
// Invokes a POST method named "Withdraw" that takes input of type "Transaction" as define in the RoutingSample.
Console.WriteLine("Invoking withdraw");
data = new { id = "17", amount = 10m, };
await client.InvokeMethodAsync<object>("routing", "Withdraw", data, cancellationToken);
Console.WriteLine("Completed");
// Invokes a POST method named "Withdraw" that takes input of type "Transaction" as define in the RoutingSample.
Console.WriteLine("Invoking withdraw");
data = new { id = "17", amount = 10m, };
await client.InvokeMethodAsync<object>("routing", "Withdraw", data, cancellationToken);
Console.WriteLine("Completed");
// Invokes a GET method named "hello" that takes input of type "MyData" and returns a string.
Console.WriteLine("Invoking balance");
account = await client.InvokeMethodAsync<Account>(HttpMethod.Get, "routing", "17", cancellationToken);
Console.WriteLine($"Received balance {account.Balance}");
}
internal class Transaction
{
public string? Id { get; set; }
public decimal? Amount { get; set; }
}
internal class Account
{
public string? Id { get; set; }
public decimal? Balance { get; set; }
}
// Invokes a GET method named "hello" that takes input of type "MyData" and returns a string.
Console.WriteLine("Invoking balance");
account = await client.InvokeMethodAsync<Account>(HttpMethod.Get, "routing", "17", cancellationToken);
Console.WriteLine($"Received balance {account.Balance}");
}
}
internal class Transaction
{
public string? Id { get; set; }
public decimal? Amount { get; set; }
}
internal class Account
{
public string? Id { get; set; }
public decimal? Balance { get; set; }
}
}

View File

@ -17,48 +17,45 @@ using System.Threading;
using System.Threading.Tasks;
using Dapr.Client;
namespace Samples.Client
namespace Samples.Client;
public class InvokeServiceHttpNonDaprEndpointExample : Example
{
public class InvokeServiceHttpNonDaprEndpointExample : Example
public override string DisplayName => "Invoke a non Dapr endpoint using DaprClient";
public override async Task RunAsync(CancellationToken cancellationToken)
{
public override string DisplayName => "Invoke a non Dapr endpoint using DaprClient";
public override async Task RunAsync(CancellationToken cancellationToken)
{
using var client = new DaprClientBuilder().Build();
using var client = new DaprClientBuilder().Build();
// Invoke a POST method named "deposit" that takes input of type "Transaction" as defined in the RoutingSample.
Console.WriteLine("Invoking deposit using non Dapr endpoint.");
var data = new { id = "17", amount = 99m };
var account = await client.InvokeMethodAsync<object, Account>("http://localhost:5000", "deposit", data, cancellationToken);
Console.WriteLine("Returned: id:{0} | Balance:{1}", account.Id, account.Balance);
// Invoke a POST method named "deposit" that takes input of type "Transaction" as defined in the RoutingSample.
Console.WriteLine("Invoking deposit using non Dapr endpoint.");
var data = new { id = "17", amount = 99m };
var account = await client.InvokeMethodAsync<object, Account>("http://localhost:5000", "deposit", data, cancellationToken);
Console.WriteLine("Returned: id:{0} | Balance:{1}", account.Id, account.Balance);
// Invokes a POST method named "Withdraw" that takes input of type "Transaction" as defined in the RoutingSample.
Console.WriteLine("Invoking withdraw using non Dapr endpoint.");
data = new { id = "17", amount = 10m, };
await client.InvokeMethodAsync<object>("http://localhost:5000", "withdraw", data, cancellationToken);
Console.WriteLine("Completed");
// Invokes a POST method named "Withdraw" that takes input of type "Transaction" as defined in the RoutingSample.
Console.WriteLine("Invoking withdraw using non Dapr endpoint.");
data = new { id = "17", amount = 10m, };
await client.InvokeMethodAsync<object>("http://localhost:5000", "withdraw", data, cancellationToken);
Console.WriteLine("Completed");
// Invokes a GET method named "hello" that takes input of type "MyData" and returns a string.
Console.WriteLine("Invoking balance using non Dapr endpoint.");
account = await client.InvokeMethodAsync<Account>(HttpMethod.Get, "http://localhost:5000", "17", cancellationToken);
Console.WriteLine($"Received balance {account.Balance}");
}
internal class Transaction
{
public string? Id { get; set; }
public decimal Amount { get; set; }
}
internal class Account
{
public string? Id { get; set; }
public decimal Balance { get; set; }
}
// Invokes a GET method named "hello" that takes input of type "MyData" and returns a string.
Console.WriteLine("Invoking balance using non Dapr endpoint.");
account = await client.InvokeMethodAsync<Account>(HttpMethod.Get, "http://localhost:5000", "17", cancellationToken);
Console.WriteLine($"Received balance {account.Balance}");
}
}
internal class Transaction
{
public string? Id { get; set; }
public decimal Amount { get; set; }
}
internal class Account
{
public string? Id { get; set; }
public decimal Balance { get; set; }
}
}

View File

@ -15,36 +15,35 @@ using System;
using System.Threading;
using System.Threading.Tasks;
namespace Samples.Client
namespace Samples.Client;
class Program
{
class Program
private static readonly Example[] Examples =
[
new InvokeServiceGrpcExample(),
new InvokeServiceHttpExample(),
new InvokeServiceHttpClientExample(),
new InvokeServiceHttpNonDaprEndpointExample()
];
static async Task<int> Main(string[] args)
{
private static readonly Example[] Examples = new Example[]
if (args.Length > 0 && int.TryParse(args[0], out var index) && index >= 0 && index < Examples.Length)
{
new InvokeServiceGrpcExample(),
new InvokeServiceHttpExample(),
new InvokeServiceHttpClientExample(),
new InvokeServiceHttpNonDaprEndpointExample()
};
var cts = new CancellationTokenSource();
Console.CancelKeyPress += (sender, e) => cts.Cancel();
static async Task<int> 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;
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;
}
}

View File

@ -2,7 +2,7 @@
## Prerequisites
- [.NET 6+](https://dotnet.microsoft.com/download) installed
- [.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/install-dapr-selfhost/)
- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/)

View File

@ -2,7 +2,6 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6</TargetFramework>
<RootNamespace>Samples.Client</RootNamespace>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -4,51 +4,50 @@ using System.Threading;
using System.Threading.Tasks;
using Dapr.Client;
namespace Samples.Client
namespace Samples.Client;
public class BulkStateExample : Example
{
public class BulkStateExample : Example
private static readonly string firstKey = "testKey1";
private static readonly string secondKey = "testKey2";
private static readonly string firstEtag = "123";
private static readonly string secondEtag = "456";
private static readonly string storeName = "statestore";
public override string DisplayName => "Using the State Store";
public override async Task RunAsync(CancellationToken cancellationToken)
{
private static readonly string firstKey = "testKey1";
private static readonly string secondKey = "testKey2";
private static readonly string firstEtag = "123";
private static readonly string secondEtag = "456";
private static readonly string storeName = "statestore";
using var client = new DaprClientBuilder().Build();
public override string DisplayName => "Using the State Store";
public override async Task RunAsync(CancellationToken cancellationToken)
{
using var client = new DaprClientBuilder().Build();
var state1 = new Widget() { Size = "small", Color = "yellow", };
var state2 = new Widget() { Size = "big", Color = "green", };
var state1 = new Widget() { Size = "small", Color = "yellow", };
var state2 = new Widget() { Size = "big", Color = "green", };
var stateItem1 = new SaveStateItem<Widget>(firstKey, state1, firstEtag);
var stateItem2 = new SaveStateItem<Widget>(secondKey, state2, secondEtag);
var stateItem1 = new SaveStateItem<Widget>(firstKey, state1, firstEtag);
var stateItem2 = new SaveStateItem<Widget>(secondKey, state2, secondEtag);
await client.SaveBulkStateAsync(storeName, new List<SaveStateItem<Widget>>() { stateItem1, stateItem2});
await client.SaveBulkStateAsync(storeName, new List<SaveStateItem<Widget>>() { stateItem1, stateItem2});
Console.WriteLine("Saved 2 States!");
Console.WriteLine("Saved 2 States!");
await Task.Delay(2000);
await Task.Delay(2000);
IReadOnlyList<BulkStateItem> states = await client.GetBulkStateAsync(storeName,
new List<string>(){firstKey, secondKey}, null);
IReadOnlyList<BulkStateItem> states = await client.GetBulkStateAsync(storeName,
new List<string>(){firstKey, secondKey}, null);
Console.WriteLine($"Got {states.Count} States: ");
Console.WriteLine($"Got {states.Count} States: ");
var deleteBulkStateItem1 = new BulkDeleteStateItem(states[0].Key, states[0].ETag);
var deleteBulkStateItem2 = new BulkDeleteStateItem(states[1].Key, states[1].ETag);
var deleteBulkStateItem1 = new BulkDeleteStateItem(states[0].Key, states[0].ETag);
var deleteBulkStateItem2 = new BulkDeleteStateItem(states[1].Key, states[1].ETag);
await client.DeleteBulkStateAsync(storeName, new List<BulkDeleteStateItem>() { deleteBulkStateItem1, deleteBulkStateItem2 });
await client.DeleteBulkStateAsync(storeName, new List<BulkDeleteStateItem>() { deleteBulkStateItem1, deleteBulkStateItem2 });
Console.WriteLine("Deleted States!");
}
private class Widget
{
public string? Size { get; set; }
public string? Color { get; set; }
}
Console.WriteLine("Deleted States!");
}
}
private class Widget
{
public string? Size { get; set; }
public string? Color { get; set; }
}
}

View File

@ -14,12 +14,11 @@
using System.Threading;
using System.Threading.Tasks;
namespace Samples.Client
{
public abstract class Example
{
public abstract string DisplayName { get; }
namespace Samples.Client;
public abstract Task RunAsync(CancellationToken cancellationToken);
}
}
public abstract class Example
{
public abstract string DisplayName { get; }
public abstract Task RunAsync(CancellationToken cancellationToken);
}

View File

@ -15,37 +15,36 @@ using System;
using System.Threading;
using System.Threading.Tasks;
namespace Samples.Client
namespace Samples.Client;
class Program
{
class Program
private static readonly Example[] Examples = new Example[]
{
private static readonly Example[] Examples = new Example[]
new StateStoreExample(),
new StateStoreTransactionsExample(),
new StateStoreETagsExample(),
new BulkStateExample(),
new StateStoreBinaryExample()
};
static async Task<int> Main(string[] args)
{
if (args.Length > 0 && int.TryParse(args[0], out var index) && index >= 0 && index < Examples.Length)
{
new StateStoreExample(),
new StateStoreTransactionsExample(),
new StateStoreETagsExample(),
new BulkStateExample(),
new StateStoreBinaryExample()
};
var cts = new CancellationTokenSource();
Console.CancelKeyPress += (object? sender, ConsoleCancelEventArgs e) => cts.Cancel();
static async Task<int> 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;
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;
}
}
}

View File

@ -2,7 +2,7 @@
## Prerequisites
- [.NET 6+](https://dotnet.microsoft.com/download) installed
- [.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/install-dapr-selfhost/)
- [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/)

Some files were not shown because too many files have changed in this diff Show More