Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
6b7f9c0
Marking conversation API as experimental instead of obsolete
WhitWaldo Jun 21, 2025
9839041
Applied experiemental flags to Dapr.Jobs
WhitWaldo Jun 21, 2025
cd7e2d6
Replaced Obsolete with Experimental attributes through cryptography i…
WhitWaldo Jun 21, 2025
47987c1
Replaced Obsolete with Experimental attribute across DistributedLock …
WhitWaldo Jun 21, 2025
d83b60c
Applied the experiemental attribute to the commented out subtle crypt…
WhitWaldo Jun 21, 2025
080b387
Applied Experimental attribute to more crypto methods
WhitWaldo Jun 21, 2025
189576a
Applied Experimental to more distributed lock methods
WhitWaldo Jun 21, 2025
c3b26ce
Replaced Obsolete attributes with Experimental attributes on example …
WhitWaldo Jun 21, 2025
e0bfa96
Adding messages to opt-out of warnings about the experimental attribu…
WhitWaldo Jun 21, 2025
ee76d87
Added documentation calling out the use of the experimental attributes
WhitWaldo Jun 21, 2025
069b568
Fixed typo in documentation
WhitWaldo Jun 21, 2025
78112c2
Removed erroneous text
WhitWaldo Jun 21, 2025
c7fd4f1
Marked more of the crypto package as experiemental
WhitWaldo Jun 21, 2025
6513f43
Applied experimental suppression to source generator testing
WhitWaldo Jun 21, 2025
7e0d106
Suppressing messages using pragma in source generators instead
WhitWaldo Jun 21, 2025
f765bf3
Fixed line numbers of diagnostic messages
WhitWaldo Jun 21, 2025
dd375db
Removed one of the options for suppression since I had no luck with i…
WhitWaldo Jun 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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)
74 changes: 31 additions & 43 deletions examples/Client/DistributedLock/Controllers/BindingController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
Expand All @@ -11,24 +12,15 @@
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);
Expand All @@ -47,44 +39,40 @@ public async Task<IActionResult> HandleBindingEvent()
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}");
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);
// 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;
}
if (fileState == null)
{
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";
// "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);
// 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);
// 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.LogInformation("Done processing {fileName}", fileName);
}
else
{
logger.LogWarning("Failed to lock {fileName}.", fileName);
}
}

Expand All @@ -103,4 +91,4 @@ private async Task<StateData> GetFile(string fileName)
return null;
}
}
}
}
1 change: 1 addition & 0 deletions examples/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -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>
2 changes: 2 additions & 0 deletions src/Dapr.AI/Conversation/DaprConversationClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------

using System.Diagnostics.CodeAnalysis;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1.Dapr;

namespace Dapr.AI.Conversation;
Expand All @@ -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>
Expand Down
3 changes: 3 additions & 0 deletions src/Dapr.AI/Conversation/DaprConversationClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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>
Expand All @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/Dapr.AI/Conversation/DaprConversationGrpcClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// limitations under the License.
// ------------------------------------------------------------------------

using System.Diagnostics.CodeAnalysis;
using Dapr.Common.Extensions;
using Microsoft.Extensions.DependencyInjection;

Expand All @@ -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,
Expand Down
Loading