diff --git a/.github/holopin.yml b/.github/holopin.yml new file mode 100644 index 000000000..44a7f0c8a --- /dev/null +++ b/.github/holopin.yml @@ -0,0 +1,6 @@ +organization: dapr +defaultSticker: clmjkxscc122740fl0mkmb7egi +stickers: + - + id: clmjkxscc122740fl0mkmb7egi + alias: ghc2023 diff --git a/.github/workflows/itests.yml b/.github/workflows/itests.yml index 0044fec93..8a299bd59 100644 --- a/.github/workflows/itests.yml +++ b/.github/workflows/itests.yml @@ -19,37 +19,33 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - dotnet-version: ['3.1', '6.0', '7.0'] + dotnet-version: ['6.0', '7.0', '8.0'] include: - - dotnet-version: '3.1' - install-3: true - display-name: '.NET Core 3.1' - framework: 'netcoreapp3.1' - prefix: 'netcoreapp31' - install-version: '3.1.x' # We always need a new .NET - dotnet-version: '6.0' - install-3: false display-name: '.NET 6.0' framework: 'net6' prefix: 'net6' install-version: '6.0.x' - dotnet-version: '7.0' - install-3: false 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' + prefix: 'net8' + install-version: '8.0.x' env: NUPKG_OUTDIR: bin/Release/nugets GOVER: 1.20.3 GOOS: linux GOARCH: amd64 GOPROXY: https://proxy.golang.org - DAPR_CLI_VER: 1.9.1 - DAPR_RUNTIME_VER: 1.10.5 - DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/3dacfb672d55f1436c249057aaebbe597e1066f3/install/install.sh + DAPR_CLI_VER: 1.12.0 + DAPR_RUNTIME_VER: 1.12.0 + DAPR_INSTALL_URL: https://raw.githubusercontent.com/dapr/cli/release-1.12/install/install.sh DAPR_CLI_REF: '' - DAPR_REF: '4181de0edc65fc98a836ae7abc6042c575c8fae5' steps: - name: Set up Dapr CLI run: wget -q ${{ env.DAPR_INSTALL_URL }} -O - | /bin/bash -s ${{ env.DAPR_CLI_VER }} @@ -105,25 +101,15 @@ jobs: - uses: actions/checkout@v1 - name: Parse release version run: python ./.github/scripts/get_release_version.py - - name: Install Local kafka using docker-compose - run: | - docker-compose -f test/Dapr.E2E.Test/deploy/local-test-kafka.yml up -d - docker ps - - name: Install Local Hashicorp Vault using docker-compose - run: | - docker-compose -f test/Dapr.E2E.Test/deploy/local-test-vault.yml up -d - docker ps - - name: Setup Vault's test token - run: echo myroot > /tmp/.hashicorp_vault_token - name: Setup ${{ matrix.display-name }} uses: actions/setup-dotnet@v1 with: dotnet-version: ${{ matrix.install-version }} - - name: Setup .NET 7.0 # net7 is always required. + - name: Setup .NET 8.0 # net8 is always required. uses: actions/setup-dotnet@v1 - if: ${{ matrix.install-version != '7.0.x' }} + if: ${{ matrix.install-version != '8.0.x' }} with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - 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 diff --git a/.github/workflows/sdk_build.yml b/.github/workflows/sdk_build.yml index b0b6b042c..4fde80610 100644 --- a/.github/workflows/sdk_build.yml +++ b/.github/workflows/sdk_build.yml @@ -26,7 +26,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Build run: dotnet build --configuration release - name: Generate Packages @@ -42,14 +42,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - dotnet-version: ['3.1', '6.0', '7.0'] + dotnet-version: ['6.0', '7.0', '8.0'] include: - - dotnet-version: '3.1' - install-3: true - display-name: '.NET Core 3.1' - framework: 'netcoreapp3.1' - prefix: 'netcoreapp31' - install-version: '3.1.x' # We always need a new .NET - dotnet-version: '6.0' install-3: false display-name: '.NET 6.0' @@ -62,6 +56,12 @@ jobs: framework: 'net7' prefix: 'net7' install-version: '7.0.x' + - dotnet-version: '8.0' + install-3: false + display-name: '.NET 8.0' + framework: 'net8' + prefix: 'net8' + install-version: '8.0.x' steps: - uses: actions/checkout@v1 - name: Parse release version @@ -70,11 +70,11 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: ${{ matrix.install-version }} - - name: Setup .NET 7.0 # net7 is always required. + - name: Setup .NET 8.0 # net8 is always required. uses: actions/setup-dotnet@v1 - if: ${{ matrix.install-version != '7.0.x' }} + if: ${{ matrix.install-version != '8.0.x' }} with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - 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 diff --git a/README.md b/README.md index 54bb606dc..d7232c54e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status](https://github.com/dapr/dotnet-sdk/workflows/build/badge.svg)](https://github.com/dapr/dotnet-sdk/actions?workflow=build) [![codecov](https://codecov.io/gh/dapr/dotnet-sdk/branch/master/graph/badge.svg)](https://codecov.io/gh/dapr/dotnet-sdk) [![License: Apache](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) -[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Fdapr%2Fcomponents-contrib.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fdapr%2Fcomponents-contrib?ref=badge_shield) +[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgit%40github.com%3Adapr%2Fdotnet-sdk.git.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgit%40github.com%3Adapr%2Fdotnet-sdk.git?ref=badge_shield) Dapr SDK for .NET allows you to: diff --git a/daprdocs/content/en/dotnet-sdk-docs/_index.md b/daprdocs/content/en/dotnet-sdk-docs/_index.md index 65d818723..e823ca29f 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/_index.md +++ b/daprdocs/content/en/dotnet-sdk-docs/_index.md @@ -5,6 +5,11 @@ linkTitle: ".NET" weight: 1000 description: .NET SDK packages for developing Dapr applications no_list: true +cascade: + github_repo: https://github.com/dapr/dotnet-sdk + github_subdir: daprdocs/content/en/dotnet-sdk-docs + path_base_for_github_subdir: content/en/developing-applications/sdks/dotnet/ + github_branch: master --- Dapr offers a variety of packages to help with the development of .NET applications. Using them you can create .NET clients, servers, and virtual actors with Dapr. diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-howto.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-howto.md index 986562ca8..ab41c3917 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-howto.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-actors/dotnet-actors-howto.md @@ -81,6 +81,7 @@ Define `IMyActor` interface and `MyData` data object. Paste the following code i ```csharp using Dapr.Actors; +using Dapr.Actors.Runtime; using System.Threading.Tasks; namespace MyActor.Interfaces @@ -91,6 +92,7 @@ namespace MyActor.Interfaces Task GetDataAsync(); Task RegisterReminder(); Task UnregisterReminder(); + Task GetReminder(); Task RegisterTimer(); Task UnregisterTimer(); } @@ -219,6 +221,14 @@ namespace MyActorService TimeSpan.FromSeconds(5)); // Time interval between reminder invocations after the first invocation } + /// + /// Get MyReminder reminder details with the actor + /// + public async Task GetReminder() + { + await this.GetReminderAsync("MyReminder"); + } + /// /// Unregister MyReminder reminder with the actor /// @@ -306,14 +316,6 @@ namespace MyActorService { app.UseDeveloperExceptionPage(); } - else - { - // By default, ASP.Net Core uses port 5000 for HTTP. The HTTP - // redirection will interfere with the Dapr runtime. You can - // move this out of the else block if you use port 5001 in this - // example, and developer tooling (such as the VSCode extension). - app.UseHttpsRedirection(); - } app.UseRouting(); diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-client/dotnet-daprclient-usage.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-client/dotnet-daprclient-usage.md index 00b330693..26328050c 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-client/dotnet-daprclient-usage.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-client/dotnet-daprclient-usage.md @@ -34,8 +34,10 @@ The `DaprClientBuilder` contains settings for: The SDK will read the following environment variables to configure the default values: -- `DAPR_HTTP_PORT`: used to find the HTTP endpoint of the Dapr sidecar -- `DAPR_GRPC_PORT`: used to find the gRPC endpoint of the Dapr sidecar +- `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 diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-workflow/dotnet-workflow-howto.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-workflow/dotnet-workflow-howto.md index 650cdec38..f6d18bc58 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-workflow/dotnet-workflow-howto.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-workflow/dotnet-workflow-howto.md @@ -83,7 +83,7 @@ Run the following command to start a workflow. {{% codetab %}} ```bash -curl -i -X POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=12345678 \ +curl -i -X POST http://localhost:3500/v1.0-beta1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=12345678 \ -H "Content-Type: application/json" \ -d '{"Name": "Paperclips", "TotalCost": 99.95, "Quantity": 1}' ``` @@ -93,7 +93,7 @@ curl -i -X POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessing {{% codetab %}} ```powershell -curl -i -X POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=12345678 ` +curl -i -X POST http://localhost:3500/v1.0-beta1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=12345678 ` -H "Content-Type: application/json" ` -d '{"Name": "Paperclips", "TotalCost": 99.95, "Quantity": 1}' ``` @@ -111,7 +111,7 @@ If successful, you should see a response like the following: Send an HTTP request to get the status of the workflow that was started: ```bash -curl -i -X GET http://localhost:3500/v1.0-alpha1/workflows/dapr/12345678 +curl -i -X GET http://localhost:3500/v1.0-beta1/workflows/dapr/12345678 ``` The workflow is designed to take several seconds to complete. If the workflow hasn't completed when you issue the HTTP request, you'll see the following JSON response (formatted for readability) with workflow status as `RUNNING`: diff --git a/examples/Actor/ActorClient/ActorClient.csproj b/examples/Actor/ActorClient/ActorClient.csproj index 48c8318c6..0d1d94f55 100644 --- a/examples/Actor/ActorClient/ActorClient.csproj +++ b/examples/Actor/ActorClient/ActorClient.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net6 diff --git a/examples/Actor/ActorClient/Program.cs b/examples/Actor/ActorClient/Program.cs index 103aed6b9..5d7f06fb2 100644 --- a/examples/Actor/ActorClient/Program.cs +++ b/examples/Actor/ActorClient/Program.cs @@ -18,7 +18,6 @@ namespace ActorClient using System.Threading.Tasks; using Dapr.Actors; using Dapr.Actors.Client; - using Dapr.Actors.Communication; using IDemoActorInterface; /// @@ -69,7 +68,7 @@ public static async Task Main(string[] args) } catch (ActorMethodInvocationException ex) { - if (ex.InnerException is NotImplementedException) + if (ex.InnerException is ActorInvokeException invokeEx && invokeEx.ActualExceptionType is "System.NotImplementedException") { Console.WriteLine($"Got Correct Exception from actor method invocation."); } @@ -96,6 +95,10 @@ public static async Task Main(string[] args) 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."); @@ -105,14 +108,23 @@ public static async Task Main(string[] args) 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)); diff --git a/examples/Actor/DemoActor/DemoActor.cs b/examples/Actor/DemoActor/DemoActor.cs index 057b7df6d..62c100f79 100644 --- a/examples/Actor/DemoActor/DemoActor.cs +++ b/examples/Actor/DemoActor/DemoActor.cs @@ -85,6 +85,20 @@ public async Task RegisterReminderWithTtlAndRepetitions(TimeSpan ttl, int repeti await this.RegisterReminderAsync("TestReminder", null, TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(1), repetitions, ttl); } + public async Task GetReminder() + { + var reminder = await this.GetReminderAsync("TestReminder"); + + return reminder is not null + ? new ActorReminderData + { + Name = reminder.Name, + Period = reminder.Period, + DueTime = reminder.DueTime + } + : null; + } + public Task UnregisterReminder() { return this.UnregisterReminderAsync("TestReminder"); diff --git a/examples/Actor/DemoActor/DemoActor.csproj b/examples/Actor/DemoActor/DemoActor.csproj index 80a3883c3..1ee37fdbe 100644 --- a/examples/Actor/DemoActor/DemoActor.csproj +++ b/examples/Actor/DemoActor/DemoActor.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net6 diff --git a/examples/Actor/IDemoActor/IDemoActor.cs b/examples/Actor/IDemoActor/IDemoActor.cs index 3220dfdbd..c2926a048 100644 --- a/examples/Actor/IDemoActor/IDemoActor.cs +++ b/examples/Actor/IDemoActor/IDemoActor.cs @@ -16,6 +16,7 @@ namespace IDemoActorInterface using System; using System.Threading.Tasks; using Dapr.Actors; + using Dapr.Actors.Runtime; /// /// Interface for Actor method. @@ -94,6 +95,13 @@ public interface IDemoActor : IActor /// A task that represents the asynchronous save operation. Task RegisterReminderWithTtlAndRepetitions(TimeSpan ttl, int repetitions); + /// + /// Gets the registered reminder. + /// + /// The name of the reminder. + /// A task that returns the reminder after completion. + Task GetReminder(); + /// /// Unregisters the registered timer. /// @@ -124,4 +132,18 @@ public override string ToString() 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}"; + } + } } diff --git a/examples/Actor/IDemoActor/IDemoActor.csproj b/examples/Actor/IDemoActor/IDemoActor.csproj index 23a96ffc6..9f7744796 100644 --- a/examples/Actor/IDemoActor/IDemoActor.csproj +++ b/examples/Actor/IDemoActor/IDemoActor.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net6 diff --git a/examples/AspNetCore/ControllerSample/ControllerSample.csproj b/examples/AspNetCore/ControllerSample/ControllerSample.csproj index 863290324..6dbe750a6 100644 --- a/examples/AspNetCore/ControllerSample/ControllerSample.csproj +++ b/examples/AspNetCore/ControllerSample/ControllerSample.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net6 diff --git a/examples/AspNetCore/ControllerSample/README.md b/examples/AspNetCore/ControllerSample/README.md index a2700e94d..3b2ca02b9 100644 --- a/examples/AspNetCore/ControllerSample/README.md +++ b/examples/AspNetCore/ControllerSample/README.md @@ -12,7 +12,7 @@ The application also registers for pub/sub with the `deposit`, `multideposit` an ## Prerequisitess -- [.NET Core 3.1 or .NET 5+](https://dotnet.microsoft.com/download) installed +- [.NET 6+](https://dotnet.microsoft.com/download) installed - [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) - [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/) - [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/) diff --git a/examples/AspNetCore/GrpcServiceSample/GrpcServiceSample.csproj b/examples/AspNetCore/GrpcServiceSample/GrpcServiceSample.csproj index 23ead7d4d..123763489 100644 --- a/examples/AspNetCore/GrpcServiceSample/GrpcServiceSample.csproj +++ b/examples/AspNetCore/GrpcServiceSample/GrpcServiceSample.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6 true @@ -12,7 +12,7 @@ - + diff --git a/examples/AspNetCore/GrpcServiceSample/README.md b/examples/AspNetCore/GrpcServiceSample/README.md index 3f27d280c..d08e96cd9 100644 --- a/examples/AspNetCore/GrpcServiceSample/README.md +++ b/examples/AspNetCore/GrpcServiceSample/README.md @@ -11,7 +11,7 @@ The application also registers for pub/sub with the `deposit` and `withdraw` top ## Prerequisitess -- [.NET Core 3.1 or .NET 5+](https://dotnet.microsoft.com/download) installed +- [.NET 6+](https://dotnet.microsoft.com/download) installed - [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) - [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/) - [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/) diff --git a/examples/AspNetCore/RoutingSample/README.md b/examples/AspNetCore/RoutingSample/README.md index cd545b747..901c51c40 100644 --- a/examples/AspNetCore/RoutingSample/README.md +++ b/examples/AspNetCore/RoutingSample/README.md @@ -12,7 +12,7 @@ The application also registers for pub/sub with the `deposit`, `multideposit`, a ## Prerequisites -- [.NET Core 3.1 or .NET 5+](https://dotnet.microsoft.com/download) installed +- [.NET 6+](https://dotnet.microsoft.com/download) installed - [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) - [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/) - [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/) diff --git a/examples/AspNetCore/SecretStoreConfigurationProviderSample/README.md b/examples/AspNetCore/SecretStoreConfigurationProviderSample/README.md index 5be9eb5e1..09422e474 100644 --- a/examples/AspNetCore/SecretStoreConfigurationProviderSample/README.md +++ b/examples/AspNetCore/SecretStoreConfigurationProviderSample/README.md @@ -2,7 +2,7 @@ ## Prerequisites -- [.NET Core 3.1 or .NET 5+](https://dotnet.microsoft.com/download) installed +- [.NET 6+](https://dotnet.microsoft.com/download) installed - [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) - [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/) - [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/) diff --git a/examples/AspNetCore/SecretStoreConfigurationProviderSample/SecretStoreConfigurationProviderSample.csproj b/examples/AspNetCore/SecretStoreConfigurationProviderSample/SecretStoreConfigurationProviderSample.csproj index 10928470b..01fbc2079 100644 --- a/examples/AspNetCore/SecretStoreConfigurationProviderSample/SecretStoreConfigurationProviderSample.csproj +++ b/examples/AspNetCore/SecretStoreConfigurationProviderSample/SecretStoreConfigurationProviderSample.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net6 diff --git a/examples/Client/ConfigurationApi/ConfigurationApi.csproj b/examples/Client/ConfigurationApi/ConfigurationApi.csproj index f73fd4974..dee6a9878 100644 --- a/examples/Client/ConfigurationApi/ConfigurationApi.csproj +++ b/examples/Client/ConfigurationApi/ConfigurationApi.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6 diff --git a/examples/Client/ConfigurationApi/README.md b/examples/Client/ConfigurationApi/README.md index 41c1c831f..7425a780a 100644 --- a/examples/Client/ConfigurationApi/README.md +++ b/examples/Client/ConfigurationApi/README.md @@ -9,7 +9,7 @@ It demonstrates the following APIs: ## Prerequisites -- [.NET Core 3.1 or .NET 5+](https://dotnet.microsoft.com/download) installed +- [.NET 6+](https://dotnet.microsoft.com/download) installed - [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) - [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/) - [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/) diff --git a/examples/Client/DistributedLock/DistributedLock.csproj b/examples/Client/DistributedLock/DistributedLock.csproj index 180c46f28..9c3272e6e 100644 --- a/examples/Client/DistributedLock/DistributedLock.csproj +++ b/examples/Client/DistributedLock/DistributedLock.csproj @@ -6,7 +6,7 @@ - netcoreapp3.1 + net6 diff --git a/examples/Client/DistributedLock/README.md b/examples/Client/DistributedLock/README.md index 788d57e3a..cdac6f91a 100644 --- a/examples/Client/DistributedLock/README.md +++ b/examples/Client/DistributedLock/README.md @@ -2,7 +2,7 @@ ## Prerequisites -- [.NET Core 3.1 or .NET 5+](https://dotnet.microsoft.com/download) installed +- [.NET 6+](https://dotnet.microsoft.com/download) installed - [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) - [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/) - [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/) diff --git a/examples/Client/PublishSubscribe/BulkPublishEventExample/BulkPublishEventExample.csproj b/examples/Client/PublishSubscribe/BulkPublishEventExample/BulkPublishEventExample.csproj index 5c05faf9f..3f22acaf8 100644 --- a/examples/Client/PublishSubscribe/BulkPublishEventExample/BulkPublishEventExample.csproj +++ b/examples/Client/PublishSubscribe/BulkPublishEventExample/BulkPublishEventExample.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net6 Samples.Client enable diff --git a/examples/Client/PublishSubscribe/PublishEventExample/PublishEventExample.csproj b/examples/Client/PublishSubscribe/PublishEventExample/PublishEventExample.csproj index f7d335879..2df4ec967 100644 --- a/examples/Client/PublishSubscribe/PublishEventExample/PublishEventExample.csproj +++ b/examples/Client/PublishSubscribe/PublishEventExample/PublishEventExample.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net6 Samples.Client enable diff --git a/examples/Client/ServiceInvocation/README.md b/examples/Client/ServiceInvocation/README.md index 171869c15..ede5a506a 100644 --- a/examples/Client/ServiceInvocation/README.md +++ b/examples/Client/ServiceInvocation/README.md @@ -2,7 +2,7 @@ ## Prerequisites -- [.NET Core 3.1 or .NET 5+](https://dotnet.microsoft.com/download) installed +- [.NET 6+](https://dotnet.microsoft.com/download) installed - [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) - [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/) - [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/) diff --git a/examples/Client/ServiceInvocation/ServiceInvocation.csproj b/examples/Client/ServiceInvocation/ServiceInvocation.csproj index 790bfc53a..e3df962a1 100644 --- a/examples/Client/ServiceInvocation/ServiceInvocation.csproj +++ b/examples/Client/ServiceInvocation/ServiceInvocation.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net6 Samples.Client enable diff --git a/examples/Client/StateManagement/README.md b/examples/Client/StateManagement/README.md index 141f16760..fb266a242 100644 --- a/examples/Client/StateManagement/README.md +++ b/examples/Client/StateManagement/README.md @@ -2,7 +2,7 @@ ## Prerequisites -- [.NET Core 3.1 or .NET 5+](https://dotnet.microsoft.com/download) installed +- [.NET 6+](https://dotnet.microsoft.com/download) installed - [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) - [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/) - [Dapr .NET SDK](https://docs.dapr.io/developing-applications/sdks/dotnet/) diff --git a/examples/Client/StateManagement/StateManagement.csproj b/examples/Client/StateManagement/StateManagement.csproj index 790bfc53a..e3df962a1 100644 --- a/examples/Client/StateManagement/StateManagement.csproj +++ b/examples/Client/StateManagement/StateManagement.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net6 Samples.Client enable diff --git a/examples/Workflow/README.md b/examples/Workflow/README.md index b93af1bb5..1af855767 100644 --- a/examples/Workflow/README.md +++ b/examples/Workflow/README.md @@ -9,12 +9,24 @@ This Dapr workflow example shows how to create a Dapr workflow (`Workflow`) and - [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/) - [Dapr .NET SDK](https://github.com/dapr/dotnet-sdk/) + +## Optional Setup +Dapr workflow, as well as this example program, now support authentication through the use of API tokens. For more information on this, view the following document: [API Token](https://github.com/dapr/dotnet-sdk/blob/master/docs/api-tokens.md) + ## Projects in sample This sample contains a single [WorkflowConsoleApp](./WorkflowConsoleApp) .NET project. -It utilizes the workflow SDK as well as the workflow management API for starting and querying workflows instances. -The main `Program.cs` file contains the main setup of the app, including the registration of the workflow and workflow activities. -The workflow definition is found in the `Workflows` directory and the workflow activity definitions are found in the `Activities` directory. +It utilizes the workflow SDK as well as the workflow management API for simulating inventory management and sale of goods in a store. +The main `Program.cs` file contains the main setup of the app, the registration of the workflow and its activities, and interaction with the user. The workflow definition is found in the `Workflows` directory and the workflow activity definitions are found in the `Activities` directory. + +There are five activities in the directory that could be called by the workflows: +- `NotifyActivity`: printing logs as notifications +- `ProcessPaymentActivity`: printing logs and delaying for simulating payment processing +- `RequestApprovalActivity`: printing logs to indicate that the order has been approved +- `ReserveInventoryActivity`: checking if there are enough items for purchase +- `UpdateInventoryActivity`: updating the statestore according to purchasing + +The `OrderProcessingWorkflow.cs` in `Workflows` directory implements the running logic of the workflow. Based on the purchase stage and outcome, it calls different activities and waits for the corresponding events to trigger interaction with the user. This sample also contains a [WorkflowUnitTest](./WorkflowUnitTest) .NET project that utilizes [xUnit](https://xunit.net/) and [Moq](https://github.com/moq/moq) to test the workflow logic. It works by creating an instance of the `OrderProcessingWorkflow` (defined in the `WorkflowConsoleApp` project), mocking activity calls, and testing the inputs and outputs. @@ -49,7 +61,7 @@ For the workflow API option, two identical `curl` commands are shown, one for Li Make note of the "1234" in the commands below. This represents the unique identifier for the workflow run and can be replaced with any identifier of your choosing. ```bash -curl -i -X POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=1234 \ +curl -i -X POST http://localhost:3500/v1.0-beta1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=1234 \ -H "Content-Type: application/json" \ -d '{"Name": "Paperclips", "TotalCost": 99.95, "Quantity": 1}' ``` @@ -57,7 +69,7 @@ curl -i -X POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessing On Windows (PowerShell): ```powershell -curl -i -X POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=1234 ` +curl -i -X POST http://localhost:3500/v1.0-beta1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=1234 ` -H "Content-Type: application/json" ` -d '{"Name": "Paperclips", "TotalCost": 99.95, "Quantity": 1}' ``` @@ -71,7 +83,7 @@ If successful, you should see a response like the following: Next, send an HTTP request to get the status of the workflow that was started: ```bash -curl -i -X GET http://localhost:3500/v1.0-alpha1/workflows/dapr/1234 +curl -i -X GET http://localhost:3500/v1.0-beta1/workflows/dapr/1234 ``` The workflow is designed to take several seconds to complete. If the workflow hasn't completed yet when you issue the previous command, you should see the following JSON response (formatted for readability): @@ -120,4 +132,4 @@ info: WorkflowConsoleApp.Activities.NotifyActivity[0] Order 1234 processed successfully! ``` -If you have Zipkin configured for Dapr locally on your machine, then you can view the workflow trace spans in the Zipkin web UI (typically at http://localhost:9411/zipkin/). +If you have Zipkin configured for Dapr locally on your machine, then you can view the workflow trace spans in the Zipkin web UI (typically at http://localhost:9411/zipkin/). \ No newline at end of file diff --git a/examples/Workflow/WorkflowConsoleApp/Program.cs b/examples/Workflow/WorkflowConsoleApp/Program.cs index a1189e70b..055b1b4c1 100644 --- a/examples/Workflow/WorkflowConsoleApp/Program.cs +++ b/examples/Workflow/WorkflowConsoleApp/Program.cs @@ -47,7 +47,16 @@ using var host = builder.Build(); host.Start(); -using var daprClient = new DaprClientBuilder().Build(); +DaprClient daprClient; +string apiToken = Environment.GetEnvironmentVariable("DAPR_API_TOKEN"); +if (!string.IsNullOrEmpty(apiToken)) +{ + daprClient = new DaprClientBuilder().UseDaprApiToken(apiToken).Build(); +} +else +{ + daprClient = new DaprClientBuilder().Build(); +} // Wait for the sidecar to become available while (!await daprClient.CheckHealthAsync()) @@ -70,136 +79,145 @@ await RestockInventory(daprClient, baseInventory); // Start the input loop -while (true) +using (daprClient) { - // Get the name of the item to order and make sure we have inventory - string items = string.Join(", ", baseInventory.Select(i => i.Name)); - Console.WriteLine($"Enter the name of one of the following items to order [{items}]."); - Console.WriteLine("To restock items, type 'restock'."); - string itemName = Console.ReadLine()?.Trim(); - if (string.IsNullOrEmpty(itemName)) - { - continue; - } - else if (string.Equals("restock", itemName, StringComparison.OrdinalIgnoreCase)) + bool quit = false; + Console.CancelKeyPress += delegate { - await RestockInventory(daprClient, baseInventory); - continue; - } + quit = true; + Console.WriteLine("Shutting down the example."); + }; - InventoryItem item = baseInventory.FirstOrDefault(item => string.Equals(item.Name, itemName, StringComparison.OrdinalIgnoreCase)); - if (item == null) + while (!quit) { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine($"We don't have {itemName}!"); - Console.ResetColor(); - continue; - } + // Get the name of the item to order and make sure we have inventory + string items = string.Join(", ", baseInventory.Select(i => i.Name)); + Console.WriteLine($"Enter the name of one of the following items to order [{items}]."); + Console.WriteLine("To restock items, type 'restock'."); + string itemName = Console.ReadLine()?.Trim(); + if (string.IsNullOrEmpty(itemName)) + { + continue; + } + else if (string.Equals("restock", itemName, StringComparison.OrdinalIgnoreCase)) + { + await RestockInventory(daprClient, baseInventory); + continue; + } - Console.WriteLine($"How many {itemName} would you like to purchase?"); - string amountStr = Console.ReadLine().Trim(); - if (!int.TryParse(amountStr, out int amount) || amount <= 0) - { - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine($"Invalid input. Assuming you meant to type '1'."); - Console.ResetColor(); - amount = 1; - } + InventoryItem item = baseInventory.FirstOrDefault(item => string.Equals(item.Name, itemName, StringComparison.OrdinalIgnoreCase)); + if (item == null) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"We don't have {itemName}!"); + Console.ResetColor(); + continue; + } - // Construct the order with a unique order ID - string orderId = $"{itemName.ToLowerInvariant()}-{Guid.NewGuid().ToString()[..8]}"; - double totalCost = amount * item.PerItemCost; - var orderInfo = new OrderPayload(itemName.ToLowerInvariant(), totalCost, amount); + Console.WriteLine($"How many {itemName} would you like to purchase?"); + string amountStr = Console.ReadLine().Trim(); + if (!int.TryParse(amountStr, out int amount) || amount <= 0) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"Invalid input. Assuming you meant to type '1'."); + Console.ResetColor(); + amount = 1; + } - // Start the workflow using the order ID as the workflow ID - Console.WriteLine($"Starting order workflow '{orderId}' purchasing {amount} {itemName}"); - await daprClient.StartWorkflowAsync( - workflowComponent: DaprWorkflowComponent, - workflowName: nameof(OrderProcessingWorkflow), - input: orderInfo, - instanceId: orderId); + // Construct the order with a unique order ID + string orderId = $"{itemName.ToLowerInvariant()}-{Guid.NewGuid().ToString()[..8]}"; + double totalCost = amount * item.PerItemCost; + var orderInfo = new OrderPayload(itemName.ToLowerInvariant(), totalCost, amount); - // Wait for the workflow to start and confirm the input - GetWorkflowResponse state = await daprClient.WaitForWorkflowStartAsync( - instanceId: orderId, - workflowComponent: DaprWorkflowComponent); + // Start the workflow using the order ID as the workflow ID + Console.WriteLine($"Starting order workflow '{orderId}' purchasing {amount} {itemName}"); + await daprClient.StartWorkflowAsync( + workflowComponent: DaprWorkflowComponent, + workflowName: nameof(OrderProcessingWorkflow), + input: orderInfo, + instanceId: orderId); - Console.WriteLine($"{state.WorkflowName} (ID = {orderId}) started successfully with {state.ReadInputAs()}"); + // Wait for the workflow to start and confirm the input + GetWorkflowResponse state = await daprClient.WaitForWorkflowStartAsync( + instanceId: orderId, + workflowComponent: DaprWorkflowComponent); - // Wait for the workflow to complete - while (true) - { - using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - try - { - state = await daprClient.WaitForWorkflowCompletionAsync( - instanceId: orderId, - workflowComponent: DaprWorkflowComponent, - cancellationToken: cts.Token); - break; - } - catch (OperationCanceledException) + Console.WriteLine($"{state.WorkflowName} (ID = {orderId}) started successfully with {state.ReadInputAs()}"); + + // Wait for the workflow to complete + while (true) { - // Check to see if the workflow is blocked waiting for an approval - state = await daprClient.GetWorkflowAsync( - instanceId: orderId, - workflowComponent: DaprWorkflowComponent); - if (state.Properties.TryGetValue("dapr.workflow.custom_status", out string customStatus) && - customStatus.Contains("Waiting for approval")) + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + try { - Console.WriteLine($"{state.WorkflowName} (ID = {orderId}) requires approval. Approve? [Y/N]"); - string approval = Console.ReadLine(); - ApprovalResult approvalResult = ApprovalResult.Unspecified; - if (string.Equals(approval, "Y", StringComparison.OrdinalIgnoreCase)) - { - Console.WriteLine("Approving order..."); - approvalResult = ApprovalResult.Approved; - } - else if (string.Equals(approval, "N", StringComparison.OrdinalIgnoreCase)) - { - Console.WriteLine("Rejecting order..."); - approvalResult = ApprovalResult.Rejected; - } - - if (approvalResult != ApprovalResult.Unspecified) + state = await daprClient.WaitForWorkflowCompletionAsync( + instanceId: orderId, + workflowComponent: DaprWorkflowComponent, + cancellationToken: cts.Token); + break; + } + catch (OperationCanceledException) + { + // Check to see if the workflow is blocked waiting for an approval + state = await daprClient.GetWorkflowAsync( + instanceId: orderId, + workflowComponent: DaprWorkflowComponent); + if (state.Properties.TryGetValue("dapr.workflow.custom_status", out string customStatus) && + customStatus.Contains("Waiting for approval")) { - // Raise the workflow event to the workflow - await daprClient.RaiseWorkflowEventAsync( - instanceId: orderId, - workflowComponent: DaprWorkflowComponent, - eventName: "ManagerApproval", - eventData: approvalResult); + Console.WriteLine($"{state.WorkflowName} (ID = {orderId}) requires approval. Approve? [Y/N]"); + string approval = Console.ReadLine(); + ApprovalResult approvalResult = ApprovalResult.Unspecified; + if (string.Equals(approval, "Y", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine("Approving order..."); + approvalResult = ApprovalResult.Approved; + } + else if (string.Equals(approval, "N", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine("Rejecting order..."); + approvalResult = ApprovalResult.Rejected; + } + + if (approvalResult != ApprovalResult.Unspecified) + { + // Raise the workflow event to the workflow + await daprClient.RaiseWorkflowEventAsync( + instanceId: orderId, + workflowComponent: DaprWorkflowComponent, + eventName: "ManagerApproval", + eventData: approvalResult); + } + + // otherwise, keep waiting } - - // otherwise, keep waiting } } - } - if (state.RuntimeStatus == WorkflowRuntimeStatus.Completed) - { - OrderResult result = state.ReadOutputAs(); - if (result.Processed) + if (state.RuntimeStatus == WorkflowRuntimeStatus.Completed) { - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine($"Order workflow is {state.RuntimeStatus} and the order was processed successfully ({result})."); - Console.ResetColor(); + OrderResult result = state.ReadOutputAs(); + if (result.Processed) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($"Order workflow is {state.RuntimeStatus} and the order was processed successfully ({result})."); + Console.ResetColor(); + } + else + { + Console.WriteLine($"Order workflow is {state.RuntimeStatus} but the order was not processed."); + } } - else + else if (state.RuntimeStatus == WorkflowRuntimeStatus.Failed) { - Console.WriteLine($"Order workflow is {state.RuntimeStatus} but the order was not processed."); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"The workflow failed - {state.FailureDetails}"); + Console.ResetColor(); } - } - else if (state.RuntimeStatus == WorkflowRuntimeStatus.Failed) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"The workflow failed - {state.FailureDetails}"); - Console.ResetColor(); - } - Console.WriteLine(); + Console.WriteLine(); + } } - static async Task RestockInventory(DaprClient daprClient, List inventory) { Console.WriteLine("*** Restocking inventory..."); diff --git a/examples/Workflow/WorkflowConsoleApp/WorkflowConsoleApp.csproj b/examples/Workflow/WorkflowConsoleApp/WorkflowConsoleApp.csproj index 0c40eea0c..25c03a419 100644 --- a/examples/Workflow/WorkflowConsoleApp/WorkflowConsoleApp.csproj +++ b/examples/Workflow/WorkflowConsoleApp/WorkflowConsoleApp.csproj @@ -8,7 +8,6 @@ Exe net6 enable - latest 612,618 diff --git a/examples/Workflow/WorkflowConsoleApp/demo.http b/examples/Workflow/WorkflowConsoleApp/demo.http index bb91e5045..669cefeb7 100644 --- a/examples/Workflow/WorkflowConsoleApp/demo.http +++ b/examples/Workflow/WorkflowConsoleApp/demo.http @@ -1,17 +1,17 @@ ### Start order processing workflow - replace xxx with any id you like -POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/xxx/start +POST http://localhost:3500/v1.0-beta1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=xxx Content-Type: application/json -{ "input" : {"Name": "Paperclips", "TotalCost": 99.95, "Quantity": 1}} +{"Name": "Paperclips", "TotalCost": 99.95, "Quantity": 1} ### Start order processing workflow - replace xxx with any id you like -POST http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/xxx/start +POST http://localhost:3500/v1.0-beta1/workflows/dapr/OrderProcessingWorkflow/start?instanceID=xxx Content-Type: application/json -{ "input" : {"Name": "Cars", "TotalCost": 10000, "Quantity": 30}} +{"Name": "Cars", "TotalCost": 10000, "Quantity": 30} ### Query dapr sidecar - replace xxx with id from the workflow you've created above -GET http://localhost:3500/v1.0-alpha1/workflows/dapr/OrderProcessingWorkflow/xxx +GET http://localhost:3500/v1.0-beta1/workflows/dapr/xxx ### Terminate the workflow - replace xxx with id from the workflow you've created above -POST http://localhost:3500/v1.0-alpha1/workflows/dapr/xxx/terminate \ No newline at end of file +POST http://localhost:3500/v1.0-beta1/workflows/dapr/xxx/terminate \ No newline at end of file diff --git a/examples/Workflow/WorkflowUnitTest/WorkflowUnitTest.csproj b/examples/Workflow/WorkflowUnitTest/WorkflowUnitTest.csproj index 9c4a74a17..4ce0c9801 100644 --- a/examples/Workflow/WorkflowUnitTest/WorkflowUnitTest.csproj +++ b/examples/Workflow/WorkflowUnitTest/WorkflowUnitTest.csproj @@ -3,7 +3,6 @@ net6.0 enable - 10 false diff --git a/properties/IsExternalInit.cs b/properties/IsExternalInit.cs index 2f68a08f4..34357c39a 100644 --- a/properties/IsExternalInit.cs +++ b/properties/IsExternalInit.cs @@ -13,6 +13,5 @@ namespace System.Runtime.CompilerServices internal static class IsExternalInit { } - - // This is a polyfill for init only properties in netcoreapp3.1 + } diff --git a/properties/dapr_managed_netcore.props b/properties/dapr_managed_netcore.props index 59bb68c9a..3bafcb50c 100644 --- a/properties/dapr_managed_netcore.props +++ b/properties/dapr_managed_netcore.props @@ -3,7 +3,7 @@ Debug - 9.0 + 10.0 true 4 false diff --git a/src/Dapr.Actors.AspNetCore/ActorsEndpointRouteBuilderExtensions.cs b/src/Dapr.Actors.AspNetCore/ActorsEndpointRouteBuilderExtensions.cs index e7937bbbf..55d161d9a 100644 --- a/src/Dapr.Actors.AspNetCore/ActorsEndpointRouteBuilderExtensions.cs +++ b/src/Dapr.Actors.AspNetCore/ActorsEndpointRouteBuilderExtensions.cs @@ -109,7 +109,7 @@ private static IEndpointConventionBuilder MapActorMethodEndpoint(this IEndpointR if (header != string.Empty) { // exception case - context.Response.Headers.Add(Constants.ErrorResponseHeaderName, header); // add error header + context.Response.Headers[Constants.ErrorResponseHeaderName] = header; // add error header } await context.Response.Body.WriteAsync(body, 0, body.Length); // add response message body @@ -118,7 +118,7 @@ private static IEndpointConventionBuilder MapActorMethodEndpoint(this IEndpointR { var (header, body) = CreateExceptionResponseMessage(ex); - context.Response.Headers.Add(Constants.ErrorResponseHeaderName, header); + context.Response.Headers[Constants.ErrorResponseHeaderName] = header; await context.Response.Body.WriteAsync(body, 0, body.Length); } finally diff --git a/src/Dapr.Actors.AspNetCore/Dapr.Actors.AspNetCore.csproj b/src/Dapr.Actors.AspNetCore/Dapr.Actors.AspNetCore.csproj index a9f6e8818..1114b7828 100644 --- a/src/Dapr.Actors.AspNetCore/Dapr.Actors.AspNetCore.csproj +++ b/src/Dapr.Actors.AspNetCore/Dapr.Actors.AspNetCore.csproj @@ -1,7 +1,4 @@  - - netcoreapp3.1 - This package contains the reference assemblies for developing Actor services using Dapr. diff --git a/src/Dapr.Actors/DaprHttpInteractor.cs b/src/Dapr.Actors/DaprHttpInteractor.cs index df5207f4e..4695375fb 100644 --- a/src/Dapr.Actors/DaprHttpInteractor.cs +++ b/src/Dapr.Actors/DaprHttpInteractor.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ // Copyright 2021 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -70,7 +70,7 @@ HttpRequestMessage RequestFunc() return request; } - var response = await this.SendAsync(RequestFunc, relativeUrl, cancellationToken); + using var response = await this.SendAsync(RequestFunc, relativeUrl, cancellationToken); var stringResponse = await response.Content.ReadAsStringAsync(); return stringResponse; } @@ -164,11 +164,11 @@ HttpRequestMessage RequestFunc() // actorResponseMessageHeader is not null, it means there is remote exception if (actorResponseMessageHeader != null) { - var isDeserialzied = + var isDeserialized = ActorInvokeException.ToException( responseMessageBody, out var remoteMethodException); - if (isDeserialzied) + if (isDeserialized) { var exceptionDetails = GetExceptionDetails(header.ToString()); throw new ActorMethodInvocationException( @@ -185,7 +185,7 @@ HttpRequestMessage RequestFunc() } } - actorResponseMessageBody = responseBodySerializer.Deserialize(responseMessageBody); + actorResponseMessageBody = await responseBodySerializer.DeserializeAsync(responseMessageBody); } return new ActorResponseMessage(actorResponseMessageHeader, actorResponseMessageBody); @@ -231,8 +231,8 @@ HttpRequestMessage RequestFunc() } var response = await this.SendAsync(RequestFunc, relativeUrl, cancellationToken); - var byteArray = await response.Content.ReadAsStreamAsync(); - return byteArray; + var stream = await response.Content.ReadAsStreamAsync(); + return stream; } public Task RegisterReminderAsync(string actorType, string actorId, string reminderName, string data, CancellationToken cancellationToken = default) @@ -254,6 +254,23 @@ HttpRequestMessage RequestFunc() return this.SendAsync(RequestFunc, relativeUrl, cancellationToken); } + public async Task GetReminderAsync(string actorType, string actorId, string reminderName, CancellationToken cancellationToken = default) + { + var relativeUrl = string.Format(CultureInfo.InvariantCulture, Constants.ActorReminderRelativeUrlFormat, actorType, actorId, reminderName); + + HttpRequestMessage RequestFunc() + { + var request = new HttpRequestMessage() + { + Method = HttpMethod.Get, + }; + return request; + } + + var response = await this.SendAsync(RequestFunc, relativeUrl, cancellationToken); + return await response.Content.ReadAsStreamAsync(); + } + public Task UnregisterReminderAsync(string actorType, string actorId, string reminderName, CancellationToken cancellationToken = default) { var relativeUrl = string.Format(CultureInfo.InvariantCulture, Constants.ActorReminderRelativeUrlFormat, actorType, actorId, reminderName); @@ -334,7 +351,7 @@ internal async Task SendAsyncGetResponseAsRawJson( string relativeUri, CancellationToken cancellationToken) { - var response = await this.SendAsyncHandleUnsuccessfulResponse(requestFunc, relativeUri, cancellationToken); + using var response = await this.SendAsyncHandleUnsuccessfulResponse(requestFunc, relativeUri, cancellationToken); var retValue = default(string); if (response != null && response.Content != null) @@ -441,7 +458,7 @@ private async Task SendAsyncHandleSecurityExceptions( HttpResponseMessage response; // Get the request using the Func as same request cannot be resent when retries are implemented. - var request = requestFunc.Invoke(); + using var request = requestFunc.Invoke(); // add token for dapr api token based authentication this.AddDaprApiTokenHeader(request); diff --git a/src/Dapr.Actors/IDaprInteractor.cs b/src/Dapr.Actors/IDaprInteractor.cs index 04eb66de9..8f30aa18f 100644 --- a/src/Dapr.Actors/IDaprInteractor.cs +++ b/src/Dapr.Actors/IDaprInteractor.cs @@ -74,6 +74,16 @@ internal interface IDaprInteractor /// A representing the result of the asynchronous operation. Task RegisterReminderAsync(string actorType, string actorId, string reminderName, string data, CancellationToken cancellationToken = default); + /// + /// Gets a reminder. + /// + /// Type of actor. + /// ActorId. + /// Name of reminder to unregister. + /// Cancels the operation. + /// A representing the result of the asynchronous operation. + Task GetReminderAsync(string actorType, string actorId, string reminderName, CancellationToken cancellationToken = default); + /// /// Unregisters a reminder. /// diff --git a/src/Dapr.Actors/Runtime/Actor.cs b/src/Dapr.Actors/Runtime/Actor.cs index 0f74513a1..ddfc266e9 100644 --- a/src/Dapr.Actors/Runtime/Actor.cs +++ b/src/Dapr.Actors/Runtime/Actor.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ // Copyright 2021 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -101,10 +101,11 @@ internal async Task OnPostActorMethodAsyncInternal(ActorMethodContext actorMetho await this.SaveStateAsync(); } - internal Task OnInvokeFailedAsync() + internal async Task OnActorMethodFailedInternalAsync(ActorMethodContext actorMethodContext, Exception e) { + await this.OnActorMethodFailedAsync(actorMethodContext, e); // Exception has been thrown by user code, reset the state in state manager. - return this.ResetStateAsync(); + await this.ResetStateAsync(); } internal Task ResetStateAsync() @@ -190,6 +191,30 @@ protected virtual Task OnPostActorMethodAsync(ActorMethodContext actorMethodCont return Task.CompletedTask; } + /// + /// Override this method for performing any action when invoking actor method has thrown an exception. + /// This method is invoked by actor runtime when invoking actor method has thrown an exception. + /// + /// + /// An describing the method that was invoked by actor runtime prior to this method. + /// + /// Exception thrown by either actor method or by OnPreActorMethodAsync/OnPostActorMethodAsync overriden methods. + /// + /// Returns a Task representing post-actor-method operation. + /// + /// /// + /// This method is invoked by actor runtime prior to: + /// + /// Invoking an actor interface method when a client request comes. + /// Invoking a method when a reminder fires. + /// Invoking a timer callback when timer fires. + /// + /// + protected virtual Task OnActorMethodFailedAsync(ActorMethodContext actorMethodContext, Exception e) + { + return Task.CompletedTask; + } + /// /// Registers a reminder with the actor. /// @@ -360,6 +385,18 @@ internal async Task RegisterReminderAsync(ActorReminderOptions o return reminder; } + /// + /// Gets a reminder previously registered using . + /// + /// The name of the reminder to get. + /// + /// Returns a task that represents the asynchronous get operation. The result of the task contains the reminder if it exists, otherwise null. + /// + protected async Task GetReminderAsync(string reminderName) + { + return await this.Host.TimerManager.GetReminderAsync(new ActorReminderToken(this.actorTypeName, this.Id, reminderName)); + } + /// /// Unregisters a reminder previously registered using . /// diff --git a/src/Dapr.Actors/Runtime/ActorManager.cs b/src/Dapr.Actors/Runtime/ActorManager.cs index e7f29f5cc..b7ee3bf3e 100644 --- a/src/Dapr.Actors/Runtime/ActorManager.cs +++ b/src/Dapr.Actors/Runtime/ActorManager.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ // Copyright 2021 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -56,7 +56,8 @@ internal sealed class ActorManager internal ActorManager( ActorRegistration registration, ActorActivator activator, - JsonSerializerOptions jsonSerializerOptions, + JsonSerializerOptions jsonSerializerOptions, + bool useJsonSerialization, ILoggerFactory loggerFactory, IActorProxyFactory proxyFactory, IDaprInteractor daprInteractor) @@ -78,7 +79,15 @@ internal ActorManager( this.activeActors = new ConcurrentDictionary(); this.reminderMethodContext = ActorMethodContext.CreateForReminder(ReceiveReminderMethodName); this.timerMethodContext = ActorMethodContext.CreateForTimer(TimerMethodName); - this.serializersManager = IntializeSerializationManager(null); + + // provide a serializer if 'useJsonSerialization' is true and no serialization provider is provided. + IActorMessageBodySerializationProvider serializationProvider = null; + if (useJsonSerialization) + { + serializationProvider = new ActorMessageBodyJsonSerializationProvider(jsonSerializerOptions); + } + + this.serializersManager = IntializeSerializationManager(serializationProvider); this.messageBodyFactory = new WrappedRequestMessageFactory(); this.logger = loggerFactory.CreateLogger(this.GetType()); @@ -103,7 +112,7 @@ internal async Task> DispatchWithRemotingAsync(ActorId act using (var stream = new MemoryStream()) { await data.CopyToAsync(stream); - actorMessageBody = msgBodySerializer.Deserialize(stream); + actorMessageBody = await msgBodySerializer.DeserializeAsync(stream); } // Call the method on the method dispatcher using the Func below. @@ -355,9 +364,9 @@ private async Task DispatchInternalAsync(ActorId actorId, ActorMethodConte // PostActivate will save the state, its not invoked when actorFunc invocation throws. await actor.OnPostActorMethodAsyncInternal(actorMethodContext); } - catch (Exception) + catch (Exception e) { - await actor.OnInvokeFailedAsync(); + await actor.OnActorMethodFailedInternalAsync(actorMethodContext, e); throw; } finally diff --git a/src/Dapr.Actors/Runtime/ActorRuntime.cs b/src/Dapr.Actors/Runtime/ActorRuntime.cs index 8d2ae0cab..7f01fefb7 100644 --- a/src/Dapr.Actors/Runtime/ActorRuntime.cs +++ b/src/Dapr.Actors/Runtime/ActorRuntime.cs @@ -55,6 +55,7 @@ internal ActorRuntime(ActorRuntimeOptions options, ILoggerFactory loggerFactory, actor, actor.Activator ?? this.activatorFactory.CreateActivator(actor.Type), this.options.JsonSerializerOptions, + this.options.UseJsonSerialization, loggerFactory, proxyFactory, daprInteractor); diff --git a/src/Dapr.Actors/Runtime/ActorRuntimeOptions.cs b/src/Dapr.Actors/Runtime/ActorRuntimeOptions.cs index 3f4a6df88..62eaceea6 100644 --- a/src/Dapr.Actors/Runtime/ActorRuntimeOptions.cs +++ b/src/Dapr.Actors/Runtime/ActorRuntimeOptions.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ // Copyright 2021 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ public sealed class ActorRuntimeOptions { Enabled = false, }; + private bool useJsonSerialization = false; private JsonSerializerOptions jsonSerializerOptions = JsonSerializerDefaults.Web; private string daprApiToken = DaprDefaults.GetDefaultDaprApiToken(); private int? remindersStoragePartitions = null; @@ -151,7 +152,22 @@ public ActorReentrancyConfig ReentrancyConfig } } - + /// + /// Enable JSON serialization for actor proxy message serialization in both remoting and non-remoting invocations. + /// + public bool UseJsonSerialization + { + get + { + return this.useJsonSerialization; + } + + set + { + this.useJsonSerialization = value; + } + } + /// /// The to use for actor state persistence and message deserialization /// @@ -220,8 +236,9 @@ public int? RemindersStoragePartitions /// /// /// The URI endpoint to use for HTTP calls to the Dapr runtime. The default value will be - /// http://127.0.0.1:DAPR_HTTP_PORT where DAPR_HTTP_PORT represents the value of the - /// DAPR_HTTP_PORT environment variable. + /// DAPR_HTTP_ENDPOINT first, or http://127.0.0.1:DAPR_HTTP_PORT as fallback + /// where DAPR_HTTP_ENDPOINT and DAPR_HTTP_PORT represents the value of the + /// corresponding environment variables. /// /// public string HttpEndpoint { get; set; } = DaprDefaults.GetDefaultHttpEndpoint(); diff --git a/src/Dapr.Actors/Runtime/ActorTestOptions.cs b/src/Dapr.Actors/Runtime/ActorTestOptions.cs index 1a1fa6f2e..47e73ce36 100644 --- a/src/Dapr.Actors/Runtime/ActorTestOptions.cs +++ b/src/Dapr.Actors/Runtime/ActorTestOptions.cs @@ -91,6 +91,11 @@ public override Task RegisterTimerAsync(ActorTimer timer) throw new NotImplementedException(Message); } + public override Task GetReminderAsync(ActorReminderToken reminder) + { + throw new NotImplementedException(Message); + } + public override Task UnregisterReminderAsync(ActorReminderToken reminder) { throw new NotImplementedException(Message); diff --git a/src/Dapr.Actors/Runtime/ActorTimerManager.cs b/src/Dapr.Actors/Runtime/ActorTimerManager.cs index 1dc304fd1..784cf418e 100644 --- a/src/Dapr.Actors/Runtime/ActorTimerManager.cs +++ b/src/Dapr.Actors/Runtime/ActorTimerManager.cs @@ -27,6 +27,13 @@ public abstract class ActorTimerManager /// A task which will complete when the operation completes. public abstract Task RegisterReminderAsync(ActorReminder reminder); + /// + /// Gets a reminder previously registered using + /// + /// The to unregister. + /// A task which will complete when the operation completes. + public abstract Task GetReminderAsync(ActorReminderToken reminder); + /// /// Unregisters the provided reminder with the runtime. /// diff --git a/src/Dapr.Actors/Runtime/DataContractStateSerializer.cs b/src/Dapr.Actors/Runtime/DataContractStateSerializer.cs deleted file mode 100644 index e30b21d48..000000000 --- a/src/Dapr.Actors/Runtime/DataContractStateSerializer.cs +++ /dev/null @@ -1,82 +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. -// ------------------------------------------------------------------------ - -namespace Dapr.Actors.Runtime -{ - using System; - using System.Collections.Concurrent; - using System.IO; - using System.Runtime.Serialization; - using System.Xml; - - /// - /// DataContract serializer for Actor state serialization/deserialziation. - /// This is the default state serializer used with Service Fabric Reliable Actors. - /// If there is user ask for the compatibility, this can be exposed by adding a compatibility option as an attribute on Actor type so that Service Fabric Reliable Actors state serialization behavior - /// can also be used using Dapr. - /// - internal class DataContractStateSerializer : IActorStateSerializer - { - private readonly ConcurrentDictionary actorStateSerializerCache; - - internal DataContractStateSerializer() - { - this.actorStateSerializerCache = new ConcurrentDictionary(); - } - - public byte[] Serialize(Type stateType, T state) - { - var serializer = this.actorStateSerializerCache.GetOrAdd( - stateType, - CreateDataContractSerializer); - - using var stream = new MemoryStream(); - using var writer = XmlDictionaryWriter.CreateBinaryWriter(stream); - serializer.WriteObject(writer, state); - writer.Flush(); - return stream.ToArray(); - } - - public T Deserialize(byte[] buffer) - { - if ((buffer == null) || (buffer.Length == 0)) - { - return default; - } - - var serializer = this.actorStateSerializerCache.GetOrAdd( - typeof(T), - CreateDataContractSerializer); - - using var stream = new MemoryStream(buffer); - using var reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max); - return (T)serializer.ReadObject(reader); - } - - private static DataContractSerializer CreateDataContractSerializer(Type actorStateType) - { - var dataContractSerializer = new DataContractSerializer( - actorStateType, - new DataContractSerializerSettings - { - MaxItemsInObjectGraph = int.MaxValue, - KnownTypes = new[] - { - typeof(ActorReference), - }, - }); - - return dataContractSerializer; - } - } -} diff --git a/src/Dapr.Actors/Runtime/DefaultActorTimerManager.cs b/src/Dapr.Actors/Runtime/DefaultActorTimerManager.cs index d3378c962..b42b432a1 100644 --- a/src/Dapr.Actors/Runtime/DefaultActorTimerManager.cs +++ b/src/Dapr.Actors/Runtime/DefaultActorTimerManager.cs @@ -14,6 +14,8 @@ using System; using System.Text.Json; using System.Threading.Tasks; +using System.IO; +using System.Text; namespace Dapr.Actors.Runtime { @@ -37,6 +39,18 @@ public override async Task RegisterReminderAsync(ActorReminder reminder) await this.interactor.RegisterReminderAsync(reminder.ActorType, reminder.ActorId.ToString(), reminder.Name, serialized); } + public override async Task GetReminderAsync(ActorReminderToken token) + { + if (token == null) + { + throw new ArgumentNullException(nameof(token)); + } + + var responseStream = await this.interactor.GetReminderAsync(token.ActorType, token.ActorId.ToString(), token.Name); + var reminder = await DeserializeReminderAsync(responseStream, token); + return reminder; + } + public override async Task UnregisterReminderAsync(ActorReminderToken reminder) { if (reminder == null) @@ -77,5 +91,21 @@ private async ValueTask SerializeReminderAsync(ActorReminder reminder) reminder.Ttl); return await info.SerializeAsync(); } + + private async ValueTask DeserializeReminderAsync(Stream stream, ActorReminderToken token) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + var info = await ReminderInfo.DeserializeAsync(stream); + if(info == null) + { + return null; + } + var reminder = new ActorReminder(token.ActorType, token.ActorId, token.Name, info.Data, info.DueTime, + info.Period); + return reminder; + } } } diff --git a/src/Dapr.Actors/Runtime/ReminderInfo.cs b/src/Dapr.Actors/Runtime/ReminderInfo.cs index 84e56bbc7..447cf607b 100644 --- a/src/Dapr.Actors/Runtime/ReminderInfo.cs +++ b/src/Dapr.Actors/Runtime/ReminderInfo.cs @@ -20,7 +20,7 @@ namespace Dapr.Actors.Runtime using System.Threading.Tasks; // represents the wire format used by Dapr to store reminder info with the runtime - internal struct ReminderInfo + internal class ReminderInfo { public ReminderInfo( byte[] data, @@ -49,13 +49,16 @@ public ReminderInfo( internal static async Task DeserializeAsync(Stream stream) { var json = await JsonSerializer.DeserializeAsync(stream); + if(json.ValueKind == JsonValueKind.Null) + { + return null; + } var dueTime = default(TimeSpan); var period = default(TimeSpan); var data = default(byte[]); int? repetition = null; TimeSpan? ttl = null; - if (json.TryGetProperty("dueTime", out var dueTimeProperty)) { var dueTimeString = dueTimeProperty.GetString(); diff --git a/src/Dapr.AspNetCore/Dapr.AspNetCore.csproj b/src/Dapr.AspNetCore/Dapr.AspNetCore.csproj index c119b2d3e..c61fc5abc 100644 --- a/src/Dapr.AspNetCore/Dapr.AspNetCore.csproj +++ b/src/Dapr.AspNetCore/Dapr.AspNetCore.csproj @@ -1,9 +1,5 @@  - - netcoreapp3.1 - - This package contains the reference assemblies for developing services using Dapr and AspNetCore. diff --git a/src/Dapr.AspNetCore/DaprAuthenticationHandler.cs b/src/Dapr.AspNetCore/DaprAuthenticationHandler.cs index 822694c3b..dc21b5926 100644 --- a/src/Dapr.AspNetCore/DaprAuthenticationHandler.cs +++ b/src/Dapr.AspNetCore/DaprAuthenticationHandler.cs @@ -25,6 +25,14 @@ internal class DaprAuthenticationHandler : AuthenticationHandler options, + ILoggerFactory logger, + UrlEncoder encoder) : base(options, logger, encoder) + { + } +#else public DaprAuthenticationHandler( IOptionsMonitor options, ILoggerFactory logger, @@ -32,6 +40,7 @@ public DaprAuthenticationHandler( ISystemClock clock) : base(options, logger, encoder, clock) { } +#endif protected override Task HandleAuthenticateAsync() { diff --git a/src/Dapr.Client/Dapr.Client.csproj b/src/Dapr.Client/Dapr.Client.csproj index 7c9ff8160..a3fd5b082 100644 --- a/src/Dapr.Client/Dapr.Client.csproj +++ b/src/Dapr.Client/Dapr.Client.csproj @@ -1,9 +1,5 @@  - - netcoreapp3.1 - - @@ -21,7 +17,7 @@ - + diff --git a/src/Dapr.Client/DaprApiException.cs b/src/Dapr.Client/DaprApiException.cs index e7af8947c..75fc2cf7f 100644 --- a/src/Dapr.Client/DaprApiException.cs +++ b/src/Dapr.Client/DaprApiException.cs @@ -92,6 +92,9 @@ public DaprApiException(string message, Exception inner, string errorCode, bool /// /// The object that contains serialized object data of the exception being thrown. /// The object that contains contextual information about the source or destination. The context parameter is reserved for future use and can be null. +#if NET8_0_OR_GREATER + [Obsolete(DiagnosticId = "SYSLIB0051")] // add this attribute to the serialization ctor +#endif protected DaprApiException(SerializationInfo info, StreamingContext context) : base(info, context) { @@ -115,6 +118,9 @@ protected DaprApiException(SerializationInfo info, StreamingContext context) public bool IsTransient { get; } = false; /// +#if NET8_0_OR_GREATER + [Obsolete(DiagnosticId = "SYSLIB0051")] // add this attribute to GetObjectData +#endif public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); diff --git a/src/Dapr.Client/DaprClient.cs b/src/Dapr.Client/DaprClient.cs index 2a7299c78..361ac54bc 100644 --- a/src/Dapr.Client/DaprClient.cs +++ b/src/Dapr.Client/DaprClient.cs @@ -292,7 +292,7 @@ public abstract Task InvokeBindingAsync( /// /// Creates an that can be used to perform service invocation for the - /// application idenfied by and invokes the method specified by + /// application identified by and invokes the method specified by /// with the POST HTTP method. /// /// The Dapr application id to invoke the method on. @@ -305,7 +305,7 @@ public HttpRequestMessage CreateInvokeMethodRequest(string appId, string methodN /// /// Creates an that can be used to perform service invocation for the - /// application idenfied by and invokes the method specified by + /// application identified by and invokes the method specified by /// with the HTTP method specified by . /// /// The to use for the invocation request. @@ -316,7 +316,7 @@ public HttpRequestMessage CreateInvokeMethodRequest(string appId, string methodN /// /// Creates an that can be used to perform service invocation for the - /// application idenfied by and invokes the method specified by + /// application identified by and invokes the method specified by /// with the POST HTTP method and a JSON serialized request body specified by . /// /// The type of the data that will be JSON serialized and provided as the request body. @@ -331,7 +331,7 @@ public HttpRequestMessage CreateInvokeMethodRequest(string appId, stri /// /// Creates an that can be used to perform service invocation for the - /// application idenfied by and invokes the method specified by + /// application identified by and invokes the method specified by /// with the HTTP method specified by and a JSON serialized request body specified by /// . /// @@ -444,7 +444,7 @@ public HttpRequestMessage CreateInvokeMethodRequest(string appId, stri public abstract Task InvokeMethodAsync(HttpRequestMessage request, CancellationToken cancellationToken = default); /// - /// Perform service invocation for the application idenfied by and invokes the method + /// Perform service invocation for the application identified by and invokes the method /// specified by with the POST HTTP method and an empty request body. /// If the response has a non-success status code an exception will be thrown. /// @@ -462,7 +462,7 @@ public Task InvokeMethodAsync( } /// - /// Perform service invocation for the application idenfied by and invokes the method + /// Perform service invocation for the application identified by and invokes the method /// specified by with the HTTP method specified by /// and an empty request body. If the response has a non-success status code an exception will be thrown. /// @@ -482,7 +482,7 @@ public Task InvokeMethodAsync( } /// - /// Perform service invocation for the application idenfied by and invokes the method + /// Perform service invocation for the application identified by and invokes the method /// specified by with the POST HTTP method /// and a JSON serialized request body specified by . If the response has a non-success /// status code an exception will be thrown. @@ -504,7 +504,7 @@ public Task InvokeMethodAsync( } /// - /// Perform service invocation for the application idenfied by and invokes the method + /// Perform service invocation for the application identified by and invokes the method /// specified by with the HTTP method specified by /// and a JSON serialized request body specified by . If the response has a non-success /// status code an exception will be thrown. @@ -528,7 +528,7 @@ public Task InvokeMethodAsync( } /// - /// Perform service invocation for the application idenfied by and invokes the method + /// Perform service invocation for the application identified by and invokes the method /// specified by with the POST HTTP method /// and an empty request body. If the response has a success /// status code the body will be deserialized using JSON to a value of type ; @@ -549,7 +549,7 @@ public Task InvokeMethodAsync( } /// - /// Perform service invocation for the application idenfied by and invokes the method + /// Perform service invocation for the application identified by and invokes the method /// specified by with the HTTP method specified by /// and an empty request body. If the response has a success /// status code the body will be deserialized using JSON to a value of type ; @@ -572,7 +572,7 @@ public Task InvokeMethodAsync( } /// - /// Perform service invocation for the application idenfied by and invokes the method + /// Perform service invocation for the application identified by and invokes the method /// specified by with the POST HTTP method /// and a JSON serialized request body specified by . If the response has a success /// status code the body will be deserialized using JSON to a value of type ; @@ -596,7 +596,7 @@ public Task InvokeMethodAsync( } /// - /// Perform service invocation for the application idenfied by and invokes the method + /// Perform service invocation for the application identified by and invokes the method /// specified by with the HTTP method specified by /// and a JSON serialized request body specified by . If the response has a success /// status code the body will be deserialized using JSON to a value of type ; @@ -622,7 +622,7 @@ public Task InvokeMethodAsync( } /// - /// Perform service invocation using gRPC semantics for the application idenfied by and invokes the method + /// Perform service invocation using gRPC semantics for the application identified by and invokes the method /// specified by with an empty request body. /// If the response has a non-success status code an exception will be thrown. /// @@ -636,7 +636,7 @@ public abstract Task InvokeMethodGrpcAsync( CancellationToken cancellationToken = default); /// - /// Perform service invocation using gRPC semantics for the application idenfied by and invokes the method + /// Perform service invocation using gRPC semantics for the application identified by and invokes the method /// specified by with a Protobuf serialized request body specified by . /// If the response has a non-success status code an exception will be thrown. /// @@ -654,7 +654,7 @@ public abstract Task InvokeMethodGrpcAsync( where TRequest : IMessage; /// - /// Perform service invocation using gRPC semantics for the application idenfied by and invokes the method + /// Perform service invocation using gRPC semantics for the application identified by and invokes the method /// specified by with an empty request body. If the response has a success /// status code the body will be deserialized using Protobuf to a value of type ; /// otherwise an exception will be thrown. @@ -671,7 +671,7 @@ public abstract Task InvokeMethodGrpcAsync( where TResponse : IMessage, new(); /// - /// Perform service invocation using gRPC semantics for the application idenfied by and invokes the method + /// Perform service invocation using gRPC semantics for the application identified by and invokes the method /// specified by with a Protobuf serialized request body specified by . If the response has a success /// status code the body will be deserialized using Protobuf to a value of type ; /// otherwise an exception will be thrown. diff --git a/src/Dapr.Client/DaprClientBuilder.cs b/src/Dapr.Client/DaprClientBuilder.cs index 5b484e208..1580afb36 100644 --- a/src/Dapr.Client/DaprClientBuilder.cs +++ b/src/Dapr.Client/DaprClientBuilder.cs @@ -63,8 +63,9 @@ public DaprClientBuilder() /// /// /// The URI endpoint to use for HTTP calls to the Dapr runtime. The default value will be - /// http://127.0.0.1:DAPR_HTTP_PORT where DAPR_HTTP_PORT represents the value of the - /// DAPR_HTTP_PORT environment variable. + /// DAPR_HTTP_ENDPOINT first, or http://127.0.0.1:DAPR_HTTP_PORT as fallback + /// where DAPR_HTTP_ENDPOINT and DAPR_HTTP_PORT represents the value of the + /// corresponding environment variables. /// /// The instance. public DaprClientBuilder UseHttpEndpoint(string httpEndpoint) diff --git a/src/Dapr.Client/DaprClientGrpc.cs b/src/Dapr.Client/DaprClientGrpc.cs index c1cad41e1..75df09323 100644 --- a/src/Dapr.Client/DaprClientGrpc.cs +++ b/src/Dapr.Client/DaprClientGrpc.cs @@ -352,14 +352,10 @@ public override HttpRequestMessage CreateInvokeMethodRequest(HttpMethod httpMeth // // This approach avoids some common pitfalls that could lead to undesired encoding. var path = $"/v1.0/invoke/{appId}/method/{methodName.TrimStart('/')}"; - var request = new HttpRequestMessage(httpMethod, new Uri(this.httpEndpoint, path)) - { - Properties = - { - { AppIdKey, appId }, - { MethodNameKey, methodName }, - } - }; + var request = new HttpRequestMessage(httpMethod, new Uri(this.httpEndpoint, path)); + + request.Options.Set(new HttpRequestOptionsKey(AppIdKey), appId); + request.Options.Set(new HttpRequestOptionsKey(MethodNameKey), methodName); if (this.apiTokenHeader is not null) { @@ -399,8 +395,8 @@ public override async Task InvokeMethodWithResponseAsync(Ht { // Our code path for creating requests places these keys in the request properties. We don't want to fail // if they are not present. - request.Properties.TryGetValue(AppIdKey, out var appId); - request.Properties.TryGetValue(MethodNameKey, out var methodName); + request.Options.TryGetValue(new HttpRequestOptionsKey(AppIdKey), out var appId); + request.Options.TryGetValue(new HttpRequestOptionsKey(MethodNameKey), out var methodName); throw new InvocationException( appId: appId as string, @@ -423,8 +419,8 @@ public async override Task InvokeMethodAsync(HttpRequestMessage request, Cancell { // Our code path for creating requests places these keys in the request properties. We don't want to fail // if they are not present. - request.Properties.TryGetValue(AppIdKey, out var appId); - request.Properties.TryGetValue(MethodNameKey, out var methodName); + request.Options.TryGetValue(new HttpRequestOptionsKey(AppIdKey), out var appId); + request.Options.TryGetValue(new HttpRequestOptionsKey(MethodNameKey), out var methodName); throw new InvocationException( appId: appId as string, @@ -447,8 +443,8 @@ public async override Task InvokeMethodAsync(HttpRequestMe { // Our code path for creating requests places these keys in the request properties. We don't want to fail // if they are not present. - request.Properties.TryGetValue(AppIdKey, out var appId); - request.Properties.TryGetValue(MethodNameKey, out var methodName); + request.Options.TryGetValue(new HttpRequestOptionsKey(AppIdKey), out var appId); + request.Options.TryGetValue(new HttpRequestOptionsKey(MethodNameKey), out var methodName); throw new InvocationException( appId: appId as string, @@ -465,8 +461,8 @@ public async override Task InvokeMethodAsync(HttpRequestMe { // Our code path for creating requests places these keys in the request properties. We don't want to fail // if they are not present. - request.Properties.TryGetValue(AppIdKey, out var appId); - request.Properties.TryGetValue(MethodNameKey, out var methodName); + request.Options.TryGetValue(new HttpRequestOptionsKey(AppIdKey), out var appId); + request.Options.TryGetValue(new HttpRequestOptionsKey(MethodNameKey), out var methodName); throw new InvocationException( appId: appId as string, @@ -476,8 +472,8 @@ public async override Task InvokeMethodAsync(HttpRequestMe } catch (JsonException ex) { - request.Properties.TryGetValue(AppIdKey, out var appId); - request.Properties.TryGetValue(MethodNameKey, out var methodName); + request.Options.TryGetValue(new HttpRequestOptionsKey(AppIdKey), out var appId); + request.Options.TryGetValue(new HttpRequestOptionsKey(MethodNameKey), out var methodName); throw new InvocationException( appId: appId as string, @@ -1747,6 +1743,12 @@ public override async Task CheckHealthAsync(CancellationToken cancellation { var path = "/v1.0/healthz"; var request = new HttpRequestMessage(HttpMethod.Get, new Uri(this.httpEndpoint, path)); + + if (this.apiTokenHeader is not null) + { + request.Headers.Add(this.apiTokenHeader.Value.Key, this.apiTokenHeader.Value.Value); + } + try { using var response = await this.httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); @@ -1763,6 +1765,12 @@ public override async Task CheckOutboundHealthAsync(CancellationToken canc { var path = "/v1.0/healthz/outbound"; var request = new HttpRequestMessage(HttpMethod.Get, new Uri(this.httpEndpoint, path)); + + if (this.apiTokenHeader is not null) + { + request.Headers.Add(this.apiTokenHeader.Value.Key, this.apiTokenHeader.Value.Value); + } + try { using var response = await this.httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); diff --git a/src/Dapr.Client/DaprException.cs b/src/Dapr.Client/DaprException.cs index 8c94a452d..e7b1efaba 100644 --- a/src/Dapr.Client/DaprException.cs +++ b/src/Dapr.Client/DaprException.cs @@ -47,6 +47,9 @@ public DaprException(string message, Exception innerException) /// /// The object that contains serialized object data of the exception being thrown. /// The object that contains contextual information about the source or destination. The context parameter is reserved for future use and can be null. +#if NET8_0_OR_GREATER + [Obsolete(DiagnosticId = "SYSLIB0051")] // add this attribute to GetObjectData +#endif protected DaprException(SerializationInfo info, StreamingContext context) : base(info, context) { diff --git a/src/Dapr.Client/InvocationHandler.cs b/src/Dapr.Client/InvocationHandler.cs index b2f74c7f9..1e9000c4d 100644 --- a/src/Dapr.Client/InvocationHandler.cs +++ b/src/Dapr.Client/InvocationHandler.cs @@ -117,7 +117,7 @@ protected override async Task SendAsync(HttpRequestMessage } // Internal for testing - internal bool TryRewriteUri(Uri uri, [NotNullWhen(true)] out Uri? rewritten) + internal bool TryRewriteUri(Uri? uri, [NotNullWhen(true)] out Uri? rewritten) { // For now the only invalid cases are when the request URI is missing or just silly. // We may support additional cases for validation in the future (like an allow-list of App-Ids). diff --git a/src/Dapr.Extensions.Configuration/Dapr.Extensions.Configuration.csproj b/src/Dapr.Extensions.Configuration/Dapr.Extensions.Configuration.csproj index 7eec7f4f7..71fd0153e 100644 --- a/src/Dapr.Extensions.Configuration/Dapr.Extensions.Configuration.csproj +++ b/src/Dapr.Extensions.Configuration/Dapr.Extensions.Configuration.csproj @@ -1,7 +1,6 @@ - netcoreapp3.1 enable diff --git a/src/Dapr.Workflow/Dapr.Workflow.csproj b/src/Dapr.Workflow/Dapr.Workflow.csproj index a951350e8..d5820deb1 100644 --- a/src/Dapr.Workflow/Dapr.Workflow.csproj +++ b/src/Dapr.Workflow/Dapr.Workflow.csproj @@ -2,19 +2,23 @@ - netcoreapp3.1;net6;net7 + + net6;net7;net8 enable Dapr.Workflow Dapr Workflow Authoring SDK Dapr Workflow SDK for building workflows as code with Dapr - 0.2.0 + 0.3.0 alpha - 10.0 - - + + + + + + diff --git a/src/Dapr.Workflow/DaprWorkflowClient.cs b/src/Dapr.Workflow/DaprWorkflowClient.cs index 249de09b7..4c4902dbb 100644 --- a/src/Dapr.Workflow/DaprWorkflowClient.cs +++ b/src/Dapr.Workflow/DaprWorkflowClient.cs @@ -70,9 +70,9 @@ public Task ScheduleNewWorkflowAsync( /// The unique ID of the workflow instance to fetch. /// /// Specify true to fetch the workflow instance's inputs, outputs, and custom status, or false to - /// omit them. Defaults to false. + /// omit them. Defaults to true. /// - public async Task GetWorkflowStateAsync(string instanceId, bool getInputsAndOutputs = false) + public async Task GetWorkflowStateAsync(string instanceId, bool getInputsAndOutputs = true) { OrchestrationMetadata? metadata = await this.innerClient.GetInstancesAsync( instanceId, @@ -94,7 +94,7 @@ public async Task GetWorkflowStateAsync(string instanceId, bool g /// The unique ID of the workflow instance to wait for. /// /// Specify true to fetch the workflow instance's inputs, outputs, and custom status, or false to - /// omit them. The default value is false to minimize the network bandwidth, serialization, and memory costs + /// omit them. Setting this value to false can help minimize the network bandwidth, serialization, and memory costs /// associated with fetching the instance metadata. /// /// A that can be used to cancel the wait operation. @@ -104,7 +104,7 @@ public async Task GetWorkflowStateAsync(string instanceId, bool g /// public async Task WaitForWorkflowStartAsync( string instanceId, - bool getInputsAndOutputs = false, + bool getInputsAndOutputs = true, CancellationToken cancellation = default) { OrchestrationMetadata metadata = await this.innerClient.WaitForInstanceStartAsync( @@ -135,7 +135,7 @@ public async Task WaitForWorkflowStartAsync( /// public async Task WaitForWorkflowCompletionAsync( string instanceId, - bool getInputsAndOutputs = false, + bool getInputsAndOutputs = true, CancellationToken cancellation = default) { OrchestrationMetadata metadata = await this.innerClient.WaitForInstanceCompletionAsync( @@ -218,7 +218,7 @@ public Task RaiseEventAsync( object? eventPayload = null, CancellationToken cancellation = default) { - return this.innerClient.RaiseEventAsync(instanceId, eventName, cancellation); + return this.innerClient.RaiseEventAsync(instanceId, eventName, eventPayload, cancellation); } /// diff --git a/src/Dapr.Workflow/WorkflowLoggingService.cs b/src/Dapr.Workflow/WorkflowLoggingService.cs new file mode 100644 index 000000000..482d95b97 --- /dev/null +++ b/src/Dapr.Workflow/WorkflowLoggingService.cs @@ -0,0 +1,75 @@ +// ------------------------------------------------------------------------ +// Copyright 2022 The Dapr Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ------------------------------------------------------------------------ + +namespace Dapr.Workflow +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Extensions.Hosting; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Configuration; + + /// + /// Defines runtime options for workflows. + /// + internal sealed class WorkflowLoggingService : IHostedService + { + private readonly ILogger logger; + private static readonly HashSet registeredWorkflows = new(); + private static readonly HashSet registeredActivities = new(); + + public WorkflowLoggingService(ILogger logger, IConfiguration configuration) + { + this.logger = logger; + + } + public Task StartAsync(CancellationToken cancellationToken) + { + this.logger.Log(LogLevel.Information, "WorkflowLoggingService started"); + + this.logger.Log(LogLevel.Information, "List of registered workflows"); + foreach (string item in registeredWorkflows) + { + this.logger.Log(LogLevel.Information, item); + } + + this.logger.Log(LogLevel.Information, "List of registered activities:"); + foreach (string item in registeredActivities) + { + this.logger.Log(LogLevel.Information, item); + } + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + this.logger.Log(LogLevel.Information, "WorkflowLoggingService stopped"); + + return Task.CompletedTask; + } + + public static void LogWorkflowName(string workflowName) + { + registeredWorkflows.Add(workflowName); + } + + public static void LogActivityName(string activityName) + { + registeredActivities.Add(activityName); + } + + } +} diff --git a/src/Dapr.Workflow/WorkflowRuntimeOptions.cs b/src/Dapr.Workflow/WorkflowRuntimeOptions.cs index 4dd202b1a..adc925777 100644 --- a/src/Dapr.Workflow/WorkflowRuntimeOptions.cs +++ b/src/Dapr.Workflow/WorkflowRuntimeOptions.cs @@ -54,6 +54,7 @@ public void RegisterWorkflow(string name, Func(string name, Func(); return new OrchestratorWrapper(workflow); }); + WorkflowLoggingService.LogWorkflowName(name); }); } @@ -91,6 +93,7 @@ public void RegisterActivity(string name, Func() where TActivity : class, IWorkflowActi TActivity activity = ActivatorUtilities.CreateInstance(serviceProvider); return new ActivityWrapper(activity); }); + WorkflowLoggingService.LogActivityName(name); }); } diff --git a/src/Dapr.Workflow/WorkflowServiceCollectionExtensions.cs b/src/Dapr.Workflow/WorkflowServiceCollectionExtensions.cs index 161c29175..ca514f221 100644 --- a/src/Dapr.Workflow/WorkflowServiceCollectionExtensions.cs +++ b/src/Dapr.Workflow/WorkflowServiceCollectionExtensions.cs @@ -14,16 +14,20 @@ namespace Dapr.Workflow { using System; + using Grpc.Net.Client; using Microsoft.DurableTask.Client; using Microsoft.DurableTask.Worker; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; + using System.Net.Http; + using Dapr; /// /// Contains extension methods for using Dapr Workflow with dependency injection. /// public static class WorkflowServiceCollectionExtensions { + /// /// Adds Dapr Workflow support to the service collection. /// @@ -43,7 +47,7 @@ public static IServiceCollection AddDaprWorkflow( #pragma warning disable CS0618 // Type or member is obsolete - keeping around temporarily - replaced by DaprWorkflowClient serviceCollection.TryAddSingleton(); #pragma warning restore CS0618 // Type or member is obsolete - + serviceCollection.AddHostedService(); serviceCollection.TryAddSingleton(); serviceCollection.AddDaprClient(); serviceCollection.AddDaprWorkflowClient(); @@ -57,7 +61,18 @@ public static IServiceCollection AddDaprWorkflow( if (TryGetGrpcAddress(out string address)) { - builder.UseGrpc(address); + var daprApiToken = DaprDefaults.GetDefaultDaprApiToken(); + if (!string.IsNullOrEmpty(daprApiToken)) + { + var client = new HttpClient(); + client.DefaultRequestHeaders.Add("Dapr-Api-Token", daprApiToken); + builder.UseGrpc(CreateChannel(address, client)); + } + else + { + builder.UseGrpc(address); + } + } else { @@ -85,7 +100,18 @@ public static IServiceCollection AddDaprWorkflowClient(this IServiceCollection s { if (TryGetGrpcAddress(out string address)) { - builder.UseGrpc(address); + var daprApiToken = DaprDefaults.GetDefaultDaprApiToken(); + if (!string.IsNullOrEmpty(daprApiToken)) + { + var client = new HttpClient(); + client.DefaultRequestHeaders.Add("Dapr-Api-Token", daprApiToken); + builder.UseGrpc(CreateChannel(address, client)); + } + else + { + builder.UseGrpc(address); + } + } else { @@ -104,7 +130,13 @@ static bool TryGetGrpcAddress(out string address) // 1. DaprDefaults.cs uses 127.0.0.1 instead of localhost, which prevents testing with Dapr on WSL2 and the app on Windows // 2. DaprDefaults.cs doesn't compile when the project has C# nullable reference types enabled. // If the above issues are fixed (ensuring we don't regress anything) we should switch to using the logic in DaprDefaults.cs. - string? daprPortStr = Environment.GetEnvironmentVariable("DAPR_GRPC_PORT"); + var daprEndpoint = DaprDefaults.GetDefaultGrpcEndpoint(); + if (!String.IsNullOrEmpty(daprEndpoint)) { + address = daprEndpoint; + return true; + } + + var daprPortStr = Environment.GetEnvironmentVariable("DAPR_GRPC_PORT"); if (int.TryParse(daprPortStr, out int daprGrpcPort)) { // There is a bug in the Durable Task SDK that requires us to change the format of the address @@ -120,6 +152,33 @@ static bool TryGetGrpcAddress(out string address) address = string.Empty; return false; } + + static GrpcChannel CreateChannel(string address, HttpClient client) + { + + GrpcChannelOptions options = new() { HttpClient = client}; + var daprEndpoint = DaprDefaults.GetDefaultGrpcEndpoint(); + if (!String.IsNullOrEmpty(daprEndpoint)) { + return GrpcChannel.ForAddress(daprEndpoint, options); + } + + var daprPortStr = Environment.GetEnvironmentVariable("DAPR_GRPC_PORT"); + if (int.TryParse(daprPortStr, out int daprGrpcPort)) + { + // If there is no address passed in, we default to localhost + if (String.IsNullOrEmpty(address)) + { + // There is a bug in the Durable Task SDK that requires us to change the format of the address + // depending on the version of .NET that we're targeting. For now, we work around this manually. + #if NET6_0_OR_GREATER + address = $"http://localhost:{daprGrpcPort}"; + #else + address = $"localhost:{daprGrpcPort}"; + #endif + } + + } + return GrpcChannel.ForAddress(address, options); + } } } - diff --git a/src/Directory.Build.props b/src/Directory.Build.props index cdfef31a6..2794f1b1f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,6 +3,7 @@ + net6;net8 $(RepoRoot)bin\$(Configuration)\prod\$(MSBuildProjectName)\ $(OutputPath)$(MSBuildProjectName).xml diff --git a/src/Shared/DaprDefaults.cs b/src/Shared/DaprDefaults.cs index 28c02c148..b738de921 100644 --- a/src/Shared/DaprDefaults.cs +++ b/src/Shared/DaprDefaults.cs @@ -17,10 +17,10 @@ namespace Dapr { internal static class DaprDefaults { - private static string httpEndpoint; - private static string grpcEndpoint; - private static string daprApiToken; - private static string appApiToken; + private static string httpEndpoint = string.Empty; + private static string grpcEndpoint = string.Empty; + private static string daprApiToken = string.Empty; + private static string appApiToken = string.Empty; /// /// Get the value of environment variable DAPR_API_TOKEN @@ -31,11 +31,11 @@ public static string GetDefaultDaprApiToken() // Lazy-init is safe because this is just populating the default // We don't plan to support the case where the user changes environment variables // for a running process. - if (daprApiToken == null) + if (string.IsNullOrEmpty(daprApiToken)) { // Treat empty the same as null since it's an environment variable var value = Environment.GetEnvironmentVariable("DAPR_API_TOKEN"); - daprApiToken = (value == string.Empty) ? null : value; + daprApiToken = string.IsNullOrEmpty(value) ? string.Empty : value; } return daprApiToken; @@ -47,23 +47,29 @@ public static string GetDefaultDaprApiToken() /// The value of environment variable APP_API_TOKEN public static string GetDefaultAppApiToken() { - if (appApiToken == null) + if (string.IsNullOrEmpty(appApiToken)) { var value = Environment.GetEnvironmentVariable("APP_API_TOKEN"); - appApiToken = (value == string.Empty) ? null : value; + appApiToken = string.IsNullOrEmpty(value) ? string.Empty : value; } return appApiToken; } /// - /// Get the value of environment variable DAPR_HTTP_PORT + /// Get the value of HTTP endpoint based off environment variables /// - /// The value of environment variable DAPR_HTTP_PORT + /// The value of HTTP endpoint based off environment variables public static string GetDefaultHttpEndpoint() { - if (httpEndpoint == null) + if (string.IsNullOrEmpty(httpEndpoint)) { + var endpoint = Environment.GetEnvironmentVariable("DAPR_HTTP_ENDPOINT"); + if (!string.IsNullOrEmpty(endpoint)) { + httpEndpoint = endpoint; + return httpEndpoint; + } + var port = Environment.GetEnvironmentVariable("DAPR_HTTP_PORT"); port = string.IsNullOrEmpty(port) ? "3500" : port; httpEndpoint = $"http://127.0.0.1:{port}"; @@ -73,13 +79,19 @@ public static string GetDefaultHttpEndpoint() } /// - /// Get the value of environment variable DAPR_GRPC_PORT + /// Get the value of gRPC endpoint based off environment variables /// - /// The value of environment variable DAPR_GRPC_PORT + /// The value of gRPC endpoint based off environment variables public static string GetDefaultGrpcEndpoint() { - if (grpcEndpoint == null) + if (string.IsNullOrEmpty(grpcEndpoint)) { + var endpoint = Environment.GetEnvironmentVariable("DAPR_GRPC_ENDPOINT"); + if (!string.IsNullOrEmpty(endpoint)) { + grpcEndpoint = endpoint; + return grpcEndpoint; + } + var port = Environment.GetEnvironmentVariable("DAPR_GRPC_PORT"); port = string.IsNullOrEmpty(port) ? "50001" : port; grpcEndpoint = $"http://127.0.0.1:{port}"; diff --git a/test/Dapr.Actors.AspNetCore.IntegrationTest.App/Dapr.Actors.AspNetCore.IntegrationTest.App.csproj b/test/Dapr.Actors.AspNetCore.IntegrationTest.App/Dapr.Actors.AspNetCore.IntegrationTest.App.csproj index a518db3f5..c06d651f4 100644 --- a/test/Dapr.Actors.AspNetCore.IntegrationTest.App/Dapr.Actors.AspNetCore.IntegrationTest.App.csproj +++ b/test/Dapr.Actors.AspNetCore.IntegrationTest.App/Dapr.Actors.AspNetCore.IntegrationTest.App.csproj @@ -1,9 +1,5 @@ - - netcoreapp3.1;net6;net7 - - diff --git a/test/Dapr.Actors.AspNetCore.IntegrationTest/Dapr.Actors.AspNetCore.IntegrationTest.csproj b/test/Dapr.Actors.AspNetCore.IntegrationTest/Dapr.Actors.AspNetCore.IntegrationTest.csproj index 0f2d6b580..deccfc1e6 100644 --- a/test/Dapr.Actors.AspNetCore.IntegrationTest/Dapr.Actors.AspNetCore.IntegrationTest.csproj +++ b/test/Dapr.Actors.AspNetCore.IntegrationTest/Dapr.Actors.AspNetCore.IntegrationTest.csproj @@ -1,8 +1,4 @@ - - netcoreapp3.1;net6;net7 - - runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/Dapr.Actors.AspNetCore.Test/Dapr.Actors.AspNetCore.Test.csproj b/test/Dapr.Actors.AspNetCore.Test/Dapr.Actors.AspNetCore.Test.csproj index 071b72746..7e352d007 100644 --- a/test/Dapr.Actors.AspNetCore.Test/Dapr.Actors.AspNetCore.Test.csproj +++ b/test/Dapr.Actors.AspNetCore.Test/Dapr.Actors.AspNetCore.Test.csproj @@ -1,7 +1,6 @@ - netcoreapp3.1;net6;net7 Dapr.Actors.AspNetCore diff --git a/test/Dapr.Actors.Test/ActorUnitTestTests.cs b/test/Dapr.Actors.Test/ActorUnitTestTests.cs index 318ede9fc..baa52c568 100644 --- a/test/Dapr.Actors.Test/ActorUnitTestTests.cs +++ b/test/Dapr.Actors.Test/ActorUnitTestTests.cs @@ -74,6 +74,7 @@ public async Task CanTestStartingAndStoppingTimer() public async Task CanTestStartingAndStoppinReminder() { var reminders = new List(); + IActorReminder getReminder = null; var timerManager = new Mock(MockBehavior.Strict); timerManager @@ -84,6 +85,9 @@ public async Task CanTestStartingAndStoppinReminder() .Setup(tm => tm.UnregisterReminderAsync(It.IsAny())) .Callback(reminder => reminders.RemoveAll(t => t.Name == reminder.Name)) .Returns(Task.CompletedTask); + timerManager + .Setup(tm => tm.GetReminderAsync(It.IsAny())) + .Returns(() => Task.FromResult(getReminder)); var host = ActorHost.CreateForTest(new ActorTestOptions(){ TimerManager = timerManager.Object, }); var actor = new CoolTestActor(host); @@ -109,6 +113,10 @@ public async Task CanTestStartingAndStoppinReminder() await actor.ReceiveReminderAsync(reminder.Name, reminder.State, reminder.DueTime, reminder.Period); } + getReminder = reminder; + var reminderFromGet = await actor.GetReminderAsync(); + Assert.Equal(reminder, reminderFromGet); + // Stop the reminder await actor.StopReminderAsync(); Assert.Empty(reminders); @@ -148,6 +156,11 @@ public async Task StartReminderAsync(Message message) await this.RegisterReminderAsync("record", bytes, dueTime: TimeSpan.Zero, period: TimeSpan.FromSeconds(5)); } + public async Task GetReminderAsync() + { + return await this.GetReminderAsync("record"); + } + public async Task StopReminderAsync() { await this.UnregisterReminderAsync("record"); diff --git a/test/Dapr.Actors.Test/Dapr.Actors.Test.csproj b/test/Dapr.Actors.Test/Dapr.Actors.Test.csproj index 0ac27e077..8852dd465 100644 --- a/test/Dapr.Actors.Test/Dapr.Actors.Test.csproj +++ b/test/Dapr.Actors.Test/Dapr.Actors.Test.csproj @@ -1,6 +1,5 @@  - netcoreapp3.1;net6;net7 Dapr.Actors $(DefineConstants);ACTORS diff --git a/test/Dapr.Actors.Test/Runtime/ActorManagerTests.cs b/test/Dapr.Actors.Test/Runtime/ActorManagerTests.cs index dd3683124..6b92c7e18 100644 --- a/test/Dapr.Actors.Test/Runtime/ActorManagerTests.cs +++ b/test/Dapr.Actors.Test/Runtime/ActorManagerTests.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ // Copyright 2021 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ private ActorManager CreateActorManager(Type type, ActorActivator activator = nu { var registration = new ActorRegistration(ActorTypeInformation.Get(type, actorTypeName: null)); var interactor = new DaprHttpInteractor(clientHandler: null, "http://localhost:3500", apiToken: null, requestTimeout: null); - return new ActorManager(registration, activator ?? new DefaultActorActivator(), JsonSerializerDefaults.Web, NullLoggerFactory.Instance, ActorProxy.DefaultProxyFactory, interactor); + return new ActorManager(registration, activator ?? new DefaultActorActivator(), JsonSerializerDefaults.Web, false, NullLoggerFactory.Instance, ActorProxy.DefaultProxyFactory, interactor); } [Fact] diff --git a/test/Dapr.Actors.Test/TestDaprInteractor.cs b/test/Dapr.Actors.Test/TestDaprInteractor.cs index 1b382208d..92cfa7096 100644 --- a/test/Dapr.Actors.Test/TestDaprInteractor.cs +++ b/test/Dapr.Actors.Test/TestDaprInteractor.cs @@ -99,6 +99,20 @@ Task IDaprInteractor.InvokeActorMethodWithRemotingAsync(A throw new System.NotImplementedException(); } + /// + /// Gets a reminder. + /// + /// Type of actor. + /// ActorId. + /// Name of reminder to unregister. + /// Cancels the operation. + /// A representing the result of the asynchronous operation. + public Task GetReminderAsync(string actorType, string actorId, string reminderName, + CancellationToken cancellationToken = default) + { + throw new System.NotImplementedException(); + } + /// /// Unregisters a reminder. /// diff --git a/test/Dapr.AspNetCore.IntegrationTest.App/Dapr.AspNetCore.IntegrationTest.App.csproj b/test/Dapr.AspNetCore.IntegrationTest.App/Dapr.AspNetCore.IntegrationTest.App.csproj index 86a561f59..f415639be 100644 --- a/test/Dapr.AspNetCore.IntegrationTest.App/Dapr.AspNetCore.IntegrationTest.App.csproj +++ b/test/Dapr.AspNetCore.IntegrationTest.App/Dapr.AspNetCore.IntegrationTest.App.csproj @@ -1,9 +1,5 @@  - - netcoreapp3.1;net6;net7 - - diff --git a/test/Dapr.AspNetCore.IntegrationTest/Dapr.AspNetCore.IntegrationTest.csproj b/test/Dapr.AspNetCore.IntegrationTest/Dapr.AspNetCore.IntegrationTest.csproj index f3c08b1b7..3cd79d908 100644 --- a/test/Dapr.AspNetCore.IntegrationTest/Dapr.AspNetCore.IntegrationTest.csproj +++ b/test/Dapr.AspNetCore.IntegrationTest/Dapr.AspNetCore.IntegrationTest.csproj @@ -1,7 +1,4 @@  - - netcoreapp3.1;net6;net7 - diff --git a/test/Dapr.AspNetCore.Test/Dapr.AspNetCore.Test.csproj b/test/Dapr.AspNetCore.Test/Dapr.AspNetCore.Test.csproj index b153287b2..aa463be98 100644 --- a/test/Dapr.AspNetCore.Test/Dapr.AspNetCore.Test.csproj +++ b/test/Dapr.AspNetCore.Test/Dapr.AspNetCore.Test.csproj @@ -1,7 +1,4 @@  - - netcoreapp3.1;net6;net7 - diff --git a/test/Dapr.Client.Test/Dapr.Client.Test.csproj b/test/Dapr.Client.Test/Dapr.Client.Test.csproj index c94f031e9..aef5b4113 100644 --- a/test/Dapr.Client.Test/Dapr.Client.Test.csproj +++ b/test/Dapr.Client.Test/Dapr.Client.Test.csproj @@ -1,7 +1,4 @@  - - netcoreapp3.1;net6;net7 - @@ -11,7 +8,7 @@ - + diff --git a/test/Dapr.Client.Test/PublishEventApiTest.cs b/test/Dapr.Client.Test/PublishEventApiTest.cs index d8caf63d1..77d6ee905 100644 --- a/test/Dapr.Client.Test/PublishEventApiTest.cs +++ b/test/Dapr.Client.Test/PublishEventApiTest.cs @@ -11,6 +11,12 @@ // limitations under the License. // ------------------------------------------------------------------------ +using System.Collections.Immutable; +using System.Linq; +using System.Net.Http; +using System.Text.Json.Serialization; +using Grpc.Net.Client; + namespace Dapr.Client.Test { using System; @@ -51,6 +57,44 @@ public async Task PublishEventAsync_CanPublishTopicWithData() envelope.Metadata.Count.Should().Be(0); } + [Fact] + public async Task PublishEvent_ShouldRespectJsonStringEnumConverter() + { + //The following mimics how the TestClient is built, but adds the JsonStringEnumConverter to the serialization options + var handler = new TestClient.CapturingHandler(); + var httpClient = new HttpClient(handler); + var clientBuilder = new DaprClientBuilder() + .UseJsonSerializationOptions(new JsonSerializerOptions() + { + Converters = {new JsonStringEnumConverter(null, false)} + }) + .UseHttpClientFactory(() => httpClient) + .UseGrpcChannelOptions(new GrpcChannelOptions() + { + HttpClient = httpClient, ThrowOperationCanceledOnCancellation = true + }); + var client = new TestClient(clientBuilder.Build(), handler); + + //Ensure that the JsonStringEnumConverter is registered + client.InnerClient.JsonSerializerOptions.Converters.Count.Should().Be(1); + client.InnerClient.JsonSerializerOptions.Converters.First().GetType().Name.Should() + .Match(nameof(JsonStringEnumConverter)); + + var publishData = new Widget {Size = "Large", Color = WidgetColor.Red}; + var request = await client.CaptureGrpcRequestAsync(async daprClient => + { + await daprClient.PublishEventAsync(TestPubsubName, "test", publishData); + }); + + request.Dismiss(); + + var envelope = await request.GetRequestEnvelopeAsync(); + var jsonFromRequest = envelope.Data.ToStringUtf8(); + jsonFromRequest.Should() + .Be(JsonSerializer.Serialize(publishData, client.InnerClient.JsonSerializerOptions)); + jsonFromRequest.Should().Match("{\"Size\":\"Large\",\"Color\":\"Red\"}"); + } + [Fact] public async Task PublishEventAsync_CanPublishTopicWithData_WithMetadata() { @@ -259,5 +303,18 @@ private class PublishData { public string PublishObjectParameter { get; set; } } + + private class Widget + { + public string Size { get; set; } + public WidgetColor Color { get; set; } + } + + private enum WidgetColor + { + Red, + Green, + Yellow + } } } diff --git a/test/Dapr.E2E.Test.Actors/Dapr.E2E.Test.Actors.csproj b/test/Dapr.E2E.Test.Actors/Dapr.E2E.Test.Actors.csproj index a0f13978d..56ab3d222 100644 --- a/test/Dapr.E2E.Test.Actors/Dapr.E2E.Test.Actors.csproj +++ b/test/Dapr.E2E.Test.Actors/Dapr.E2E.Test.Actors.csproj @@ -1,9 +1,5 @@ - - netcoreapp3.1;net6;net7 - - diff --git a/test/Dapr.E2E.Test.Actors/ISerializationActor.cs b/test/Dapr.E2E.Test.Actors/ISerializationActor.cs new file mode 100644 index 000000000..28190a0d7 --- /dev/null +++ b/test/Dapr.E2E.Test.Actors/ISerializationActor.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Dapr.Actors; + +namespace Dapr.E2E.Test.Actors +{ + public interface ISerializationActor : IActor, IPingActor + { + Task SendAsync(string name, SerializationPayload payload, CancellationToken cancellationToken = default); + } + + public record SerializationPayload(string Message) + { + public JsonElement Value { get; set; } + + [JsonExtensionData] + public Dictionary ExtensionData { get; set; } + } +} diff --git a/test/Dapr.E2E.Test.Actors/Reminders/IReminderActor.cs b/test/Dapr.E2E.Test.Actors/Reminders/IReminderActor.cs index 33a5e0d05..0bf57f64c 100644 --- a/test/Dapr.E2E.Test.Actors/Reminders/IReminderActor.cs +++ b/test/Dapr.E2E.Test.Actors/Reminders/IReminderActor.cs @@ -28,5 +28,7 @@ public interface IReminderActor : IPingActor, IActor Task StartReminderWithTtlAndRepetitions(TimeSpan ttl, int repetitions); Task GetState(); + + Task GetReminder(); } } diff --git a/test/Dapr.E2E.Test.App.Grpc/Dapr.E2E.Test.App.Grpc.csproj b/test/Dapr.E2E.Test.App.Grpc/Dapr.E2E.Test.App.Grpc.csproj index abd821d39..849870b98 100644 --- a/test/Dapr.E2E.Test.App.Grpc/Dapr.E2E.Test.App.Grpc.csproj +++ b/test/Dapr.E2E.Test.App.Grpc/Dapr.E2E.Test.App.Grpc.csproj @@ -1,7 +1,4 @@ - - netcoreapp3.1;net6;net7 - diff --git a/test/Dapr.E2E.Test.App.ReentrantActor/Dapr.E2E.Test.App.ReentrantActors.csproj b/test/Dapr.E2E.Test.App.ReentrantActor/Dapr.E2E.Test.App.ReentrantActors.csproj index b533b52f3..2fda3becc 100644 --- a/test/Dapr.E2E.Test.App.ReentrantActor/Dapr.E2E.Test.App.ReentrantActors.csproj +++ b/test/Dapr.E2E.Test.App.ReentrantActor/Dapr.E2E.Test.App.ReentrantActors.csproj @@ -1,9 +1,5 @@ - - netcoreapp3.1;net6;net7 - - diff --git a/test/Dapr.E2E.Test.App/Actors/ReminderActor.cs b/test/Dapr.E2E.Test.App/Actors/ReminderActor.cs index f9b9d7573..b08e483c2 100644 --- a/test/Dapr.E2E.Test.App/Actors/ReminderActor.cs +++ b/test/Dapr.E2E.Test.App/Actors/ReminderActor.cs @@ -44,6 +44,12 @@ public async Task StartReminder(StartReminderOptions options) await this.StateManager.SetStateAsync("reminder-state", new State(){ IsReminderRunning = true, }); } + public async Task GetReminder(){ + var reminder = await this.GetReminderAsync("test-reminder"); + var reminderString = JsonSerializer.Serialize(reminder, this.Host.JsonSerializerOptions); + return reminderString; + } + public async Task StartReminderWithTtl(TimeSpan ttl) { var options = new StartReminderOptions() diff --git a/test/Dapr.E2E.Test.App/Actors/SerializationActor.cs b/test/Dapr.E2E.Test.App/Actors/SerializationActor.cs new file mode 100644 index 000000000..e8da59826 --- /dev/null +++ b/test/Dapr.E2E.Test.App/Actors/SerializationActor.cs @@ -0,0 +1,26 @@ + +using System.Threading; +using System.Threading.Tasks; +using Dapr.Actors.Runtime; + +namespace Dapr.E2E.Test.Actors.Serialization +{ + public class SerializationActor : Actor, ISerializationActor + { + public SerializationActor(ActorHost host) + : base(host) + { + } + + public Task Ping() + { + return Task.CompletedTask; + } + + public Task SendAsync(string name, + SerializationPayload payload, CancellationToken cancellationToken = default) + { + return Task.FromResult(payload); + } + } +} diff --git a/test/Dapr.E2E.Test.App/Dapr.E2E.Test.App.csproj b/test/Dapr.E2E.Test.App/Dapr.E2E.Test.App.csproj index 7e114e8df..e6ad11456 100644 --- a/test/Dapr.E2E.Test.App/Dapr.E2E.Test.App.csproj +++ b/test/Dapr.E2E.Test.App/Dapr.E2E.Test.App.csproj @@ -1,13 +1,16 @@ - - netcoreapp3.1;net6;net7 - - + + + + + + + diff --git a/test/Dapr.E2E.Test.App/Program.cs b/test/Dapr.E2E.Test.App/Program.cs index e617558bf..5713129d5 100644 --- a/test/Dapr.E2E.Test.App/Program.cs +++ b/test/Dapr.E2E.Test.App/Program.cs @@ -1,23 +1,25 @@ -// ------------------------------------------------------------------------ -// 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. -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ +// 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. +// ------------------------------------------------------------------------ namespace Dapr.E2E.Test { using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; + using Serilog; public class Program { public static void Main(string[] args) { + Log.Logger = new LoggerConfiguration().WriteTo.File("log.txt").CreateLogger(); CreateHostBuilder(args).Build().Run(); } @@ -26,6 +28,6 @@ public static IHostBuilder CreateHostBuilder(string[] args) => .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); - }); + }).UseSerilog(); } } diff --git a/test/Dapr.E2E.Test.App/Startup.cs b/test/Dapr.E2E.Test.App/Startup.cs index b5aebbba2..8207c5883 100644 --- a/test/Dapr.E2E.Test.App/Startup.cs +++ b/test/Dapr.E2E.Test.App/Startup.cs @@ -17,6 +17,7 @@ namespace Dapr.E2E.Test using Dapr.E2E.Test.Actors.Reminders; using Dapr.E2E.Test.Actors.Timers; using Dapr.E2E.Test.Actors.ExceptionTesting; + using Dapr.E2E.Test.Actors.Serialization; using Dapr.E2E.Test.App.ErrorTesting; using Dapr.Workflow; using Microsoft.AspNetCore.Authentication; @@ -28,12 +29,17 @@ namespace Dapr.E2E.Test using Microsoft.Extensions.Hosting; using System.Threading.Tasks; using System; + using Microsoft.Extensions.Logging; + using Serilog; /// /// Startup class. /// public class Startup { + bool JsonSerializationEnabled => + System.Linq.Enumerable.Contains(System.Environment.GetCommandLineArgs(), "--json-serialization"); + /// /// Initializes a new instance of the class. /// @@ -57,6 +63,10 @@ public void ConfigureServices(IServiceCollection services) services.AddAuthentication().AddDapr(); services.AddAuthorization(o => o.AddDapr()); services.AddControllers().AddDapr(); + services.AddLogging(builder => + { + builder.AddConsole(); + }); // Register a workflow and associated activity services.AddDaprWorkflow(options => { @@ -66,6 +76,11 @@ public void ConfigureServices(IServiceCollection services) var itemToPurchase = input; + // There are 5 of the same event to test that multiple similarly-named events can be raised in parallel + await context.WaitForExternalEventAsync("ChangePurchaseItem"); + await context.WaitForExternalEventAsync("ChangePurchaseItem"); + await context.WaitForExternalEventAsync("ChangePurchaseItem"); + await context.WaitForExternalEventAsync("ChangePurchaseItem"); itemToPurchase = await context.WaitForExternalEventAsync("ChangePurchaseItem"); // In real life there are other steps related to placing an order, like reserving @@ -83,10 +98,12 @@ public void ConfigureServices(IServiceCollection services) }); services.AddActors(options => { + options.UseJsonSerialization = JsonSerializationEnabled; options.Actors.RegisterActor(); options.Actors.RegisterActor(); options.Actors.RegisterActor(); options.Actors.RegisterActor(); + options.Actors.RegisterActor(); }); } @@ -97,11 +114,19 @@ public void ConfigureServices(IServiceCollection services) /// Webhost environment. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + var logger = new LoggerConfiguration().WriteTo.File("log.txt").CreateLogger(); + + var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddSerilog(logger); + }); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } + app.UseSerilogRequestLogging(); + app.UseRouting(); app.UseAuthentication(); diff --git a/test/Dapr.E2E.Test/Actors/E2ETests.CustomSerializerTests.cs b/test/Dapr.E2E.Test/Actors/E2ETests.CustomSerializerTests.cs new file mode 100644 index 000000000..c393f2ef1 --- /dev/null +++ b/test/Dapr.E2E.Test/Actors/E2ETests.CustomSerializerTests.cs @@ -0,0 +1,88 @@ +// ------------------------------------------------------------------------ +// 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. +// ------------------------------------------------------------------------ +namespace Dapr.E2E.Test +{ + using System; + using System.Diagnostics; + using System.Text.Json; + using System.Threading; + using System.Threading.Tasks; + using Dapr.Actors; + using Dapr.Actors.Client; + using Dapr.E2E.Test.Actors; + using Xunit; + using Xunit.Abstractions; + + public class CustomSerializerTests : DaprTestAppLifecycle + { + private readonly Lazy proxyFactory; + private IActorProxyFactory ProxyFactory => this.HttpEndpoint == null ? null : this.proxyFactory.Value; + + public CustomSerializerTests(ITestOutputHelper output, DaprTestAppFixture fixture) : base(output, fixture) + { + base.Configuration = new DaprRunConfiguration + { + UseAppPort = true, + AppId = "serializerapp", + AppJsonSerialization = true, + TargetProject = "./../../../../../test/Dapr.E2E.Test.App/Dapr.E2E.Test.App.csproj" + }; + + this.proxyFactory = new Lazy(() => + { + Debug.Assert(this.HttpEndpoint != null); + return new ActorProxyFactory(new ActorProxyOptions() { + HttpEndpoint = this.HttpEndpoint, + JsonSerializerOptions = new JsonSerializerOptions() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + WriteIndented = true, + }, + UseJsonSerialization = true, + }); + }); + } + + [Fact] + public async Task ActorCanSupportCustomSerializer() + { + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60)); + var proxy = this.ProxyFactory.CreateActorProxy(ActorId.CreateRandom(), "SerializationActor"); + + await ActorRuntimeChecker.WaitForActorRuntimeAsync(this.AppId, this.Output, proxy, cts.Token); + + var payload = new SerializationPayload("hello world") + { + Value = JsonSerializer.SerializeToElement(new { foo = "bar" }), + ExtensionData = new System.Collections.Generic.Dictionary() + { + { "baz", "qux" }, + { "count", 42 }, + } + }; + + var result = await proxy.SendAsync("test", payload, CancellationToken.None); + + Assert.Equal(payload.Message, result.Message); + Assert.Equal(payload.Value.GetRawText(), result.Value.GetRawText()); + Assert.Equal(payload.ExtensionData.Count, result.ExtensionData.Count); + + foreach (var kvp in payload.ExtensionData) + { + Assert.True(result.ExtensionData.TryGetValue(kvp.Key, out var value)); + Assert.Equal(JsonSerializer.Serialize(kvp.Value), JsonSerializer.Serialize(value)); + } + } + } +} diff --git a/test/Dapr.E2E.Test/Actors/E2ETests.ReminderTests.cs b/test/Dapr.E2E.Test/Actors/E2ETests.ReminderTests.cs index 50cd87219..ff39cce8a 100644 --- a/test/Dapr.E2E.Test/Actors/E2ETests.ReminderTests.cs +++ b/test/Dapr.E2E.Test/Actors/E2ETests.ReminderTests.cs @@ -13,6 +13,7 @@ namespace Dapr.E2E.Test { using System; + using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Dapr.Actors; @@ -49,6 +50,58 @@ public async Task ActorCanStartAndStopReminder() Assert.Equal(10, state.Count); } + [Fact] + public async Task ActorCanStartAndStopAndGetReminder() + { + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60)); + var proxy = this.ProxyFactory.CreateActorProxy(ActorId.CreateRandom(), "ReminderActor"); + + await WaitForActorRuntimeAsync(proxy, cts.Token); + + // Get reminder before starting it, should return null. + var reminder = await proxy.GetReminder(); + Assert.Equal("null", reminder); + + // Start reminder, to count up to 10 + await proxy.StartReminder(new StartReminderOptions(){ Total = 10, }); + + State state = new State(); + var countGetReminder = 0; + while (true) + { + cts.Token.ThrowIfCancellationRequested(); + + reminder = await proxy.GetReminder(); + Assert.NotNull(reminder); + + // If reminder is null then it means the reminder has been stopped. + if (reminder != "null") + { + countGetReminder++; + var reminderJson = JsonSerializer.Deserialize(reminder); + var name = reminderJson.GetProperty("name").ToString(); + var period = reminderJson.GetProperty("period").ToString(); + var dueTime = reminderJson.GetProperty("dueTime").ToString(); + + Assert.Equal("test-reminder", name); + Assert.Equal(TimeSpan.FromMilliseconds(50).ToString(), period); + Assert.Equal(TimeSpan.Zero.ToString(), dueTime); + } + + state = await proxy.GetState(); + this.Output.WriteLine($"Got Count: {state.Count} IsReminderRunning: {state.IsReminderRunning}"); + if (!state.IsReminderRunning) + { + break; + } + } + + // Should count up to exactly 10 + Assert.Equal(10, state.Count); + // Should be able to Get Reminder at least once. + Assert.True(countGetReminder > 0); + } + [Fact] public async Task ActorCanStartReminderWithRepetitions() { diff --git a/test/Dapr.E2E.Test/Dapr.E2E.Test.csproj b/test/Dapr.E2E.Test/Dapr.E2E.Test.csproj index 10ae69b38..f899167c4 100644 --- a/test/Dapr.E2E.Test/Dapr.E2E.Test.csproj +++ b/test/Dapr.E2E.Test/Dapr.E2E.Test.csproj @@ -1,7 +1,4 @@  - - netcoreapp3.1;net6;net7 - diff --git a/test/Dapr.E2E.Test/DaprRunConfiguration.cs b/test/Dapr.E2E.Test/DaprRunConfiguration.cs index 9e423205d..fccfbcdd4 100644 --- a/test/Dapr.E2E.Test/DaprRunConfiguration.cs +++ b/test/Dapr.E2E.Test/DaprRunConfiguration.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ // Copyright 2021 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,9 +20,11 @@ public class DaprRunConfiguration public string AppId { get; set; } public string AppProtocol { get; set; } + + public bool AppJsonSerialization { get; set; } public string ConfigurationPath { get; set; } public string TargetProject { get; set; } } -} \ No newline at end of file +} diff --git a/test/Dapr.E2E.Test/DaprTestApp.cs b/test/Dapr.E2E.Test/DaprTestApp.cs index ee842f27b..83f9948ac 100644 --- a/test/Dapr.E2E.Test/DaprTestApp.cs +++ b/test/Dapr.E2E.Test/DaprTestApp.cs @@ -89,6 +89,11 @@ public DaprTestApp(ITestOutputHelper output, string appId) arguments.AddRange(new[] { "--urls", $"http://localhost:{appPort.ToString(CultureInfo.InvariantCulture)}", }); } + if (configuration.AppJsonSerialization) + { + arguments.AddRange(new[] { "--json-serialization" }); + } + // TODO: we don't do any quoting right now because our paths are guaranteed not to contain spaces var daprStart = new DaprCommand(this.testOutput) { @@ -127,24 +132,14 @@ public void Stop() private static string GetTargetFrameworkName() { var targetFrameworkName = ((TargetFrameworkAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(TargetFrameworkAttribute), false).FirstOrDefault()).FrameworkName; - string frameworkMoniker; - if (targetFrameworkName == ".NETCoreApp,Version=v3.1") - { - frameworkMoniker = "netcoreapp3.1"; - } - else if (targetFrameworkName == ".NETCoreApp,Version=v5.0") - { - frameworkMoniker = "net5"; - } - else if (targetFrameworkName == ".NETCoreApp,Version=v6.0") - { - frameworkMoniker = "net6"; - } - else + + return targetFrameworkName switch { - frameworkMoniker = "net7"; - } - return frameworkMoniker; + ".NETCoreApp,Version=v6.0" => "net6", + ".NETCoreApp,Version=v7.0" => "net7", + ".NETCoreApp,Version=v8.0" => "net8", + _ => throw new InvalidOperationException($"Unsupported target framework: {targetFrameworkName}") + }; } private static (int, int, int, int) GetFreePorts() diff --git a/test/Dapr.E2E.Test/E2ETests.cs b/test/Dapr.E2E.Test/E2ETests.cs index 94ebbf3df..bc469f715 100644 --- a/test/Dapr.E2E.Test/E2ETests.cs +++ b/test/Dapr.E2E.Test/E2ETests.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------ +// ------------------------------------------------------------------------ // Copyright 2021 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ using Xunit; using Xunit.Abstractions; +[assembly: CollectionBehavior(DisableTestParallelization = true)] + namespace Dapr.E2E.Test { // We're using IClassFixture to manage the state we need across tests. diff --git a/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs b/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs index ae30a9151..d95929ca3 100644 --- a/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs +++ b/test/Dapr.E2E.Test/Workflows/WorkflowTest.cs @@ -11,25 +11,81 @@ // limitations under the License. // ------------------------------------------------------------------------ using System; +using System.IO; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Dapr.Client; using FluentAssertions; using Xunit; +using System.Linq; +using System.Diagnostics; namespace Dapr.E2E.Test { [Obsolete] public partial class E2ETests { + [Fact] + public async Task TestWorkflowLogging() + { + // This test starts the daprclient and searches through the logfile to ensure the + // workflow logger is correctly logging the registered workflow(s) and activity(s) + + Dictionary logStrings = new Dictionary(); + logStrings["PlaceOrder"] = false; + logStrings["ShipProduct"] = false; + var logFilePath = "../../../../../test/Dapr.E2E.Test.App/log.txt"; + var allLogsFound = false; + var timeout = 30; // 30s + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeout)); + using var daprClient = new DaprClientBuilder().UseGrpcEndpoint(this.GrpcEndpoint).UseHttpEndpoint(this.HttpEndpoint).Build(); + var health = await daprClient.CheckHealthAsync(); + health.Should().Be(true, "DaprClient is not healthy"); + + var searchTask = Task.Run(async() => + { + using (StreamReader reader = new StreamReader(logFilePath)) + { + string line; + while ((line = await reader.ReadLineAsync().WaitAsync(cts.Token)) != null) + { + foreach (var entry in logStrings) + { + if (line.Contains(entry.Key)) + { + logStrings[entry.Key] = true; + } + } + allLogsFound = logStrings.All(k => k.Value); + if (allLogsFound) + { + break; + } + } + } + }, cts.Token); + + try + { + await searchTask; + } + finally + { + File.Delete(logFilePath); + } + if (!allLogsFound) + { + Assert.True(false, "The logs were not able to found within the timeout"); + } + } [Fact] public async Task TestWorkflows() { - string instanceId = "testInstanceId"; - string instanceId2 = "EventRaiseId"; - string workflowComponent = "dapr"; - string workflowName = "PlaceOrder"; + var instanceId = "testInstanceId"; + var instanceId2 = "EventRaiseId"; + var workflowComponent = "dapr"; + var workflowName = "PlaceOrder"; object input = "paperclips"; Dictionary workflowOptions = new Dictionary(); workflowOptions.Add("task_queue", "testQueue"); @@ -82,7 +138,7 @@ public async Task TestWorkflows() } catch (DaprException ex) { - ex.InnerException.Message.Should().Contain("No such instance exists", $"Instance {instanceId} was not correctly purged"); + ex.InnerException.Message.Should().Contain("no such instance exists", $"Instance {instanceId} was not correctly purged"); } // Start another workflow for event raising purposes @@ -93,8 +149,16 @@ public async Task TestWorkflows() input: input, workflowOptions: workflowOptions); - // RAISE EVENT TEST - await daprClient.RaiseWorkflowEventAsync(instanceId2, workflowComponent, "ChangePurchaseItem", "computers"); + // PARALLEL RAISE EVENT TEST + var event1 = daprClient.RaiseWorkflowEventAsync(instanceId2, workflowComponent, "ChangePurchaseItem", "computers"); + var event2 = daprClient.RaiseWorkflowEventAsync(instanceId2, workflowComponent, "ChangePurchaseItem", "computers"); + var event3 = daprClient.RaiseWorkflowEventAsync(instanceId2, workflowComponent, "ChangePurchaseItem", "computers"); + var event4 = daprClient.RaiseWorkflowEventAsync(instanceId2, workflowComponent, "ChangePurchaseItem", "computers"); + var event5 = daprClient.RaiseWorkflowEventAsync(instanceId2, workflowComponent, "ChangePurchaseItem", "computers"); + + var externalEvents = Task.WhenAll(event1, event2, event3, event4, event5); + var winner = await Task.WhenAny(externalEvents, Task.Delay(TimeSpan.FromSeconds(30))); + externalEvents.IsCompletedSuccessfully.Should().BeTrue($"Unsuccessful at raising events. Status of events: {externalEvents.IsCompletedSuccessfully}"); // Wait up to 30 seconds for the workflow to complete and check the output using var cts = new CancellationTokenSource(delay: TimeSpan.FromSeconds(30)); diff --git a/test/Dapr.Extensions.Configuration.Test/Dapr.Extensions.Configuration.Test.csproj b/test/Dapr.Extensions.Configuration.Test/Dapr.Extensions.Configuration.Test.csproj index 79e980ccb..2e4523582 100644 --- a/test/Dapr.Extensions.Configuration.Test/Dapr.Extensions.Configuration.Test.csproj +++ b/test/Dapr.Extensions.Configuration.Test/Dapr.Extensions.Configuration.Test.csproj @@ -1,7 +1,4 @@  - - netcoreapp3.1;net6;net7 - diff --git a/test/Directory.Build.props b/test/Directory.Build.props index e7d30ea2d..0ce23c19e 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -2,6 +2,8 @@ + net6;net7;net8 + $(RepoRoot)bin\$(Configuration)\test\$(MSBuildProjectName)\