Merge branch 'release-1.16' into actor-timer-return-value

Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
This commit is contained in:
Whit Waldo 2025-06-21 05:30:34 -05:00 committed by GitHub
commit 3f5be552cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 964 additions and 332 deletions

View File

@ -66,12 +66,18 @@ dotnet new web MyApp
```
Next we'll configure the AppHost project to add the necessary package to support local Dapr development. Navigate
into the AppHost directory with the following and install the `Aspire.Hosting.Dapr` package from NuGet into the project.
into the AppHost directory with the following and install the `CommunityToolkit.Aspire.Hosting.Dapr` package from NuGet into the project.
We'll also add a reference to our `MyApp` project so we can reference it during the registration process.
{{% alert color="primary" %}}
This package was previously called `Aspire.Hosting.Dapr`, which has been [marked as deprecated](https://www.nuget.org/packages/Aspire.Hosting.Dapr).
{{% /alert %}}
```sh
cd aspiredemo.AppHost
dotnet add package Aspire.Hosting.Dapr
dotnet add package CommunityToolkit.Aspire.Hosting.Dapr
dotnet add reference ../MyApp/
```

View File

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

View File

@ -0,0 +1,72 @@
// ------------------------------------------------------------------------
// Copyright 2023 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
using System.Buffers;
using Dapr.Client;
#pragma warning disable CS0618 // Type or member is obsolete
namespace Cryptography.Examples
{
internal class EncryptDecryptFileStreamExample(string componentName, string keyName) : Example
{
public override string DisplayName => "Use Cryptography to encrypt and decrypt a file";
public override async Task RunAsync(CancellationToken cancellationToken)
{
using var client = new DaprClientBuilder().Build();
// The name of the file we're using as an example
const string fileName = "file.txt";
Console.WriteLine("Original file contents:");
foreach (var line in await File.ReadAllLinesAsync(fileName, cancellationToken))
{
Console.WriteLine(line);
}
//Encrypt from a file stream and buffer the resulting bytes to an in-memory buffer
await using var encryptFs = new FileStream(fileName, FileMode.Open);
var bufferedEncryptedBytes = new ArrayBufferWriter<byte>();
await foreach (var bytes in (client.EncryptAsync(componentName, encryptFs, keyName,
new EncryptionOptions(KeyWrapAlgorithm.Rsa), cancellationToken)))
{
bufferedEncryptedBytes.Write(bytes.Span);
}
Console.WriteLine("Encrypted bytes:");
Console.WriteLine(Convert.ToBase64String(bufferedEncryptedBytes.WrittenMemory.ToArray()));
//We'll write to a temporary file via a FileStream
var tempDecryptedFile = Path.GetTempFileName();
await using var decryptFs = new FileStream(tempDecryptedFile, FileMode.Create);
//We'll stream the decrypted bytes from a MemoryStream into the above temporary file
await using var encryptedMs = new MemoryStream(bufferedEncryptedBytes.WrittenMemory.ToArray());
await foreach (var result in (client.DecryptAsync(componentName, encryptedMs, keyName,
cancellationToken)))
{
decryptFs.Write(result.Span);
}
decryptFs.Close();
//Let's confirm the value as written to the file
var decryptedValue = await File.ReadAllTextAsync(tempDecryptedFile, cancellationToken);
Console.WriteLine("Decrypted value: ");
Console.WriteLine(decryptedValue);
//And some cleanup to delete our temp file
File.Delete(tempDecryptedFile);
}
}
}

View File

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

View File

@ -7,5 +7,6 @@
<OutputPath>$(RepoRoot)bin\$(Configuration)\examples\$(MSBuildProjectName)\</OutputPath>
<IsPackable>false</IsPackable>
<NoWarn>$(NoWarn);DAPR_CONVERSATION;DAPR_JOBS;DAPR_DISTRIBUTEDLOCK;DAPR_CRYPTOGRAPHY</NoWarn>
</PropertyGroup>
</Project>

View File

@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Diagnostics.CodeAnalysis;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1.Dapr;
namespace Dapr.AI.Conversation;
@ -30,6 +31,7 @@ namespace Dapr.AI.Conversation;
/// exhaustion and other problems.
/// </para>
/// </summary>
[Experimental("DAPR_CONVERSATION", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/conversation/conversation-overview/")]
public abstract class DaprConversationClient : DaprAIClient
{
/// <summary>

View File

@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Diagnostics.CodeAnalysis;
using Dapr.Common;
using Microsoft.Extensions.Configuration;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1.Dapr;
@ -21,6 +22,7 @@ namespace Dapr.AI.Conversation;
/// Used to create a new instance of a <see cref="DaprConversationClient"/>.
/// </summary>
/// <param name="configuration">An optional <see cref="IConfiguration"/> to configure the client with.</param>
[Experimental("DAPR_CONVERSATION", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/conversation/conversation-overview/")]
public sealed class DaprConversationClientBuilder(IConfiguration? configuration = null) : DaprGenericClientBuilder<DaprConversationClient>(configuration)
{
/// <summary>
@ -30,6 +32,7 @@ public sealed class DaprConversationClientBuilder(IConfiguration? configuration
/// <summary>
/// Builds the client instance from the properties of the builder.
/// </summary>
[Experimental("DAPR_CONVERSATION", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/conversation/conversation-overview/")]
public override DaprConversationClient Build()
{
var daprClientDependencies = BuildDaprClientDependencies(typeof(DaprConversationClient).Assembly);

View File

@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Diagnostics.CodeAnalysis;
using Dapr.Common;
using Dapr.Common.Extensions;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
@ -23,6 +24,7 @@ namespace Dapr.AI.Conversation;
/// <param name="client">The Dapr client.</param>
/// <param name="httpClient">The HTTP client used by the client for calling the Dapr runtime.</param>
/// <param name="daprApiToken">An optional token required to send requests to the Dapr sidecar.</param>
[Experimental("DAPR_CONVERSATION", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/conversation/conversation-overview/")]
internal sealed class DaprConversationGrpcClient(Autogenerated.Dapr.DaprClient client, HttpClient httpClient, string? daprApiToken = null) : DaprConversationClient(client, httpClient, daprApiToken: daprApiToken)
{
/// <summary>

View File

@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Diagnostics.CodeAnalysis;
using Dapr.Common.Extensions;
using Microsoft.Extensions.DependencyInjection;
@ -24,6 +25,7 @@ public static class DaprAiConversationBuilderExtensions
/// <summary>
/// Registers the necessary functionality for the Dapr AI Conversation functionality.
/// </summary>
[Experimental("DAPR_CONVERSATION", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/conversation/conversation-overview/")]
public static IDaprAiConversationBuilder AddDaprConversationClient(
this IServiceCollection services,
Action<IServiceProvider, DaprConversationClientBuilder>? configure = null,

View File

@ -68,13 +68,12 @@ internal static class DurationExtensions
if (period.StartsWith(MonthlyPrefixPeriod))
{
var dateTime = DateTime.UtcNow;
return dateTime.AddMonths(1) - dateTime;
return TimeSpan.FromDays(30);
}
if (period.StartsWith(MidnightPrefixPeriod))
{
return new TimeSpan();
return TimeSpan.Zero;
}
if (period.StartsWith(WeeklyPrefixPeriod))

View File

@ -220,14 +220,12 @@ internal sealed class ActorManager
}
}
internal async Task<bool> FireTimerAsync(
ActorId actorId,
Stream requestBodyStream,
CancellationToken cancellationToken = default)
internal async Task FireTimerAsync(ActorId actorId, Stream requestBodyStream, CancellationToken cancellationToken = default)
{
#pragma warning disable 0618
var timerData = await JsonSerializer.DeserializeAsync<TimerInfo>(requestBodyStream, cancellationToken: cancellationToken);
#pragma warning restore 0618
#pragma warning disable 0618
var timerData = await DeserializeAsync(requestBodyStream);
#pragma warning restore 0618
return await this.DispatchInternalAsync(actorId, this.timerMethodContext, RequestFunc, cancellationToken);
@ -255,6 +253,62 @@ internal sealed class ActorManager
}
}
#pragma warning disable 0618
internal static async Task<TimerInfo> DeserializeAsync(Stream stream)
{
var json = await JsonSerializer.DeserializeAsync<JsonElement>(stream);
if (json.ValueKind == JsonValueKind.Null)
{
return null;
}
var setAnyProperties = false; // Used to determine if anything was actually deserialized
var dueTime = TimeSpan.Zero;
var callback = "";
var period = TimeSpan.Zero;
var data = Array.Empty<byte>();
TimeSpan? ttl = null;
if (json.TryGetProperty("callback", out var callbackProperty))
{
setAnyProperties = true;
callback = callbackProperty.GetString();
}
if (json.TryGetProperty("dueTime", out var dueTimeProperty))
{
setAnyProperties = true;
var dueTimeString = dueTimeProperty.GetString();
dueTime = ConverterUtils.ConvertTimeSpanFromDaprFormat(dueTimeString);
}
if (json.TryGetProperty("period", out var periodProperty))
{
setAnyProperties = true;
var periodString = periodProperty.GetString();
(period, _) = ConverterUtils.ConvertTimeSpanValueFromISO8601Format(periodString);
}
if (json.TryGetProperty("data", out var dataProperty) && dataProperty.ValueKind != JsonValueKind.Null)
{
setAnyProperties = true;
data = dataProperty.GetBytesFromBase64();
}
if (json.TryGetProperty("ttl", out var ttlProperty))
{
setAnyProperties = true;
var ttlString = ttlProperty.GetString();
ttl = ConverterUtils.ConvertTimeSpanFromDaprFormat(ttlString);
}
if (!setAnyProperties)
{
return null; //No properties were ever deserialized, so return null instead of default values
}
return new TimerInfo(callback, data, dueTime, period, ttl);
}
#pragma warning restore 0618
internal async Task ActivateActorAsync(ActorId actorId)
{
// An actor is activated by "Dapr" runtime when a call is to be made for an actor.

View File

@ -49,7 +49,7 @@ internal static class ConverterUtils
int msIndex = spanOfValue.IndexOf("ms");
// handle days from hours.
var hoursSpan = spanOfValue.Slice(0, hIndex);
var hoursSpan = spanOfValue[..hIndex];
var hours = int.Parse(hoursSpan);
var days = hours / 24;
hours %= 24;
@ -103,7 +103,7 @@ internal static class ConverterUtils
builder.Append($"{value.Days}D");
}
builder.Append("T");
builder.Append('T');
if(value.Hours > 0)
{

View File

@ -0,0 +1,153 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Google.Protobuf;
using Grpc.Core;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
namespace Dapr.Client.Crypto;
/// <summary>
/// Provides the implementation to decrypt a stream of plaintext data with the Dapr runtime.
/// </summary>
internal sealed class DecryptionStreamProcessor : IDisposable
{
private bool disposed;
private readonly Channel<ReadOnlyMemory<byte>> outputChannel = Channel.CreateUnbounded<ReadOnlyMemory<byte>>();
/// <summary>
/// Surfaces any exceptions encountered while asynchronously processing the inbound and outbound streams.
/// </summary>
internal event EventHandler<Exception>? OnException;
/// <summary>
/// Sends the provided bytes in chunks to the sidecar for the encryption operation.
/// </summary>
/// <param name="inputStream">The stream containing the bytes to decrypt.</param>
/// <param name="call">The call to make to the sidecar to process the encryption operation.</param>
/// <param name="streamingBlockSizeInBytes">The size, in bytes, of the streaming blocks.</param>
/// <param name="options">The decryption options.</param>
/// <param name="cancellationToken">Token used to cancel the ongoing request.</param>
public async Task ProcessStreamAsync(
Stream inputStream,
AsyncDuplexStreamingCall<Autogenerated.DecryptRequest, Autogenerated.DecryptResponse> call,
int streamingBlockSizeInBytes,
Autogenerated.DecryptRequestOptions options,
CancellationToken cancellationToken)
{
//Read from the input stream and write to the gRPC call
_ = Task.Run(async () =>
{
try
{
await using var bufferedStream = new BufferedStream(inputStream, streamingBlockSizeInBytes);
var buffer = new byte[streamingBlockSizeInBytes];
int bytesRead;
ulong sequenceNumber = 0;
while ((bytesRead = await bufferedStream.ReadAsync(buffer, cancellationToken)) > 0)
{
var request = new Autogenerated.DecryptRequest
{
Payload = new Autogenerated.StreamPayload
{
Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
}
};
//Only include the options in the first message
if (sequenceNumber == 0)
{
request.Options = options;
}
await call.RequestStream.WriteAsync(request, cancellationToken);
//Increment the sequence number
sequenceNumber++;
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
// Expected cancellation exception
}
catch (Exception ex)
{
OnException?.Invoke(this, ex);
}
finally
{
await call.RequestStream.CompleteAsync();
}
}, cancellationToken);
//Start reading from the gRPC call and writing to the output channel
_ = Task.Run(async () =>
{
try
{
await foreach (var response in call.ResponseStream.ReadAllAsync(cancellationToken))
{
await outputChannel.Writer.WriteAsync(response.Payload.Data.Memory, cancellationToken);
}
}
catch (Exception ex)
{
OnException?.Invoke(this, ex);
}
finally
{
outputChannel.Writer.Complete();
}
}, cancellationToken);
}
/// <summary>
/// Retrieves the processed bytes from the operation from the sidecar and
/// returns as an enumerable stream.
/// </summary>
public async IAsyncEnumerable<ReadOnlyMemory<byte>> GetProcessedDataAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var data in outputChannel.Reader.ReadAllAsync(cancellationToken))
{
yield return data;
}
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
outputChannel.Writer.TryComplete();
}
disposed = true;
}
}
}

View File

@ -0,0 +1,154 @@
// ------------------------------------------------------------------------
// Copyright 2025 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Google.Protobuf;
using Grpc.Core;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
namespace Dapr.Client.Crypto;
/// <summary>
/// Provides the implementation to encrypt a stream of plaintext data with the Dapr runtime.
/// </summary>
internal sealed class EncryptionStreamProcessor : IDisposable
{
private bool disposed;
private readonly Channel<ReadOnlyMemory<byte>> outputChannel = Channel.CreateUnbounded<ReadOnlyMemory<byte>>();
/// <summary>
/// Surfaces any exceptions encountered while asynchronously processing the inbound and outbound streams.
/// </summary>
internal event EventHandler<Exception>? OnException;
/// <summary>
/// Sends the provided bytes in chunks to the sidecar for the encryption operation.
/// </summary>
/// <param name="inputStream">The stream containing the bytes to encrypt.</param>
/// <param name="call">The call to make to the sidecar to process the encryption operation.</param>
/// <param name="options">The encryption options.</param>
/// <param name="streamingBlockSizeInBytes">The size, in bytes, of the streaming blocks.</param>
/// <param name="cancellationToken">Token used to cancel the ongoing request.</param>
public async Task ProcessStreamAsync(
Stream inputStream,
AsyncDuplexStreamingCall<Autogenerated.EncryptRequest, Autogenerated.EncryptResponse> call,
Autogenerated.EncryptRequestOptions options,
int streamingBlockSizeInBytes,
CancellationToken cancellationToken)
{
//Read from the input stream and write to the gRPC call
_ = Task.Run(async () =>
{
try
{
await using var bufferedStream = new BufferedStream(inputStream, streamingBlockSizeInBytes);
var buffer = new byte[streamingBlockSizeInBytes];
int bytesRead;
ulong sequenceNumber = 0;
while ((bytesRead = await bufferedStream.ReadAsync(buffer, cancellationToken)) > 0)
{
var request = new Autogenerated.EncryptRequest
{
Payload = new Autogenerated.StreamPayload
{
Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
}
};
//Only include the options in the first message
if (sequenceNumber == 0)
{
request.Options = options;
}
await call.RequestStream.WriteAsync(request, cancellationToken);
//Increment the sequence number
sequenceNumber++;
}
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
// Expected cancellation exception
}
catch (Exception ex)
{
OnException?.Invoke(this, ex);
}
finally
{
await call.RequestStream.CompleteAsync();
}
}, cancellationToken);
//Start reading from the gRPC call and writing to the output channel
_ = Task.Run(async () =>
{
try
{
await foreach (var response in call.ResponseStream.ReadAllAsync(cancellationToken))
{
await outputChannel.Writer.WriteAsync(response.Payload.Data.Memory, cancellationToken);
}
}
catch (Exception ex)
{
OnException?.Invoke(this, ex);
}
finally
{
outputChannel.Writer.Complete();
}
}, cancellationToken);
}
/// <summary>
/// Retrieves the processed bytes from the operation from the sidecar and
/// returns as an enumerable stream.
/// </summary>
public async IAsyncEnumerable<ReadOnlyMemory<byte>> GetProcessedDataAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var data in outputChannel.Reader.ReadAllAsync(cancellationToken))
{
yield return data;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
outputChannel.Writer.TryComplete();
}
disposed = true;
}
}
}

View File

@ -14,6 +14,7 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net.Http;
@ -1089,7 +1090,7 @@ public abstract class DaprClient : IDisposable
/// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of encrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public abstract Task<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName,
ReadOnlyMemory<byte> plaintextBytes, string keyName, EncryptionOptions encryptionOptions,
CancellationToken cancellationToken = default);
@ -1103,8 +1104,8 @@ public abstract class DaprClient : IDisposable
/// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of encrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract Task<IAsyncEnumerable<ReadOnlyMemory<byte>>> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName,
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public abstract IAsyncEnumerable<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName,
EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default);
/// <summary>
@ -1116,7 +1117,7 @@ public abstract class DaprClient : IDisposable
/// <param name="options">Options informing how the decryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of decrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public abstract Task<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName, ReadOnlyMemory<byte> ciphertextBytes, string keyName, DecryptionOptions options,
CancellationToken cancellationToken = default);
@ -1128,7 +1129,7 @@ public abstract class DaprClient : IDisposable
/// <param name="keyName">The name of the key to use from the Vault for the decryption operation.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of decrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public abstract Task<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName,
ReadOnlyMemory<byte> ciphertextBytes, string keyName, CancellationToken cancellationToken = default);
@ -1141,9 +1142,8 @@ public abstract class DaprClient : IDisposable
/// <param name="options">Options informing how the decryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An asynchronously enumerable array of decrypted bytes.</returns>
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract Task<IAsyncEnumerable<ReadOnlyMemory<byte>>> DecryptAsync(string vaultResourceName, Stream ciphertextStream,
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public abstract IAsyncEnumerable<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName, Stream ciphertextStream,
string keyName, DecryptionOptions options, CancellationToken cancellationToken = default);
/// <summary>
@ -1154,9 +1154,8 @@ public abstract class DaprClient : IDisposable
/// <param name="keyName">The name of the key to use from the Vault for the decryption operation.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An asynchronously enumerable array of decrypted bytes.</returns>
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract Task<IAsyncEnumerable<ReadOnlyMemory<byte>>> DecryptAsync(string vaultResourceName, Stream ciphertextStream,
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public abstract IAsyncEnumerable<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName, Stream ciphertextStream,
string keyName, CancellationToken cancellationToken = default);
#endregion
@ -1171,7 +1170,7 @@ public abstract class DaprClient : IDisposable
///// <param name="keyFormat">The format to use for the key result.</param>
///// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
///// <returns>The name (and possibly version as name/version) of the key and its public key.</returns>
//[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public abstract Task<(string Name, string PublicKey)> GetKeyAsync(string vaultResourceName, string keyName, SubtleGetKeyRequest.Types.KeyFormat keyFormat,
// CancellationToken cancellationToken = default);
@ -1186,7 +1185,7 @@ public abstract class DaprClient : IDisposable
///// <param name="associatedData">Any associated data when using AEAD ciphers.</param>
///// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
///// <returns>The array of encrypted bytes.</returns>
//[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public abstract Task<(byte[] CipherTextBytes, byte[] AuthenticationTag)> EncryptAsync(
// string vaultResourceName,
// byte[] plainTextBytes,
@ -1206,7 +1205,7 @@ public abstract class DaprClient : IDisposable
///// <param name="nonce">The bytes comprising the nonce.</param>
///// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
///// <returns>The array of encrypted bytes.</returns>
//[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public async Task<(byte[] CipherTextBytes, byte[] AuthenticationTag)> EncryptAsync(
// string vaultResourceName,
// byte[] plainTextBytes,
@ -1229,7 +1228,7 @@ public abstract class DaprClient : IDisposable
///// <param name="tag"></param>
///// <param name="associatedData">Any associated data when using AEAD ciphers.</param>
///// <returns>The array of plaintext bytes.</returns>
//[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public abstract Task<byte[]> DecryptAsync(string vaultResourceName, byte[] cipherTextBytes,
// string algorithm, string keyName, byte[] nonce, byte[] tag, byte[] associatedData,
// CancellationToken cancellationToken = default);
@ -1245,8 +1244,7 @@ public abstract class DaprClient : IDisposable
///// <param name="nonce">The nonce value used.</param>
///// <param name="tag"></param>
///// <returns>The array of plaintext bytes.</returns>
//[Obsolete(
// "The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public async Task<byte[]> DecryptAsync(string vaultResourceName, byte[] cipherTextBytes,
// string algorithm, string keyName, byte[] nonce, byte[] tag, CancellationToken cancellationToken = default) =>
// await DecryptAsync(vaultResourceName, cipherTextBytes, algorithm, keyName, nonce, tag, Array.Empty<byte>(), cancellationToken);
@ -1262,7 +1260,7 @@ public abstract class DaprClient : IDisposable
///// <param name="associatedData">Any associated data when using AEAD ciphers.</param>
///// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
///// <returns>The bytes comprising the wrapped plain-text key and the authentication tag, if applicable.</returns>
//[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public abstract Task<(byte[] WrappedKey, byte[] AuthenticationTag)> WrapKeyAsync(string vaultResourceName, byte[] plainTextKey, string keyName, string algorithm, byte[] nonce, byte[] associatedData,
// CancellationToken cancellationToken = default);
@ -1276,7 +1274,7 @@ public abstract class DaprClient : IDisposable
///// <param name="nonce">The none used.</param>
///// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
///// <returns>The bytes comprising the unwrapped key and the authentication tag, if applicable.</returns>
//[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public async Task<(byte[] WrappedKey, byte[] AuthenticationTag)> WrapKeyAsync(string vaultResourceName, byte[] plainTextKey, string keyName, string algorithm,
// byte[] nonce, CancellationToken cancellationToken = default) => await WrapKeyAsync(vaultResourceName, plainTextKey,
// keyName, algorithm, nonce, Array.Empty<byte>(), cancellationToken);
@ -1293,7 +1291,7 @@ public abstract class DaprClient : IDisposable
///// <param name="associatedData">Any associated data when using AEAD ciphers.</param>
///// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
///// <returns>The bytes comprising the unwrapped key.</returns>
//[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public abstract Task<byte[]> UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm, string keyName, byte[] nonce, byte[] tag, byte[] associatedData,
// CancellationToken cancellationToken = default);
@ -1308,7 +1306,7 @@ public abstract class DaprClient : IDisposable
///// <param name="tag">The bytes comprising the authentication tag.</param>
///// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
///// <returns>The bytes comprising the unwrapped key.</returns>
//[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public async Task<byte[]> UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm, string keyName,
// byte[] nonce, byte[] tag,
// CancellationToken cancellationToken = default) => await UnwrapKeyAsync(vaultResourceName,
@ -1324,7 +1322,7 @@ public abstract class DaprClient : IDisposable
///// <param name="nonce">The nonce value.</param>
///// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
///// <returns>The bytes comprising the unwrapped key.</returns>
//[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public async Task<byte[]> UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm, string keyName,
// byte[] nonce, CancellationToken cancellationToken = default) => await UnwrapKeyAsync(vaultResourceName,
// wrappedKey, algorithm, keyName, nonce, Array.Empty<byte>(), Array.Empty<byte>(), cancellationToken);
@ -1338,7 +1336,7 @@ public abstract class DaprClient : IDisposable
///// <param name="keyName">The name of the key used.</param>
///// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
///// <returns>The bytes comprising the signature.</returns>
//[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public abstract Task<byte[]> SignAsync(string vaultResourceName, byte[] digest, string algorithm, string keyName,
// CancellationToken cancellationToken = default);
@ -1352,12 +1350,14 @@ public abstract class DaprClient : IDisposable
///// <param name="keyName">The name of the key used.</param>
///// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
///// <returns><c>True</c> if the signature verification is successful; otherwise <c>false</c>.</returns>
//[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public abstract Task<bool> VerifyAsync(string vaultResourceName, byte[] digest, byte[] signature, string algorithm, string keyName,
// CancellationToken cancellationToken = default);
#endregion
#region Distributed Lock
/// <summary>
/// Attempt to lock the given resourceId with response indicating success.
/// </summary>
@ -1367,7 +1367,7 @@ public abstract class DaprClient : IDisposable
/// <param name="expiryInSeconds">The time after which the lock gets expired.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> that can be used to cancel the operation.</param>
/// <returns>A <see cref="Task"/> containing a <see cref="TryLockResponse"/></returns>
[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
[Experimental("DAPR_DISTRIBUTEDLOCK", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/")]
public abstract Task<TryLockResponse> Lock(
string storeName,
string resourceId,
@ -1384,12 +1384,14 @@ public abstract class DaprClient : IDisposable
/// <param name="lockOwner">Indicates the identifier of lock owner.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> that can be used to cancel the operation.</param>
/// <returns>A <see cref="Task"/> containing a <see cref="UnlockResponse"/></returns>
[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
[Experimental("DAPR_DISTRIBUTEDLOCK", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/")]
public abstract Task<UnlockResponse> Unlock(
string storeName,
string resourceId,
string lockOwner,
CancellationToken cancellationToken = default);
#endregion
/// <inheritdoc />
public void Dispose()

View File

@ -13,6 +13,7 @@
namespace Dapr.Client;
using Crypto;
using System;
using System.Buffers;
using System.Collections.Generic;
@ -28,6 +29,7 @@ using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using Grpc.Net.Client;
using System.Diagnostics.CodeAnalysis;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
/// <summary>
@ -1666,19 +1668,17 @@ internal class DaprClientGrpc : DaprClient
#region Cryptography
/// <inheritdoc />
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public override async Task<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName,
ReadOnlyMemory<byte> plaintextBytes, string keyName, EncryptionOptions encryptionOptions,
CancellationToken cancellationToken = default)
{
using var memoryStream = plaintextBytes.CreateMemoryStream(true);
var encryptionResult =
await EncryptAsync(vaultResourceName, memoryStream, keyName, encryptionOptions, cancellationToken);
var encryptionResult = EncryptAsync(vaultResourceName, memoryStream, keyName, encryptionOptions, cancellationToken);
var bufferedResult = new ArrayBufferWriter<byte>();
await foreach (var item in encryptionResult.WithCancellation(cancellationToken))
await foreach (var item in encryptionResult)
{
bufferedResult.Write(item.Span);
}
@ -1687,16 +1687,18 @@ internal class DaprClientGrpc : DaprClient
}
/// <inheritdoc />
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public override async Task<IAsyncEnumerable<ReadOnlyMemory<byte>>> EncryptAsync(string vaultResourceName,
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public override async IAsyncEnumerable<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName,
Stream plaintextStream,
string keyName, EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default)
string keyName, EncryptionOptions encryptionOptions,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName));
ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName));
ArgumentVerifier.ThrowIfNull(plaintextStream, nameof(plaintextStream));
ArgumentVerifier.ThrowIfNull(encryptionOptions, nameof(encryptionOptions));
EventHandler<Exception> exceptionHandler = (_, ex) => throw ex;
var shouldOmitDecryptionKeyName =
string.IsNullOrWhiteSpace(encryptionOptions
@ -1719,185 +1721,88 @@ internal class DaprClientGrpc : DaprClient
}
var options = CreateCallOptions(headers: null, cancellationToken);
var duplexStream = client.EncryptAlpha1(options);
var duplexStream = Client.EncryptAlpha1(options);
//Run both operations at the same time, but return the output of the streaming values coming from the operation
var receiveResult = Task.FromResult(RetrieveEncryptedStreamAsync(duplexStream, cancellationToken));
return await Task.WhenAll(
//Stream the plaintext data to the sidecar in chunks
SendPlaintextStreamAsync(plaintextStream, encryptionOptions.StreamingBlockSizeInBytes,
duplexStream, encryptRequestOptions, cancellationToken),
//At the same time, retrieve the encrypted response from the sidecar
receiveResult).ContinueWith(_ => receiveResult.Result, cancellationToken);
}
/// <summary>
/// Sends the plaintext bytes in chunks to the sidecar to be encrypted.
/// </summary>
private async Task SendPlaintextStreamAsync(Stream plaintextStream,
int streamingBlockSizeInBytes,
AsyncDuplexStreamingCall<Autogenerated.EncryptRequest, Autogenerated.EncryptResponse> duplexStream,
Autogenerated.EncryptRequestOptions encryptRequestOptions,
CancellationToken cancellationToken)
{
//Start with passing the metadata about the encryption request itself in the first message
await duplexStream.RequestStream.WriteAsync(
new Autogenerated.EncryptRequest { Options = encryptRequestOptions }, cancellationToken);
//Send the plaintext bytes in blocks in subsequent messages
await using (var bufferedStream = new BufferedStream(plaintextStream, streamingBlockSizeInBytes))
using var streamProcessor = new EncryptionStreamProcessor();
try
{
var buffer = new byte[streamingBlockSizeInBytes];
int bytesRead;
ulong sequenceNumber = 0;
streamProcessor.OnException += exceptionHandler;
await streamProcessor.ProcessStreamAsync(plaintextStream, duplexStream, encryptRequestOptions,
encryptionOptions.StreamingBlockSizeInBytes,
cancellationToken);
while ((bytesRead =
await bufferedStream.ReadAsync(buffer.AsMemory(0, streamingBlockSizeInBytes),
cancellationToken)) !=
0)
await foreach (var value in streamProcessor.GetProcessedDataAsync(cancellationToken))
{
await duplexStream.RequestStream.WriteAsync(
new Autogenerated.EncryptRequest
{
Payload = new Autogenerated.StreamPayload
{
Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
}
}, cancellationToken);
//Increment the sequence number
sequenceNumber++;
yield return value;
}
}
//Send the completion message
await duplexStream.RequestStream.CompleteAsync();
}
/// <summary>
/// Retrieves the encrypted bytes from the encryption operation on the sidecar and returns as an enumerable stream.
/// </summary>
private async IAsyncEnumerable<ReadOnlyMemory<byte>> RetrieveEncryptedStreamAsync(
AsyncDuplexStreamingCall<Autogenerated.EncryptRequest, Autogenerated.EncryptResponse> duplexStream,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var encryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken)
.ConfigureAwait(false))
finally
{
yield return encryptResponse.Payload.Data.Memory;
streamProcessor.OnException -= exceptionHandler;
}
}
/// <inheritdoc />
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public override async Task<IAsyncEnumerable<ReadOnlyMemory<byte>>> DecryptAsync(string vaultResourceName,
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public override async IAsyncEnumerable<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName,
Stream ciphertextStream, string keyName,
DecryptionOptions decryptionOptions, CancellationToken cancellationToken = default)
DecryptionOptions decryptionOptions,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName));
ArgumentVerifier.ThrowIfNullOrEmpty(keyName, nameof(keyName));
ArgumentVerifier.ThrowIfNull(ciphertextStream, nameof(ciphertextStream));
ArgumentVerifier.ThrowIfNull(decryptionOptions, nameof(decryptionOptions));
decryptionOptions ??= new DecryptionOptions();
EventHandler<Exception> exceptionHandler = (_, ex) => throw ex;
var decryptRequestOptions = new Autogenerated.DecryptRequestOptions
{
ComponentName = vaultResourceName, KeyName = keyName
ComponentName = vaultResourceName,
KeyName = keyName
};
var options = CreateCallOptions(headers: null, cancellationToken);
var duplexStream = client.DecryptAlpha1(options);
//Run both operations at the same time, but return the output of the streaming values coming from the operation
var receiveResult = Task.FromResult(RetrieveDecryptedStreamAsync(duplexStream, cancellationToken));
return await Task.WhenAll(
//Stream the ciphertext data to the sidecar in chunks
SendCiphertextStreamAsync(ciphertextStream, decryptionOptions.StreamingBlockSizeInBytes,
duplexStream, decryptRequestOptions, cancellationToken),
//At the same time, retrieve the decrypted response from the sidecar
receiveResult)
//Return only the result of the `RetrieveEncryptedStreamAsync` method
.ContinueWith(_ => receiveResult.Result, cancellationToken);
using var streamProcessor = new DecryptionStreamProcessor();
try
{
streamProcessor.OnException += exceptionHandler;
await streamProcessor.ProcessStreamAsync(ciphertextStream, duplexStream, decryptionOptions.StreamingBlockSizeInBytes,
decryptRequestOptions,
cancellationToken);
await foreach (var value in streamProcessor.GetProcessedDataAsync(cancellationToken))
{
yield return value;
}
}
finally
{
streamProcessor.OnException -= exceptionHandler;
}
}
/// <inheritdoc />
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public override Task<IAsyncEnumerable<ReadOnlyMemory<byte>>> DecryptAsync(string vaultResourceName,
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public override IAsyncEnumerable<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName,
Stream ciphertextStream, string keyName, CancellationToken cancellationToken = default) =>
DecryptAsync(vaultResourceName, ciphertextStream, keyName, new DecryptionOptions(),
cancellationToken);
/// <summary>
/// Sends the ciphertext bytes in chunks to the sidecar to be decrypted.
/// </summary>
private async Task SendCiphertextStreamAsync(Stream ciphertextStream,
int streamingBlockSizeInBytes,
AsyncDuplexStreamingCall<Autogenerated.DecryptRequest, Autogenerated.DecryptResponse> duplexStream,
Autogenerated.DecryptRequestOptions decryptRequestOptions,
CancellationToken cancellationToken)
{
//Start with passing the metadata about the decryption request itself in the first message
await duplexStream.RequestStream.WriteAsync(
new Autogenerated.DecryptRequest { Options = decryptRequestOptions }, cancellationToken);
//Send the ciphertext bytes in blocks in subsequent messages
await using (var bufferedStream = new BufferedStream(ciphertextStream, streamingBlockSizeInBytes))
{
var buffer = new byte[streamingBlockSizeInBytes];
int bytesRead;
ulong sequenceNumber = 0;
while ((bytesRead =
await bufferedStream.ReadAsync(buffer.AsMemory(0, streamingBlockSizeInBytes),
cancellationToken)) != 0)
{
await duplexStream.RequestStream.WriteAsync(
new Autogenerated.DecryptRequest
{
Payload = new Autogenerated.StreamPayload
{
Data = ByteString.CopyFrom(buffer, 0, bytesRead), Seq = sequenceNumber
}
}, cancellationToken);
//Increment the sequence number
sequenceNumber++;
}
}
//Send the completion message
await duplexStream.RequestStream.CompleteAsync();
}
/// <summary>
/// Retrieves the decrypted bytes from the decryption operation on the sidecar and returns as an enumerable stream.
/// </summary>
private async IAsyncEnumerable<ReadOnlyMemory<byte>> RetrieveDecryptedStreamAsync(
AsyncDuplexStreamingCall<Autogenerated.DecryptRequest, Autogenerated.DecryptResponse> duplexStream,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var decryptResponse in duplexStream.ResponseStream.ReadAllAsync(cancellationToken)
.ConfigureAwait(false))
{
yield return decryptResponse.Payload.Data.Memory;
}
}
/// <inheritdoc />
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public override async Task<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName,
ReadOnlyMemory<byte> ciphertextBytes, string keyName, DecryptionOptions decryptionOptions,
CancellationToken cancellationToken = default)
{
using var memoryStream = ciphertextBytes.CreateMemoryStream(true);
var decryptionResult =
await DecryptAsync(vaultResourceName, memoryStream, keyName, decryptionOptions, cancellationToken);
var decryptionResult = DecryptAsync(vaultResourceName, memoryStream, keyName, decryptionOptions, cancellationToken);
var bufferedResult = new ArrayBufferWriter<byte>();
await foreach (var item in decryptionResult.WithCancellation(cancellationToken))
await foreach (var item in decryptionResult)
{
bufferedResult.Write(item.Span);
}
@ -1906,8 +1811,7 @@ internal class DaprClientGrpc : DaprClient
}
/// <inheritdoc />
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public override async Task<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName,
ReadOnlyMemory<byte> ciphertextBytes, string keyName, CancellationToken cancellationToken = default) =>
await DecryptAsync(vaultResourceName, ciphertextBytes, keyName,
@ -1916,7 +1820,7 @@ internal class DaprClientGrpc : DaprClient
#region Subtle Crypto Implementation
///// <inheritdoc/>
//[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public override async Task<(string Name, string PublicKey)> GetKeyAsync(string vaultResourceName, string keyName, Autogenerated.SubtleGetKeyRequest.Types.KeyFormat keyFormat,
// CancellationToken cancellationToken = default)
//{
@ -1945,7 +1849,7 @@ internal class DaprClientGrpc : DaprClient
//}
///// <inheritdoc/>
//[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public override async Task<(byte[] CipherTextBytes, byte[] AuthenticationTag)> EncryptAsync(string vaultResourceName, byte[] plainTextBytes, string algorithm,
// string keyName, byte[] nonce, byte[] associatedData, CancellationToken cancellationToken = default)
//{
@ -1981,7 +1885,7 @@ internal class DaprClientGrpc : DaprClient
//}
///// <inheritdoc/>
//[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public override async Task<byte[]> DecryptAsync(string vaultResourceName, byte[] cipherTextBytes, string algorithm, string keyName, byte[] nonce, byte[] tag,
// byte[] associatedData, CancellationToken cancellationToken = default)
//{
@ -2017,7 +1921,7 @@ internal class DaprClientGrpc : DaprClient
//}
///// <inheritdoc/>
//[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public override async Task<(byte[] WrappedKey, byte[] AuthenticationTag)> WrapKeyAsync(string vaultResourceName, byte[] plainTextKey, string keyName,
// string algorithm, byte[] nonce, byte[] associatedData, CancellationToken cancellationToken = default)
//{
@ -2053,7 +1957,7 @@ internal class DaprClientGrpc : DaprClient
//}
///// <inheritdoc/>
//[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public override async Task<byte[]> UnwrapKeyAsync(string vaultResourceName, byte[] wrappedKey, string algorithm,
// string keyName, byte[] nonce, byte[] tag, byte[] associatedData, CancellationToken cancellationToken = default)
//{
@ -2090,7 +1994,7 @@ internal class DaprClientGrpc : DaprClient
//}
///// <inheritdoc/>
//[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public override async Task<byte[]> SignAsync(string vaultResourceName, byte[] digest, string algorithm, string keyName, CancellationToken cancellationToken = default)
//{
// ArgumentVerifier.ThrowIfNullOrEmpty(vaultResourceName, nameof(vaultResourceName));
@ -2123,7 +2027,7 @@ internal class DaprClientGrpc : DaprClient
//}
///// <inheritdoc/>
//[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
//[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
//public override async Task<bool> VerifyAsync(string vaultResourceName, byte[] digest, byte[] signature,
// string algorithm, string keyName, CancellationToken cancellationToken = default)
//{
@ -2165,7 +2069,7 @@ internal class DaprClientGrpc : DaprClient
#region Distributed Lock API
/// <inheritdoc/>
[Obsolete]
[Experimental("DAPR_DISTRIBUTEDLOCK", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/")]
public async override Task<TryLockResponse> Lock(
string storeName,
string resourceId,
@ -2205,7 +2109,7 @@ internal class DaprClientGrpc : DaprClient
}
/// <inheritdoc/>
[Obsolete]
[Experimental("DAPR_DISTRIBUTEDLOCK", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/")]
public async override Task<UnlockResponse> Unlock(
string storeName,
string resourceId,

View File

@ -12,6 +12,7 @@
// ------------------------------------------------------------------------
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
namespace Dapr.Client;
@ -19,7 +20,7 @@ namespace Dapr.Client;
/// <summary>
/// Class representing the response from a Lock API call.
/// </summary>
[Obsolete("This API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
[Experimental("DAPR_DISTRIBUTEDLOCK", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/distributed-lock-api-overview/")]
public sealed class TryLockResponse : IAsyncDisposable
{
/// <summary>

View File

@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Diagnostics.CodeAnalysis;
using Dapr.Cryptography.Encryption.Models;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1.Dapr;
@ -31,6 +32,7 @@ namespace Dapr.Cryptography.Encryption;
/// exhaustion and other problems.
/// </para>
/// </summary>
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public abstract class DaprEncryptionClient(Autogenerated.DaprClient client, HttpClient httpClient, string? daprApiToken = null) : IDaprEncryptionClient
{
private bool disposed;
@ -68,7 +70,6 @@ public abstract class DaprEncryptionClient(Autogenerated.DaprClient client, Http
/// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of encrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract Task<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName,
ReadOnlyMemory<byte> plaintextBytes, string keyName, EncryptionOptions encryptionOptions,
CancellationToken cancellationToken = default);
@ -82,7 +83,6 @@ public abstract class DaprEncryptionClient(Autogenerated.DaprClient client, Http
/// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of encrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract IAsyncEnumerable<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName,
EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default);
@ -95,7 +95,6 @@ public abstract class DaprEncryptionClient(Autogenerated.DaprClient client, Http
/// <param name="options">Options informing how the decryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of decrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract Task<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName, ReadOnlyMemory<byte> ciphertextBytes, string keyName, DecryptionOptions? options = null,
CancellationToken cancellationToken = default);
@ -108,8 +107,6 @@ public abstract class DaprEncryptionClient(Autogenerated.DaprClient client, Http
/// <param name="options">Options informing how the decryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An asynchronously enumerable array of decrypted bytes.</returns>
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract IAsyncEnumerable<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName, Stream ciphertextStream,
string keyName, DecryptionOptions? options = null, CancellationToken cancellationToken = default);

View File

@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Diagnostics.CodeAnalysis;
using Dapr.Common;
using Microsoft.Extensions.Configuration;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
@ -21,6 +22,7 @@ namespace Dapr.Cryptography.Encryption;
/// Builds a <see cref="DaprEncryptionClient"/>.
/// </summary>
/// <param name="configuration">An optional instance of <see cref="IConfiguration"/>.</param>
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public sealed class DaprEncryptionClientBuilder(IConfiguration? configuration = null) : DaprGenericClientBuilder<DaprEncryptionClient>(configuration)
{
/// <summary>

View File

@ -12,6 +12,7 @@
// ------------------------------------------------------------------------
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Dapr.Common;
using Dapr.Cryptography.Extensions;
@ -24,6 +25,7 @@ namespace Dapr.Cryptography.Encryption;
/// <summary>
/// A client for performing cryptography operations with Dapr.
/// </summary>
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
internal sealed class DaprEncryptionGrpcClient(Autogenerated.Dapr.DaprClient client, HttpClient httpClient, string? daprApiToken = null) : DaprEncryptionClient(client, httpClient, daprApiToken: daprApiToken)
{
/// <summary>
@ -35,7 +37,6 @@ internal sealed class DaprEncryptionGrpcClient(Autogenerated.Dapr.DaprClient cli
/// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of encrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public override async Task<ReadOnlyMemory<byte>> EncryptAsync(
string vaultResourceName,
ReadOnlyMemory<byte> plaintextBytes,
@ -64,8 +65,6 @@ internal sealed class DaprEncryptionGrpcClient(Autogenerated.Dapr.DaprClient cli
/// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of encrypted bytes.</returns>
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public override async IAsyncEnumerable<ReadOnlyMemory<byte>> EncryptAsync(
string vaultResourceName,
Stream plaintextStream,
@ -132,7 +131,6 @@ internal sealed class DaprEncryptionGrpcClient(Autogenerated.Dapr.DaprClient cli
/// <param name="options">Options informing how the decryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of decrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public override async Task<ReadOnlyMemory<byte>> DecryptAsync(
string vaultResourceName,
ReadOnlyMemory<byte> ciphertextBytes,
@ -161,8 +159,6 @@ internal sealed class DaprEncryptionGrpcClient(Autogenerated.Dapr.DaprClient cli
/// <param name="options">Options informing how the decryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An asynchronously enumerable array of decrypted bytes.</returns>
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public override async IAsyncEnumerable<ReadOnlyMemory<byte>> DecryptAsync(
string vaultResourceName,
Stream ciphertextStream,

View File

@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Diagnostics.CodeAnalysis;
using Dapr.Common.Extensions;
using Microsoft.Extensions.DependencyInjection;
@ -19,6 +20,7 @@ namespace Dapr.Cryptography.Encryption.Extensions;
/// <summary>
/// Contains extension methods for using Dapr cryptography with dependency injection.
/// </summary>
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public static class DaprCryptographyServiceCollectionExtensions
{
/// <summary>

View File

@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Diagnostics.CodeAnalysis;
using Dapr.Common;
using Dapr.Cryptography.Encryption.Models;
@ -19,6 +20,7 @@ namespace Dapr.Cryptography.Encryption;
/// <summary>
/// Provides the implementation shape for the Dapr encryption client.
/// </summary>
[Experimental("DAPR_CRYPTOGRAPHY", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/cryptography/cryptography-overview/")]
public interface IDaprEncryptionClient : IDaprClient
{
/// <summary>
@ -30,7 +32,6 @@ public interface IDaprEncryptionClient : IDaprClient
/// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of encrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public Task<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName,
ReadOnlyMemory<byte> plaintextBytes, string keyName, EncryptionOptions encryptionOptions,
CancellationToken cancellationToken = default);
@ -44,7 +45,6 @@ public interface IDaprEncryptionClient : IDaprClient
/// <param name="encryptionOptions">Options informing how the encryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of encrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public IAsyncEnumerable<ReadOnlyMemory<byte>> EncryptAsync(string vaultResourceName, Stream plaintextStream, string keyName,
EncryptionOptions encryptionOptions, CancellationToken cancellationToken = default);
@ -57,7 +57,6 @@ public interface IDaprEncryptionClient : IDaprClient
/// <param name="options">Options informing how the decryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An array of decrypted bytes.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public Task<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName, ReadOnlyMemory<byte> ciphertextBytes, string keyName, DecryptionOptions? options = null,
CancellationToken cancellationToken = default);
@ -70,8 +69,6 @@ public interface IDaprEncryptionClient : IDaprClient
/// <param name="options">Options informing how the decryption operation should be configured.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the operation.</param>
/// <returns>An asynchronously enumerable array of decrypted bytes.</returns>
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public IAsyncEnumerable<ReadOnlyMemory<byte>> DecryptAsync(string vaultResourceName, Stream ciphertextStream,
string keyName, DecryptionOptions? options = null, CancellationToken cancellationToken = default);
}

View File

@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Diagnostics.CodeAnalysis;
using Dapr.Common;
using Dapr.Jobs.Models;
using Dapr.Jobs.Models.Responses;
@ -33,6 +34,7 @@ namespace Dapr.Jobs;
/// exhaustion and other problems.
/// </para>
/// </summary>
[Experimental("DAPR_JOBS", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/jobs/jobs-overview/")]
public abstract class DaprJobsClient(Autogenerated.DaprClient client, HttpClient httpClient, string? daprApiToken = null) : IDaprClient
{
private bool disposed;
@ -74,8 +76,6 @@ public abstract class DaprJobsClient(Autogenerated.DaprClient client, HttpClient
/// to require that an existing job with the same name be deleted first.</param>
/// <param name="failurePolicyOptions">The characteristics of the policy to apply when a job fails to trigger.</param>
/// <param name="cancellationToken">Cancellation token.</param>
[Obsolete(
"The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract Task ScheduleJobAsync(string jobName, DaprJobSchedule schedule,
ReadOnlyMemory<byte>? payload = null, DateTimeOffset? startingFrom = null, int? repeats = null,
DateTimeOffset? ttl = null, bool overwrite = false, IJobFailurePolicyOptions? failurePolicyOptions = null,
@ -87,7 +87,6 @@ public abstract class DaprJobsClient(Autogenerated.DaprClient client, HttpClient
/// <param name="jobName">The jobName of the job.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The details comprising the job.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract Task<DaprJobDetails> GetJobAsync(string jobName, CancellationToken cancellationToken = default);
/// <summary>
@ -95,7 +94,6 @@ public abstract class DaprJobsClient(Autogenerated.DaprClient client, HttpClient
/// </summary>
/// <param name="jobName">The jobName of the job.</param>
/// <param name="cancellationToken">Cancellation token.</param>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public abstract Task DeleteJobAsync(string jobName, CancellationToken cancellationToken = default);
internal static KeyValuePair<string, string>? GetDaprApiTokenHeader(string apiToken)

View File

@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Diagnostics.CodeAnalysis;
using Dapr.Common;
using Microsoft.Extensions.Configuration;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;
@ -21,6 +22,7 @@ namespace Dapr.Jobs;
/// Builds a <see cref="DaprJobsClient"/>.
/// </summary>
/// <param name="configuration">An optional instance of <see cref="IConfiguration"/>.</param>
[Experimental("DAPR_JOBS", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/jobs/jobs-overview/")]
public sealed class DaprJobsClientBuilder(IConfiguration? configuration = null) : DaprGenericClientBuilder<DaprJobsClient>(configuration)
{
/// <summary>

View File

@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Diagnostics.CodeAnalysis;
using Dapr.Common;
using Dapr.Jobs.Models;
using Dapr.Jobs.Models.Responses;
@ -24,6 +25,7 @@ namespace Dapr.Jobs;
/// <summary>
/// A client for interacting with the Dapr endpoints.
/// </summary>
[Experimental("DAPR_JOBS", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/jobs/jobs-overview/")]
internal sealed class DaprJobsGrpcClient(Autogenerated.Dapr.DaprClient client, HttpClient httpClient, string? daprApiToken = null) : DaprJobsClient(client, httpClient, daprApiToken: daprApiToken)
{
/// <summary>
@ -38,7 +40,6 @@ internal sealed class DaprJobsGrpcClient(Autogenerated.Dapr.DaprClient client, H
/// <param name="overwrite">A flag indicating whether the job should be overwritten when submitted (true); otherwise false to require that an existing job with the same name be deleted first.</param>
/// <param name="failurePolicyOptions">The characteristics of the policy to apply when a job fails to trigger.</param>
/// <param name="cancellationToken">Cancellation token.</param>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public override async Task ScheduleJobAsync(string jobName, DaprJobSchedule schedule,
ReadOnlyMemory<byte>? payload = null, DateTimeOffset? startingFrom = null, int? repeats = null,
DateTimeOffset? ttl = null, bool overwrite = false, IJobFailurePolicyOptions? failurePolicyOptions = null,
@ -157,7 +158,6 @@ internal sealed class DaprJobsGrpcClient(Autogenerated.Dapr.DaprClient client, H
/// <param name="jobName">The name of the job.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The details comprising the job.</returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public override async Task<DaprJobDetails> GetJobAsync(string jobName, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(jobName))
@ -170,17 +170,7 @@ internal sealed class DaprJobsGrpcClient(Autogenerated.Dapr.DaprClient client, H
var envelope = new Autogenerated.GetJobRequest { Name = jobName };
var grpcCallOptions = DaprClientUtilities.ConfigureGrpcCallOptions(typeof(DaprJobsClient).Assembly, this.DaprApiToken, cancellationToken);
var response = await Client.GetJobAlpha1Async(envelope, grpcCallOptions);
var schedule = DateTime.TryParse(response.Job.DueTime, out var dueTime)
? DaprJobSchedule.FromDateTime(dueTime)
: new DaprJobSchedule(response.Job.Schedule);
return new DaprJobDetails(schedule)
{
DueTime = !string.IsNullOrWhiteSpace(response.Job.DueTime) ? DateTime.Parse(response.Job.DueTime) : null,
Ttl = !string.IsNullOrWhiteSpace(response.Job.Ttl) ? DateTime.Parse(response.Job.Ttl) : null,
RepeatCount = (int?)response.Job.Repeats,
Payload = response.Job.Data.ToByteArray()
};
return DeserializeJobResponse(response);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
@ -199,13 +189,35 @@ internal sealed class DaprJobsGrpcClient(Autogenerated.Dapr.DaprClient client, H
throw new DaprException("Get job operation failed: the Dapr endpoint did not return the expected value.");
}
/// <summary>
/// Testable method for performing job response deserialization.
/// </summary>
/// <remarks>
/// This is exposed strictly for testing purposes.
/// </remarks>
/// <param name="response">The job response to deserialize.</param>
/// <returns>The deserialized job response.</returns>
internal static DaprJobDetails DeserializeJobResponse(Autogenerated.GetJobResponse response)
{
var schedule = DateTime.TryParse(response.Job.DueTime, out var dueTime)
? DaprJobSchedule.FromDateTime(dueTime)
: new DaprJobSchedule(response.Job.Schedule);
return new DaprJobDetails(schedule)
{
DueTime = !string.IsNullOrWhiteSpace(response.Job.DueTime) ? DateTime.Parse(response.Job.DueTime) : null,
Ttl = !string.IsNullOrWhiteSpace(response.Job.Ttl) ? DateTime.Parse(response.Job.Ttl) : null,
RepeatCount = (int?)response.Job.Repeats ?? 0,
Payload = response.Job.Data?.ToByteArray() ?? null
};
}
/// <summary>
/// Deletes the specified job.
/// </summary>
/// <param name="jobName">The name of the job.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns></returns>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public override async Task DeleteJobAsync(string jobName, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(jobName))

View File

@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Diagnostics.CodeAnalysis;
using Dapr.Common.Extensions;
using Microsoft.Extensions.DependencyInjection;
@ -19,6 +20,7 @@ namespace Dapr.Jobs.Extensions;
/// <summary>
/// Contains extension methods for using Dapr Jobs with dependency injection.
/// </summary>
[Experimental("DAPR_JOBS", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/jobs/jobs-overview/")]
public static class DaprJobsServiceCollectionExtensions
{
/// <summary>

View File

@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Text.Json;
using Dapr.Jobs.Models;
@ -20,6 +21,7 @@ namespace Dapr.Jobs.Extensions;
/// <summary>
/// Provides helper extensions for performing serialization operations when scheduling one-time Cron jobs for the developer.
/// </summary>
[Experimental("DAPR_JOBS", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/jobs/jobs-overview/")]
public static class DaprJobsSerializationExtensions
{
/// <summary>
@ -41,7 +43,6 @@ public static class DaprJobsSerializationExtensions
/// <param name="overwrite">A flag indicating whether the job should be overwritten when submitted (true); otherwise false to require that an existing job with the same name be deleted first.</param>
/// <param name="failurePolicyOptions">The characteristics of the policy to apply when a job fails to trigger.</param>
/// <param name="cancellationToken">Cancellation token.</param>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public static async Task ScheduleJobWithPayloadAsync(this DaprJobsClient client, string jobName, DaprJobSchedule schedule,
object payload, DateTime? startingFrom = null, int? repeats = null,
JsonSerializerOptions? jsonSerializerOptions = null, DateTimeOffset? ttl = null,
@ -71,7 +72,6 @@ public static class DaprJobsSerializationExtensions
/// <param name="overwrite">A flag indicating whether the job should be overwritten when submitted (true); otherwise false to require that an existing job with the same name be deleted first.</param>
/// <param name="failurePolicyOptions">The characteristics of the policy to apply when a job fails to trigger.</param>
/// <param name="cancellationToken">Cancellation token.</param>
[Obsolete("The API is currently not stable as it is in the Alpha stage. This attribute will be removed once it is stable.")]
public static async Task ScheduleJobWithPayloadAsync(this DaprJobsClient client, string jobName, DaprJobSchedule schedule,
string payload, DateTime? startingFrom = null, int? repeats = null, DateTimeOffset? ttl = null,
bool overwrite = false, IJobFailurePolicyOptions? failurePolicyOptions = null,

View File

@ -12,6 +12,8 @@
// ------------------------------------------------------------------------
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Dapr.Actors.Client;
@ -175,6 +177,160 @@ public sealed class ActorManagerTests
Assert.Equal(1, activator.DeleteCallCount);
}
[Fact]
public async Task DeserializeTimer_Period_Iso8601_Time()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"0h0m7s10ms\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.FromSeconds(7).Add(TimeSpan.FromMilliseconds(10)), result.Period);
}
[Fact]
public async Task DeserializeTimer_Period_DaprFormat_Every()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@every 15s\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.FromSeconds(15), result.Period);
}
[Fact]
public async Task DeserializeTimer_Period_DaprFormat_Every2()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@every 3h2m15s\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.FromHours(3).Add(TimeSpan.FromMinutes(2)).Add(TimeSpan.FromSeconds(15)), result.Period);
}
[Fact]
public async Task DeserializeTimer_Period_DaprFormat_Monthly()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@monthly\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.FromDays(30), result.Period);
}
[Fact]
public async Task DeserializeTimer_Period_DaprFormat_Weekly()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@weekly\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.FromDays(7), result.Period);
}
[Fact]
public async Task DeserializeTimer_Period_DaprFormat_Daily()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@daily\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.FromDays(1), result.Period);
}
[Fact]
public async Task DeserializeTimer_Period_DaprFormat_Hourly()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"period\": \"@hourly\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.FromHours(1), result.Period);
}
[Fact]
public async Task DeserializeTimer_DueTime_DaprFormat_Hourly()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"dueTime\": \"@hourly\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.FromHours(1), result.DueTime);
Assert.Equal(TimeSpan.Zero, result.Period);
}
[Fact]
public async Task DeserializeTimer_DueTime_Iso8601Times()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"dueTime\": \"0h0m7s10ms\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Null(result.Ttl);
Assert.Equal(TimeSpan.Zero, result.Period);
Assert.Equal(TimeSpan.FromSeconds(7).Add(TimeSpan.FromMilliseconds(10)), result.DueTime);
}
[Fact]
public async Task DeserializeTimer_Ttl_DaprFormat_Hourly()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"ttl\": \"@hourly\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.Zero, result.Period);
Assert.Equal(TimeSpan.FromHours(1), result.Ttl);
}
[Fact]
public async Task DeserializeTimer_Ttl_Iso8601Times()
{
const string timerJson = "{\"callback\": \"TimerCallback\", \"ttl\": \"0h0m7s10ms\"}";
await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(timerJson));
var result = await ActorManager.DeserializeAsync(stream);
Assert.Equal("TimerCallback", result.Callback);
Assert.Equal(Array.Empty<byte>(), result.Data);
Assert.Equal(TimeSpan.Zero, result.DueTime);
Assert.Equal(TimeSpan.Zero, result.Period);
Assert.Equal(TimeSpan.FromSeconds(7).Add(TimeSpan.FromMilliseconds(10)), result.Ttl);
}
private interface ITestActor : IActor { }
private class TestActor : Actor, ITestActor, IDisposable
@ -255,4 +411,4 @@ public sealed class ActorManagerTests
return base.DeleteAsync(state);
}
}
}
}

View File

@ -30,28 +30,6 @@ public class CryptographyApiTest
(ReadOnlyMemory<byte>) Array.Empty<byte>(), keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa), CancellationToken.None));
}
[Fact]
public async Task EncryptAsync_Stream_VaultResourceName_ArgumentVerifierException()
{
var client = new DaprClientBuilder().Build();
const string vaultResourceName = "";
//Get response and validate
await Assert.ThrowsAsync<ArgumentException>(async () => await client.EncryptAsync(vaultResourceName,
new MemoryStream(), "MyKey", new EncryptionOptions(KeyWrapAlgorithm.Rsa),
CancellationToken.None));
}
[Fact]
public async Task EncryptAsync_Stream_KeyName_ArgumentVerifierException()
{
var client = new DaprClientBuilder().Build();
const string keyName = "";
//Get response and validate
await Assert.ThrowsAsync<ArgumentException>(async () => await client.EncryptAsync("myVault",
(Stream) new MemoryStream(), keyName, new EncryptionOptions(KeyWrapAlgorithm.Rsa),
CancellationToken.None));
}
[Fact]
public async Task DecryptAsync_ByteArray_VaultResourceName_ArgumentVerifierException()
{
@ -71,24 +49,4 @@ public class CryptographyApiTest
await Assert.ThrowsAsync<ArgumentException>(async () => await client.DecryptAsync("myVault",
Array.Empty<byte>(), keyName, new DecryptionOptions(), CancellationToken.None));
}
[Fact]
public async Task DecryptAsync_Stream_VaultResourceName_ArgumentVerifierException()
{
var client = new DaprClientBuilder().Build();
const string vaultResourceName = "";
//Get response and validate
await Assert.ThrowsAsync<ArgumentException>(async () => await client.DecryptAsync(vaultResourceName,
new MemoryStream(), "MyKey", new DecryptionOptions(), CancellationToken.None));
}
[Fact]
public async Task DecryptAsync_Stream_KeyName_ArgumentVerifierException()
{
var client = new DaprClientBuilder().Build();
const string keyName = "";
//Get response and validate
await Assert.ThrowsAsync<ArgumentException>(async () => await client.DecryptAsync("myVault",
new MemoryStream(), keyName, new DecryptionOptions(), CancellationToken.None));
}
}
}

View File

@ -26,6 +26,7 @@ public class DaprJobsAnalyzerAnalyzerTests
using Dapr.Jobs.Extensions;
using Dapr.Jobs.Models;
#pragma warning disable DAPR_JOBS
public static class Program
{
public static void Main()
@ -41,10 +42,11 @@ public class DaprJobsAnalyzerAnalyzerTests
Encoding.UTF8.GetBytes("This is a test"), repeats: 10).GetAwaiter().GetResult();
}
}
#pragma warning restore DAPR_JOBS
""";
var expected = VerifyAnalyzer.Diagnostic(MapDaprScheduledJobHandlerAnalyzer.DaprJobHandlerRule)
.WithSpan(22, 25, 23, 83)
.WithSpan(23, 25, 24, 83)
.WithMessage(
"Job invocations require the MapDaprScheduledJobHandler be set and configured for job name 'myJob' on IEndpointRouteBuilder");
@ -66,6 +68,7 @@ public class DaprJobsAnalyzerAnalyzerTests
using Dapr.Jobs.Extensions;
using Dapr.Jobs.Models;
#pragma warning disable DAPR_JOBS
public static class Program
{
public static void Main()
@ -78,6 +81,7 @@ public class DaprJobsAnalyzerAnalyzerTests
var daprJobsClient = scope.ServiceProvider.GetRequiredService<DaprJobsClient>();
}
}
#pragma warning restore DAPR_JOBS
""";
var analyzer = new VerifyAnalyzer(Utilities.GetReferences());
@ -98,6 +102,7 @@ public class DaprJobsAnalyzerAnalyzerTests
using Dapr.Jobs.Extensions;
using Dapr.Jobs.Models;
#pragma warning disable DAPR_JOBS
public static class Program
{
public static void Main()
@ -115,14 +120,15 @@ public class DaprJobsAnalyzerAnalyzerTests
Encoding.UTF8.GetBytes("This is a test"), repeats: 10).GetAwaiter().GetResult();
}
}
#pragma warning restore DAPR_JOBS
""";
var expected1 = VerifyAnalyzer.Diagnostic(MapDaprScheduledJobHandlerAnalyzer.DaprJobHandlerRule)
.WithSpan(22, 25, 23, 83)
.WithSpan(23, 25, 24, 83)
.WithMessage(
"Job invocations require the MapDaprScheduledJobHandler be set and configured for job name 'myJob' on IEndpointRouteBuilder");
var expected2 = VerifyAnalyzer.Diagnostic(MapDaprScheduledJobHandlerAnalyzer.DaprJobHandlerRule)
.WithSpan(24, 25, 25, 83)
.WithSpan(25, 25, 26, 83)
.WithMessage("Job invocations require the MapDaprScheduledJobHandler be set and configured for job name 'myJob2' on IEndpointRouteBuilder");
var analyzer = new VerifyAnalyzer(Utilities.GetReferences());
await analyzer.VerifyAnalyzerAsync<MapDaprScheduledJobHandlerAnalyzer>(testCode, expected1, expected2);
@ -142,6 +148,7 @@ public class DaprJobsAnalyzerAnalyzerTests
using Dapr.Jobs.Extensions;
using Dapr.Jobs.Models;
#pragma warning disable DAPR_JOBS
public static class Program
{
public static void Main()
@ -164,6 +171,7 @@ public class DaprJobsAnalyzerAnalyzerTests
}, TimeSpan.FromSeconds(5));
}
}
#pragma warning restore DAPR_JOBS
""";
var analyzer = new VerifyAnalyzer(Utilities.GetReferences());
@ -183,7 +191,8 @@ public class DaprJobsAnalyzerAnalyzerTests
using Dapr.Jobs;
using Dapr.Jobs.Extensions;
using Dapr.Jobs.Models;
#pragma warning disable DAPR_JOBS
public static class Program
{
public static async Task Main()
@ -206,6 +215,7 @@ public class DaprJobsAnalyzerAnalyzerTests
}, TimeSpan.FromSeconds(5));
}
}
#pragma warning restore DAPR_JOBS
""";
var analyzer = new VerifyAnalyzer(Utilities.GetReferences());
@ -225,7 +235,8 @@ public class DaprJobsAnalyzerAnalyzerTests
using Dapr.Jobs;
using Dapr.Jobs.Extensions;
using Dapr.Jobs.Models;
#pragma warning disable DAPR_JOBS
public static class Program
{
public static async Task Main()
@ -245,6 +256,7 @@ public class DaprJobsAnalyzerAnalyzerTests
return Task.CompletedTask;
}
}
#pragma warning restore DAPR_JOBS
""";

View File

@ -13,6 +13,7 @@
using System;
using System.Net.Http;
using Dapr.Client.Autogen.Grpc.v1;
using Dapr.Jobs.Models;
using Moq;
@ -166,6 +167,21 @@ public sealed class DaprJobsGrpcClientTests
});
#pragma warning restore CS0618 // Type or member is obsolete
}
[Fact]
public void ShouldDeserialize_EveryExpression()
{
const string scheduleText = "@every 1m";
var response = new GetJobResponse { Job = new Job { Name = "test", Schedule = scheduleText } };
var schedule = DaprJobSchedule.FromExpression(scheduleText);
var jobDetails = DaprJobsGrpcClient.DeserializeJobResponse(response);
Assert.Null(jobDetails.Payload);
Assert.Equal(0, jobDetails.RepeatCount);
Assert.Null(jobDetails.Ttl);
Assert.Null(jobDetails.DueTime);
Assert.Equal(jobDetails.Schedule.ExpressionValue, schedule.ExpressionValue);
}
private sealed record TestPayload(string Name, string Color);
}

View File

@ -8,6 +8,7 @@
<OutputPath>$(RepoRoot)bin\$(Configuration)\test\$(MSBuildProjectName)\</OutputPath>
<IsPackable>false</IsPackable>
<NoWarn>$(NoWarn);DAPR_CONVERSATION;DAPR_JOBS;DAPR_DISTRIBUTEDLOCK;DAPR_CRYPTOGRAPHY</NoWarn>
</PropertyGroup>
<!-- Used to annotate PR with test failures: https://github.com/Tyrrrz/GitHubActionsTestLogger -->