diff --git a/daprdocs/content/en/dotnet-sdk-contributing/dotnet-contributing.md b/daprdocs/content/en/dotnet-sdk-contributing/dotnet-contributing.md index 6664191d6..f0378b430 100644 --- a/daprdocs/content/en/dotnet-sdk-contributing/dotnet-contributing.md +++ b/daprdocs/content/en/dotnet-sdk-contributing/dotnet-contributing.md @@ -6,22 +6,112 @@ weight: 3000 description: Guidelines for contributing to the Dapr .NET SDK --- -When contributing to the [.NET SDK](https://github.com/dapr/dotnet-sdk) the following rules and best-practices should be followed. +# Welcome! +If you're reading this, you're likely interested in contributing to Dapr and/or the Dapr .NET SDK. Welcome to the project +and thank you for your interest in contributing! + +Please review the documentation, familiarize yourself with what Dapr is and what it's seeking to accomplish and reach +out on [Discord](https://bit.ly/dapr-discord). Let us know how you'd like to contribute and we'd be happy to chime in +with ideas and suggestions. + +There are many ways to contribute to Dapr: +- Submit bug reports for the [Dapr runtime](https://github.com/dapr/dapr/issues/new/choose) or the [Dapr .NET SDK](https://github.com/dapr/dotnet-sdk/issues/new/choose) +- Propose new [runtime capabilities](https://github.com/dapr/proposals/issues/new/choose) or [SDK functionality](https://github.com/dapr/dotnet-sdk/issues/new/choose) +- Improve the documentation in either the [larger Dapr project](https://github.com/dapr/docs) or the [Dapr .NET SDK specifically](https://github.com/dapr/dotnet-sdk/tree/master/daprdocs) +- Add new or improve existing [components](https://github.com/dapr/components-contrib/) that implement the various building blocks +- Augment the [.NET pluggable component SDK capabilities](https://github.com/dapr-sandbox/components-dotnet-sdk) +- Improve the Dapr .NET SDK code base and/or fix a bug (detailed below) + +If you're new to the code base, please feel encouraged to ask in the #dotnet-sdk channel in Discord about how +to implement changes or generally ask questions. You are not required to seek permission to work on anything, but do +note that if an issue is assigned to someone, it's an indication that someone might have already started work on it. +Especially if it's been a while since the last activity on that issue, please feel free to reach out and see if it's +still something they're interested in pursuing or whether you can take over, and open a pull request with your +implementation. + +If you'd like to assign yourself to an issue, respond to the conversation with "/assign" and the bot will assign you +to it. + +We have labeled some issues as `good-first-issue` or `help wanted` indicating that these are likely to be small, +self-contained changes. + +If you're not certain about your implementation, please create it as a draft pull request and solicit feedback +from the [.NET maintainers](https://github.com/orgs/dapr/teams/maintainers-dotnet-sdk) by tagging +`@dapr/maintainers-dotnet-sdk` and providing some context about what you need assistance with. + +# Contribution Rules and Best Practices + +When contributing to the [.NET SDK](https://github.com/dapr/dotnet-sdk) the following rules and best-practices should +be followed. + +## Pull Requests +Pull requests that contain only formatting changes are generally discouraged. Pull requests should instead seek to +fix a bug, add new functionality, or improve on existing capabilities. + +Do aim to minimize the contents of your pull request to span only a single issue. Broad PRs that touch on a lot of files +are not likely to be reviewed or accepted in a short timeframe. Accommodating many different issues in a single PR makes +it hard to determine whether your code fully addresses the underlying issue(s) or not and complicates the code review. + +## Tests +All pull requests should include unit and/or integration tests that reflect the nature of what was added or changed +so it's clear that the functionality works as intended. Avoid using auto-generated tests that duplicate testing the +same functionality several times. Rather, seek to improve code coverage by validating each possible path of your +changes so future contributors can more easily navigate the contours of your logic and more readily identify limitations. ## Examples -The `examples` directory contains code samples for users to run to try out specific functionality of the various .NET SDK packages and extensions. When writing new and updated samples keep in mind: +The `examples` directory contains code samples for users to run to try out specific functionality of the various +Dapr .NET SDK packages and extensions. When writing new and updated samples keep in mind: -- All examples should be runnable on Windows, Linux, and MacOS. While .NET Core code is consistent among operating systems, any pre/post example commands should provide options through [codetabs]({{< ref "contributing-docs.md#tabbed-content" >}}) -- Contain steps to download/install any required pre-requisites. Someone coming in with a fresh OS install should be able to start on the example and complete it without an error. Links to external download pages are fine. +- All examples should be runnable on Windows, Linux, and MacOS. While .NET Core code is consistent among operating +systems, any pre/post example commands should provide options through +[codetabs]({{< ref "contributing-docs.md#tabbed-content" >}}) +- Contain steps to download/install any required pre-requisites. Someone coming in with a fresh OS install should be +able to start on the example and complete it without an error. Links to external download pages are fine. -## Docs +## Documentation -The `daprdocs` directory contains the markdown files that are rendered into the [Dapr Docs](https://docs.dapr.io) website. When the documentation website is built this repo is cloned and configured so that its contents are rendered with the docs content. When writing docs keep in mind: +The `daprdocs` directory contains the markdown files that are rendered into the [Dapr Docs](https://docs.dapr.io) website. When the +documentation website is built this repo is cloned and configured so that its contents are rendered with the docs +content. When writing docs keep in mind: - All rules in the [docs guide]({{< ref contributing-docs.md >}}) should be followed in addition to these. - - All files and directories should be prefixed with `dotnet-` to ensure all file/directory names are globally unique across all Dapr documentation. + - All files and directories should be prefixed with `dotnet-` to ensure all file/directory names are globally + - unique across all Dapr documentation. + +All pull requests should strive to include both XML documentation in the code clearly indicating what functionality +does and why it's there as well as changes to the published documentation to clarify for other developers how your change +improves the Dapr framework. ## GitHub Dapr Bot Commands -Checkout the [daprbot documentation](https://docs.dapr.io/contributing/daprbot/) for Github commands you can run in this repo for common tasks. For example, you can comment `/assign` on an issue to assign it to yourself. +Checkout the [daprbot documentation](https://docs.dapr.io/contributing/daprbot/) for Github commands you can run in this repo for common tasks. For example, +you can comment `/assign` on an issue to assign it to yourself. + +## Commit Sign-offs +All code submitted to the Dapr .NET SDK must be signed off by the developer authoring it. This means that every +commit must end with the following: +> Signed-off-by: First Last + +The name and email address must match the registered GitHub name and email address of the user committing the changes. +We use a bot to detect this in pull requests and we will be unable to merge the PR if this check fails to validate. + +If you notice that a PR has failed to validate because of a failed DCO check early on in the PR history, please consider +squashing the PR locally and resubmitting to ensure that the sign-off statement is included in the commit history. + +# Languages, Tools and Processes +All source code in the Dapr .NET SDK is written in C# and targets the latest language version available to the earliest +supported .NET SDK. As of v1.15, this means that because .NET 6 is still supported, the latest language version available +is [C# version 10](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-10). + +As of v1.15, the following versions of .NET are supported: + +| Version | Notes | +| --- |-----------------------------------------------------------------| +| .NET 6 | Will be discontinued in v1.16 | +| .NET 7 | Only supported in Dapr.Workflows, will be discontinued in v1.16 | +| .NET 8 | Will continue to be supported in v1.16 | +| .NET 9 | Will continue to be supported in v1.16 | + +Contributors are welcome to use whatever IDE they're most comfortable developing in, but please do not submit +IDE-specific preference files along with your contributions as these will be rejected. \ No newline at end of file diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/_index.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/_index.md index 4374a8598..dac06e0bc 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/_index.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/_index.md @@ -1,83 +1,12 @@ -_index.md - --- type: docs -title: "Getting started with the Dapr AI .NET SDK client" +title: "Dapr AI .NET SDK" linkTitle: "AI" -weight: 10000 -description: How to get up and running with the Dapr AI .NET SDK -no_list: true +weight: 50000 +description: Get up and running with the Dapr AI .NET SDK --- -The Dapr AI client package allows you to interact with the AI capabilities provided by the Dapr sidecar. - -## Installation - -To get started with the Dapr AI .NET SDK client, install the following package from NuGet: -```sh -dotnet add package Dapr.AI -``` - -A `DaprConversationClient` holes access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar. - -### Dependency Injection - -The `AddDaprAiConversation()` method will register the Dapr client ASP.NET Core dependency injection and is the recommended approach -for using this package. This method accepts an optional options delegate for configuring the `DaprConversationClient` and a -`ServiceLifetime` argument, allowing you to specify a different lifetime for the registered services instead of the default `Singleton` -value. - -The following example assumes all default values are acceptable and is sufficient to register the `DaprConversationClient`: - -```csharp -services.AddDaprAiConversation(); -``` - -The optional configuration delegate is used to configure the `DaprConversationClient` by specifying options on the -`DaprConversationClientBuilder` as in the following example: -```csharp -services.AddSingleton(); -services.AddDaprAiConversation((serviceProvider, clientBuilder) => { - //Inject a service to source a value from - var optionsProvider = serviceProvider.GetRequiredService(); - var standardTimeout = optionsProvider.GetStandardTimeout(); - - //Configure the value on the client builder - clientBuilder.UseTimeout(standardTimeout); -}); -``` - -### Manual Instantiation -Rather than using dependency injection, a `DaprConversationClient` can also be built using the static client builder. - -For best performance, create a single long-lived instance of `DaprConversationClient` and provide access to that shared instance throughout -your application. `DaprConversationClient` instances are thread-safe and intended to be shared. - -Avoid creating a `DaprConversationClient` per-operation. - -A `DaprConversationClient` can be configured by invoking methods on the `DaprConversationClientBuilder` class before calling `.Build()` -to create the client. The settings for each `DaprConversationClient` are separate and cannot be changed after calling `.Build()`. - -```csharp -var daprConversationClient = new DaprConversationClientBuilder() - .UseJsonSerializerSettings( ... ) //Configure JSON serializer - .Build(); -``` - -See the .NET [documentation here]({{< ref dotnet-client >}}) for more information about the options available when configuring the Dapr client via the builder. - -## Try it out -Put the Dapr AI .NET SDK to the test. Walk through the samples to see Dapr in action: - -| SDK Samples | Description | -| ----------- | ----------- | -| [SDK samples](https://github.com/dapr/dotnet-sdk/tree/master/examples) | Clone the SDK repo to try out some examples and get started. | - -## Building Blocks - -This part of the .NET SDK allows you to interface with the Conversations API to send and receive messages from -large language models. - -### Send messages - +With the Dapr AI package, you can interact with the Dapr AI workloads from a .NET application. +Today, Dapr provides the Conversational API to engage with large language models. To get started with this workload, +walk through the [Dapr Conversational AI]({{< ref dotnet-ai-conversation-howto.md >}}) how-to guide. \ No newline at end of file diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-conversation-howto.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-conversation-howto.md new file mode 100644 index 000000000..9d8d869d8 --- /dev/null +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-conversation-howto.md @@ -0,0 +1,90 @@ +--- +type: docs +title: "How to: Create and use Dapr AI Conversations in the .NET SDK" +linkTitle: "How to: Use the AI Conversations client" +weight: 500100 +description: Learn how to create and use the Dapr Conversational AI client using the .NET SDK +--- + +## Prerequisites +- [.NET 6](https://dotnet.microsoft.com/download/dotnet/6.0), [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0), or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed +- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) +- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost) + +{{% alert title="Note" color="primary" %}} + +.NET 6 is supported as the minimum required for the Dapr .NET SDK packages in this release. Only .NET 8 and .NET 9 +will be supported in Dapr v1.16 and later releases. + +{{% /alert %}} + +## Installation + +To get started with the Dapr AI .NET SDK client, install the [Dapr.AI package](https://www.nuget.org/packages/Dapr.AI) from NuGet: +```sh +dotnet add package Dapr.AI +``` + +A `DaprConversationClient` maintains access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar. + +### Dependency Injection + +The `AddDaprAiConversation()` method will register the Dapr client ASP.NET Core dependency injection and is the recommended approach +for using this package. This method accepts an optional options delegate for configuring the `DaprConversationClient` and a +`ServiceLifetime` argument, allowing you to specify a different lifetime for the registered services instead of the default `Singleton` +value. + +The following example assumes all default values are acceptable and is sufficient to register the `DaprConversationClient`: + +```csharp +services.AddDaprAiConversation(); +``` + +The optional configuration delegate is used to configure the `DaprConversationClient` by specifying options on the +`DaprConversationClientBuilder` as in the following example: +```csharp +services.AddSingleton(); +services.AddDaprAiConversation((serviceProvider, clientBuilder) => { + //Inject a service to source a value from + var optionsProvider = serviceProvider.GetRequiredService(); + var standardTimeout = optionsProvider.GetStandardTimeout(); + + //Configure the value on the client builder + clientBuilder.UseTimeout(standardTimeout); +}); +``` + +### Manual Instantiation +Rather than using dependency injection, a `DaprConversationClient` can also be built using the static client builder. + +For best performance, create a single long-lived instance of `DaprConversationClient` and provide access to that shared instance throughout +your application. `DaprConversationClient` instances are thread-safe and intended to be shared. + +Avoid creating a `DaprConversationClient` per-operation. + +A `DaprConversationClient` can be configured by invoking methods on the `DaprConversationClientBuilder` class before calling `.Build()` +to create the client. The settings for each `DaprConversationClient` are separate and cannot be changed after calling `.Build()`. + +```csharp +var daprConversationClient = new DaprConversationClientBuilder() + .UseJsonSerializerSettings( ... ) //Configure JSON serializer + .Build(); +``` + +See the .NET [documentation here]({{< ref dotnet-client >}}) for more information about the options available when configuring the Dapr client via the builder. + +## Try it out +Put the Dapr AI .NET SDK to the test. Walk through the samples to see Dapr in action: + +| SDK Samples | Description | +| ----------- | ----------- | +| [SDK samples](https://github.com/dapr/dotnet-sdk/tree/master/examples) | Clone the SDK repo to try out some examples and get started. | + +## Building Blocks + +This part of the .NET SDK allows you to interface with the Conversations API to send and receive messages from +large language models. + +### Send messages + + diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-conversation-usage.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-conversation-usage.md new file mode 100644 index 000000000..b4917e02e --- /dev/null +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-conversation-usage.md @@ -0,0 +1,135 @@ +--- +type: docs +title: "Dapr AI Client" +linkTitle: "AI client" +weight: 50005 +description: Learn how to create Dapr AI clients +--- + +The Dapr AI client package allows you to interact with the AI capabilities provided by the Dapr sidecar. + +## Lifetime management +A `DaprConversationClient` is a version of the Dapr client that is dedicated to interacting with the Dapr Conversation +API. It can be registered alongside a `DaprClient` and other Dapr clients without issue. + +It maintains access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar. + +For best performance, create a single long-lived instance of `DaprConversationClient` and provide access to that shared +instance throughout your application. `DaprConversationClient` instances are thread-safe and intended to be shared. + +This can be aided by utilizing the dependency injection functionality. The registration method supports registration using +as a singleton, a scoped instance or as transient (meaning it's recreated every time it's injected), but also enables +registration to utilize values from an `IConfiguration` or other injected service in a way that's impractical when +creating the client from scratch in each of your classes. + +Avoid creating a `DaprConversationClient` for each operation. + +## Configuring DaprConversationClient via DaprConversationClientBuilder + +A `DaprConversationClient` can be configured by invoking methods on the `DaprConversationClientBuilder` class before +calling `.Build()` to create the client itself. The settings for each `DaprConversationClient` are separate +and cannot be changed after calling `.Build()`. + +```cs +var daprConversationClient = new DaprConversationClientBuilder() + .UseDaprApiToken("abc123") // Specify the API token used to authenticate to other Dapr sidecars + .Build(); +``` + +The `DaprConversationClientBuilder` contains settings for: + +- The HTTP endpoint of the Dapr sidecar +- The gRPC endpoint of the Dapr sidecar +- The `JsonSerializerOptions` object used to configure JSON serialization +- The `GrpcChannelOptions` object used to configure gRPC +- The API token used to authenticate requests to the sidecar +- The factory method used to create the `HttpClient` instance used by the SDK +- The timeout used for the `HttpClient` instance when making requests to the sidecar + +The SDK will read the following environment variables to configure the default values: + +- `DAPR_HTTP_ENDPOINT`: used to find the HTTP endpoint of the Dapr sidecar, example: `https://dapr-api.mycompany.com` +- `DAPR_GRPC_ENDPOINT`: used to find the gRPC endpoint of the Dapr sidecar, example: `https://dapr-grpc-api.mycompany.com` +- `DAPR_HTTP_PORT`: if `DAPR_HTTP_ENDPOINT` is not set, this is used to find the HTTP local endpoint of the Dapr sidecar +- `DAPR_GRPC_PORT`: if `DAPR_GRPC_ENDPOINT` is not set, this is used to find the gRPC local endpoint of the Dapr sidecar +- `DAPR_API_TOKEN`: used to set the API token + +### Configuring gRPC channel options + +Dapr's use of `CancellationToken` for cancellation relies on the configuration of the gRPC channel options. If you need +to configure these options yourself, make sure to enable the [ThrowOperationCanceledOnCancellation setting](https://grpc.github.io/grpc/csharp-dotnet/api/Grpc.Net.Client.GrpcChannelOptions.html#Grpc_Net_Client_GrpcChannelOptions_ThrowOperationCanceledOnCancellation). + +```cs +var daprConversationClient = new DaprConversationClientBuilder() + .UseGrpcChannelOptions(new GrpcChannelOptions { ... ThrowOperationCanceledOnCancellation = true }) + .Build(); +``` + +## Using cancellation with `DaprConversationClient` + +The APIs on `DaprConversationClient` perform asynchronous operations and accept an optional `CancellationToken` parameter. This +follows a standard .NET practice for cancellable operations. Note that when cancellation occurs, there is no guarantee that +the remote endpoint stops processing the request, only that the client has stopped waiting for completion. + +When an operation is cancelled, it will throw an `OperationCancelledException`. + +## Configuring `DaprConversationClient` via dependency injection + +Using the built-in extension methods for registering the `DaprConversationClient` in a dependency injection container can +provide the benefit of registering the long-lived service a single time, centralize complex configuration and improve +performance by ensuring similarly long-lived resources are re-purposed when possible (e.g. `HttpClient` instances). + +There are three overloads available to give the developer the greatest flexibility in configuring the client for their +scenario. Each of these will register the `IHttpClientFactory` on your behalf if not already registered, and configure +the `DaprConversationClientBuilder` to use it when creating the `HttpClient` instance in order to re-use the same instance as +much as possible and avoid socket exhaustion and other issues. + +In the first approach, there's no configuration done by the developer and the `DaprConversationClient` is configured with the +default settings. + +```cs +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddDaprConversationClient(); //Registers the `DaprConversationClient` to be injected as needed +var app = builder.Build(); +``` + +Sometimes the developer will need to configure the created client using the various configuration options detailed +above. This is done through an overload that passes in the `DaprConversationClientBuiler` and exposes methods for configuring +the necessary options. + +```cs +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddDaprConversationClient((_, daprConversationClientBuilder) => { + //Set the API token + daprConversationClientBuilder.UseDaprApiToken("abc123"); + //Specify a non-standard HTTP endpoint + daprConversationClientBuilder.UseHttpEndpoint("http://dapr.my-company.com"); +}); + +var app = builder.Build(); +``` + +Finally, it's possible that the developer may need to retrieve information from another service in order to populate +these configuration values. That value may be provided from a `DaprClient` instance, a vendor-specific SDK or some +local service, but as long as it's also registered in DI, it can be injected into this configuration operation via the +last overload: + +```cs +var builder = WebApplication.CreateBuilder(args); + +//Register a fictional service that retrieves secrets from somewhere +builder.Services.AddSingleton(); + +builder.Services.AddDaprConversationClient((serviceProvider, daprConversationClientBuilder) => { + //Retrieve an instance of the `SecretService` from the service provider + var secretService = serviceProvider.GetRequiredService(); + var daprApiToken = secretService.GetSecret("DaprApiToken").Value; + + //Configure the `DaprConversationClientBuilder` + daprConversationClientBuilder.UseDaprApiToken(daprApiToken); +}); + +var app = builder.Build(); +``` \ No newline at end of file diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-usage.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-usage.md deleted file mode 100644 index 93700c383..000000000 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-ai/dotnet-ai-usage.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -type: docs -title: "Best practices with the Dapr AI .NET SDK client" -linkTitle: "Best Practices" -weight: 100000 -description: How to get up and running with the Dapr .NET SDK ---- \ No newline at end of file diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-client/dotnet-daprclient-usage.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-client/dotnet-daprclient-usage.md index 26328050c..08c67ab95 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-client/dotnet-daprclient-usage.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-client/dotnet-daprclient-usage.md @@ -10,6 +10,48 @@ description: Essential tips and advice for using DaprClient A `DaprClient` holds access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar. `DaprClient` implements `IDisposable` to support eager cleanup of resources. +### Dependency Injection + +The `AddDaprClient()` method will register the Dapr client with ASP.NET Core dependency injection. This method accepts an optional +options delegate for configuring the `DaprClient` and an `ServiceLifetime` argument, allowing you to specify a different lifetime +for the registered resources instead of the default `Singleton` value. + +The following example assumes all default values are acceptable and is sufficient to register the `DaprClient`. + +```csharp +services.AddDaprClient(); +``` + +The optional configuration delegates are used to configure `DaprClient` by specifying options on the provided `DaprClientBuilder` +as in the following example: + +```csharp +services.AddDaprClient(daprBuilder => { + daprBuilder.UseJsonSerializerOptions(new JsonSerializerOptions { + WriteIndented = true, + MaxDepth = 8 + }); + daprBuilder.UseTimeout(TimeSpan.FromSeconds(30)); +}); +``` + +The another optional configuration delegate overload provides access to both the `DaprClientBuilder` as well as an `IServiceProvider` +allowing for more advanced configurations that may require injecting services from the dependency injection container. + +```csharp +services.AddSingleton(); +services.AddDaprClient((serviceProvider, daprBuilder) => { + var sampleService = serviceProvider.GetRequiredService(); + var timeoutValue = sampleService.TimeoutOptions; + + daprBuilder.UseTimeout(timeoutValue); +}); +``` + +### Manual Instantiation + +Rather than using dependency injection, a `DaprClient` can also be built using the static client builder. + For best performance, create a single long-lived instance of `DaprClient` and provide access to that shared instance throughout your application. `DaprClient` instances are thread-safe and intended to be shared. Avoid creating a `DaprClient` per-operation and disposing it when the operation is complete. @@ -24,13 +66,41 @@ var daprClient = new DaprClientBuilder() .Build(); ``` -The `DaprClientBuilder` contains settings for: +By default, the `DaprClientBuilder` will prioritize the following locations, in the following order, to source the configuration +values: + +- The value provided to a method on the `DaprClientBuilder` (e.g. `UseTimeout(TimeSpan.FromSeconds(30))`) +- The value pulled from an optionally injected `IConfiguration` matching the name expected in the associated environment variable +- The value pulled from the associated environment variable +- Default values + +### Configuring on `DaprClientBuilder` + +The `DaprClientBuilder` contains the following methods to set configuration options: + +- `UseHttpEndpoint(string)`: The HTTP endpoint of the Dapr sidecar +- `UseGrpcEndpoint(string)`: Sets the gRPC endpoint of the Dapr sidecar +- `UseGrpcChannelOptions(GrpcChannelOptions)`: Sets the gRPC channel options used to connect to the Dapr sidecar +- `UseHttpClientFactory(IHttpClientFactory)`: Configures the DaprClient to use a registered `IHttpClientFactory` when building `HttpClient` instances +- `UseJsonSerializationOptions(JsonSerializerOptions)`: Used to configure JSON serialization +- `UseDaprApiToken(string)`: Adds the provided token to every request to authenticate to the Dapr sidecar +- `UseTimeout(TimeSpan)`: Specifies a timeout value used by the `HttpClient` when communicating with the Dapr sidecar -- The HTTP endpoint of the Dapr sidecar -- The gRPC endpoint of the Dapr sidecar -- The `JsonSerializerOptions` object used to configure JSON serialization -- The `GrpcChannelOptions` object used to configure gRPC -- The API Token used to authenticate requests to the sidecar +### Configuring From `IConfiguration` +Rather than rely on sourcing configuration values directly from environment variables or because the values are sourced +from dependency injected services, another options is to make these values available on `IConfiguration`. + +For example, you might be registering your application in a multi-tenant environment and need to prefix the environment +variables used. The following example shows how these values can be sourced from the environment variables to your +`IConfiguration` when their keys are prefixed with `test_`; + +```csharp +var builder = WebApplication.CreateBuilder(args); +builder.Configuration.AddEnvironmentVariables("test_"); //Retrieves all environment variables that start with "test_" and removes the prefix when sourced from IConfiguration +builder.Services.AddDaprClient(); +``` + +### Configuring From Environment Variables The SDK will read the following environment variables to configure the default values: @@ -40,9 +110,14 @@ The SDK will read the following environment variables to configure the default v - `DAPR_GRPC_PORT`: if `DAPR_GRPC_ENDPOINT` is not set, this is used to find the gRPC local endpoint of the Dapr sidecar - `DAPR_API_TOKEN`: used to set the API Token +{{% alert title="Note" color="primary" %}} +If both `DAPR_HTTP_ENDPOINT` and `DAPR_HTTP_PORT` are specified, the port value from `DAPR_HTTP_PORT` will be ignored in favor of the port +implicitly or explicitly defined on `DAPR_HTTP_ENDPOINT`. The same is true of both `DAPR_GRPC_ENDPOINT` and `DAPR_GRPC_PORT`. +{{% /alert %}} + ### Configuring gRPC channel options -Dapr's use of `CancellationToken` for cancellation relies on the configuration of the gRPC channel options. If you need to configure these options yourself, make sure to enable the [ThrowOperationCanceledOnCancellation setting](https://grpc.github.io/grpc/csharp-dotnet/api/Grpc.Net.Client.GrpcChannelOptions.html#Grpc_Net_Client_GrpcChannelOptions_ThrowOperationCanceledOnCancellation). +Dapr's use of `CancellationToken` for cancellation relies on the configuration of the gRPC channel options and this is enabled by default. If you need to configure these options yourself, make sure to enable the [ThrowOperationCanceledOnCancellation setting](https://grpc.github.io/grpc/csharp-dotnet/api/Grpc.Net.Client.GrpcChannelOptions.html#Grpc_Net_Client_GrpcChannelOptions_ThrowOperationCanceledOnCancellation). ```C# var daprClient = new DaprClientBuilder() diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-development/_index.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-development/_index.md index 2cd86303f..26327efa5 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-development/_index.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-development/_index.md @@ -2,7 +2,7 @@ type: docs title: "Developing applications with the Dapr .NET SDK" linkTitle: "Dev integrations" -weight: 50000 +weight: 100000 description: Learn about local development integration options for .NET Dapr applications --- diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-development/dotnet-development-dapr-aspire.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-development/dotnet-development-dapr-aspire.md new file mode 100644 index 000000000..238a6d5a8 --- /dev/null +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-development/dotnet-development-dapr-aspire.md @@ -0,0 +1,138 @@ +--- +type: docs +title: "Dapr .NET SDK Development with .NET Aspire" +linkTitle: ".NET Aspire" +weight: 40000 +description: Learn about local development with .NET Aspire +--- + +# .NET Aspire + +[.NET Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview) is a development tool +designed to make it easier to include external software into .NET applications by providing a framework that allows +third-party services to be readily integrated, observed and provisioned alongside your own software. + +Aspire simplifies local development by providing rich integration with popular IDEs including +[Microsoft Visual Studio](https://visualstudio.microsoft.com/vs/), +[Visual Studio Code](https://code.visualstudio.com/), +[JetBrains Rider](https://blog.jetbrains.com/dotnet/2024/02/19/jetbrains-rider-and-the-net-aspire-plugin/) and others +to launch your application with the debugger while automatically launching and provisioning access to other +integrations as well, including Dapr. + +While Aspire also assists with deployment of your application to various cloud hosts like Microsoft Azure and +Amazon AWS, deployment is currently outside the scope of this guide. More information can be found in Aspire's +documentation [here](https://learn.microsoft.com/en-us/dotnet/aspire/deployment/overview). + +## Prerequisites +- While the Dapr .NET SDK is compatible with [.NET 6](https://dotnet.microsoft.com/download/dotnet/6.0), +[.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0), +.NET Aspire is only compatible with [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) or +[.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0). +- An OCI compliant container runtime such as [Docker Desktop](https://www.docker.com/products/docker-desktop) or +[Podman](https://podman.io/) +- Install and initialize Dapr v1.13 or later + +## Using .NET Aspire via CLI + +We'll start by creating a brand new .NET application. Open your preferred CLI and navigate to the directory you wish +to create your new .NET solution within. Start by using the following command to install a template that will create +an empty Aspire application: + +```sh +dotnet new install Aspire.ProjectTemplates +``` + +Once that's installed, proceed to create an empty .NET Aspire application in your current directory. The `-n` argument +allows you to specify the name of the output solution. If it's excluded, the .NET CLI will instead use the name +of the output directory, e.g. `C:\source\aspiredemo` will result in the solution being named `aspiredemo`. The rest +of this tutorial will assume a solution named `aspiredemo`. + +```sh +dotnet new aspire -n aspiredemo +``` + +This will create two Aspire-specific directories and one file in your directory: +- `aspiredemo.AppHost/` contains the Aspire orchestration project that is used to configure each of the integrations +used in your application(s). +- `aspiredemo.ServiceDefaults/` contains a collection of extensions meant to be shared across your solution to aid in +resilience, service discovery and telemetry capabilities offered by Aspire (these are distinct from the capabilities +offered in Dapr itself). +- `aspiredemo.sln` is the file that maintains the layout of your current solution + +We'll next create a project that'll serve as our Dapr application. From the same directory, use the following +to create an empty ASP.NET Core project called `MyApp`. This will be created relative to your current directory in +`MyApp\MyApp.csproj`. + +```sh +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. +We'll also add a reference to our `MyApp` project so we can reference it during the registration process. + +```sh +cd aspiredemo.AppHost +dotnet add package Aspire.Hosting.Dapr +dotnet add reference ../MyApp/ +``` + +Next, we need to configure Dapr as a resource to be loaded alongside your project. Open the `Program.cs` file in that +project within your preferred IDE. It should look similar to the following: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +builder.Build().Run(); +``` + +If you're familiar with the dependency injection approach used in ASP.NET Core projects or others utilizing the +`Microsoft.Extensions.DependencyInjection` functionality, you'll find that this will be a familiar experience. + +Because we've already added a project reference to `MyApp`, we need to start by adding a reference in this configuration +as well. Add the following before the `builder.Build().Run()` line: + +```csharp +var myApp = builder + .AddProject("myapp") + .WithDaprSidecar(); +``` + +Because the project reference has been added to this solution, your project shows up as a type within the `Projects.` +namespace for our purposes here. The name of the variable you assign the project to doesn't much matter in this tutorial +but would be used if you wanted to create a reference between this project and another using Aspire's service discovery +functionality. + +Adding `.WithDaprSidecar()` configures Dapr as a .NET Aspire resource so that when the project runs, the sidecar will be +deployed alongside your application. This accepts a number of different options and could optionally be configured as in +the following example: + +```csharp +DaprSidecarOptions sidecarOptions = new() +{ + AppId = "my-other-app", + AppPort = 8080, //Note that this argument is required if you intend to configure pubsub, actors or workflows as of Aspire v9.0 + DaprGrpcPort = 50001, + DaprHttpPort = 3500, + MetricsPort = 9090 +}; + +builder + .AddProject("myotherapp") + .WithReference(myApp) + .WithDaprSidecar(sidecarOptions); +``` + +{{% alert color="primary" %}} + +As indicated in the example above, as of .NET Aspire 9.0, if you intend to use any functionality in which Dapr needs to +call into your application such as pubsub, actors or workflows, you will need to specify your AppPort as +a configured option as Aspire will not automatically pass it to Dapr at runtime. It's expected that this behavior will +change in a future release as a fix has been merged and can be tracked [here](https://github.com/dotnet/aspire/pull/6362). + +{{% /alert %}} + +When you open the solution in your IDE, ensure that the `aspiredemo.AppHost` is configured as your startup project, but +when you launch it in a debug configuration, you'll note that your integrated console should reflect your expected Dapr +logs and it will be available to your application. + diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-development/dotnet-development-docker-compose.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-development/dotnet-development-docker-compose.md index 603be0b74..8b832ac26 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-development/dotnet-development-docker-compose.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-development/dotnet-development-docker-compose.md @@ -2,7 +2,7 @@ type: docs title: "Dapr .NET SDK Development with Docker-Compose" linkTitle: "Docker Compose" -weight: 50000 +weight: 60000 description: Learn about local development with Docker-Compose --- diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-development/dotnet-development-tye.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-development/dotnet-development-tye.md index 71ea568f1..0077bd564 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-development/dotnet-development-tye.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-development/dotnet-development-tye.md @@ -2,7 +2,7 @@ type: docs title: "Dapr .NET SDK Development with Project Tye" linkTitle: "Project Tye" -weight: 40000 +weight: 50000 description: Learn about local development with Project Tye --- diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/_index.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/_index.md index 049994221..60f756aa4 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/_index.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/_index.md @@ -6,3 +6,8 @@ weight: 50000 description: Get up and running with Dapr Jobs and the Dapr .NET SDK --- +With the Dapr Job package, you can interact with the Dapr Job APIs from a .NET application to trigger future operations +to run according to a predefined schedule with an optional payload. + +To get started, walk through the [Dapr Jobs]({{< ref dotnet-jobs-howto.md >}}) how-to guide and refer to +[best practices documentation]({{< ref dotnet-jobs-usage.md >}}) for additional guidance. \ No newline at end of file diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/dotnet-jobs-howto.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/dotnet-jobs-howto.md index 8d98d1ca5..974b2f5ec 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/dotnet-jobs-howto.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/dotnet-jobs-howto.md @@ -2,7 +2,7 @@ type: docs title: "How to: Author and manage Dapr Jobs in the .NET SDK" linkTitle: "How to: Author & manage jobs" -weight: 10000 +weight: 51000 description: Learn how to author and manage Dapr Jobs using the .NET SDK --- @@ -10,7 +10,7 @@ Let's create an endpoint that will be invoked by Dapr Jobs when it triggers, the you will: - Deploy a .NET Web API application ([JobsSample](https://github.com/dapr/dotnet-sdk/tree/master/examples/Jobs/JobsSample)) -- Utilize the .NET Jobs SDK to schedule a job invocation and set up the endpoint to be triggered +- Utilize the Dapr .NET Jobs SDK to schedule a job invocation and set up the endpoint to be triggered In the .NET example project: - The main [`Program.cs`](https://github.com/dapr/dotnet-sdk/tree/master/examples/Jobs/JobsSample/Program.cs) file comprises the entirety of this demonstration. @@ -18,13 +18,12 @@ In the .NET example project: ## Prerequisites - [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) - [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost) -- [.NET 6](https://dotnet.microsoft.com/download), [.NET 8](https://dotnet.microsoft.com/download) or [.NET 9](https://dotnet.microsoft.com/download) installed +- [.NET 6](https://dotnet.microsoft.com/download/dotnet/6.0), [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed +- [Dapr.Jobs](https://www.nuget.org/packages/Dapr.Jobs) NuGet package installed to your project {{% alert title="Note" color="primary" %}} -Note that while .NET 6 is generally supported as the minimum .NET requirement across the Dapr .NET SDK packages -and .NET 7 is the minimally supported version of .NET by Dapr.Workflows in Dapr v1.15, only .NET 8 and .NET 9 will -continue to be supported by Dapr in v1.16 and later. +Note that while .NET 6 is the minimum support version of .NET in Dapr v1.15, only .NET 8 and .NET 9 will continue to be supported by Dapr in v1.16 and later. {{% /alert %}} @@ -54,27 +53,33 @@ We'll run a command that starts both the Dapr sidecar and the .NET program at th ```sh dapr run --app-id jobsapp --dapr-grpc-port 4001 --dapr-http-port 3500 -- dotnet run ``` + > Dapr listens for HTTP requests at `http://localhost:3500` and internal Jobs gRPC requests at `http://localhost:4001`. + ## Register the Dapr Jobs client with dependency injection -The Dapr Jobs SDK provides an extension method to simplify the registration of the Dapr Jobs client. Before completing the dependency injection registration in `Program.cs`, add the following line: +The Dapr Jobs SDK provides an extension method to simplify the registration of the Dapr Jobs client. Before completing +the dependency injection registration in `Program.cs`, add the following line: ```cs var builder = WebApplication.CreateBuilder(args); //Add anywhere between these two builder.Services.AddDaprJobsClient(); //That's it + var app = builder.Build(); ``` > Note that in today's implementation of the Jobs API, the app that schedules the job will also be the app that receives the trigger notification. In other words, you cannot schedule a trigger to run in another application. As a result, while you don't explicitly need the Dapr Jobs client to be registered in your application to schedule a trigger invocation endpoint, your endpoint will never be invoked without the same app also scheduling the job somehow (whether via this Dapr Jobs .NET SDK or an HTTP call to the sidecar). + It's possible that you may want to provide some configuration options to the Dapr Jobs client that -should be present with each call to the sidecar such as a Dapr API token or you want to use a non-standard -HTTP or gRPC endpoint. This is possible through an overload of the register method that allows configuration of a `DaprJobsClientBuilder` instance: +should be present with each call to the sidecar such as a Dapr API token, or you want to use a non-standard +HTTP or gRPC endpoint. This is possible through use of an overload of the registration method that allows configuration of a +`DaprJobsClientBuilder` instance: ```cs var builder = WebApplication.CreateBuilder(args); -builder.Services.AddDaprJobsClient(daprJobsClientBuilder => +builder.Services.AddDaprJobsClient((_, daprJobsClientBuilder) => { daprJobsClientBuilder.UseDaprApiToken("abc123"); daprJobsClientBuilder.UseHttpEndpoint("http://localhost:8512"); //Non-standard sidecar HTTP endpoint @@ -102,6 +107,79 @@ builder.Services.AddDaprJobsClient((serviceProvider, daprJobsClientBuilder) => var app = builder.Build(); ``` +## Use the Dapr Jobs client using IConfiguration +It's possible to configure the Dapr Jobs client using the values in your registered `IConfiguration` as well without +explicitly specifying each of the value overrides using the `DaprJobsClientBuilder` as demonstrated in the previous +section. Rather, by populating an `IConfiguration` made available through dependency injection the `AddDaprJobsClient()` +registration will automatically use these values over their respective defaults. + +Start by populating the values in your configuration. This can be done in several different ways as demonstrated below. + +### Configuration via `ConfigurationBuilder` +Application settings can be configured without using a configuration source and by instead populating the value in-memory +using a `ConfigurationBuilder` instance: + +```csharp +var builder = WebApplication.CreateBuilder(); + +//Create the configuration +var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { + { "DAPR_HTTP_ENDPOINT", "http://localhost:54321" }, + { "DAPR_API_TOKEN", "abc123" } + }) + .Build(); + +builder.Configuration.AddConfiguration(configuration); +builder.Services.AddDaprJobsClient(); //This will automatically populate the HTTP endpoint and API token values from the IConfiguration +``` + +### Configuration via Environment Variables +Application settings can be accessed from environment variables available to your application. + +The following environment variables will be used to populate both the HTTP endpoint and API token used to register the +Dapr Jobs client. + +| Key | Value | +| --- | --- | +| DAPR_HTTP_ENDPOINT | http://localhost:54321 | +| DAPR_API_TOKEN | abc123 | + +```csharp +var builder = WebApplication.CreateBuilder(); + +builder.Configuration.AddEnvironmentVariables(); +builder.Services.AddDaprJobsClient(); +``` + +The Dapr Jobs client will be configured to use both the HTTP endpoint `http://localhost:54321` and populate all outbound +requests with the API token header `abc123`. + +### Configuration via prefixed Environment Variables + +However, in shared-host scenarios where there are multiple applications all running on the same machine without using +containers or in development environments, it's not uncommon to prefix environment variables. The following example +assumes that both the HTTP endpoint and the API token will be pulled from environment variables prefixed with the +value "myapp_". The two environment variables used in this scenario are as follows: + +| Key | Value | +| --- | --- | +| myapp_DAPR_HTTP_ENDPOINT | http://localhost:54321 | +| myapp_DAPR_API_TOKEN | abc123 | + +These environment variables will be loaded into the registered configuration in the following example and made available +without the prefix attached. + +```csharp +var builder = WebApplication.CreateBuilder(); + +builder.Configuration.AddEnvironmentVariables(prefix: "myapp_"); +builder.Services.AddDaprJobsClient(); +``` + +The Dapr Jobs client will be configured to use both the HTTP endpoint `http://localhost:54321` and populate all outbound +requests with the API token header `abc123`. + ## Use the Dapr Jobs client without relying on dependency injection While the use of dependency injection simplifies the use of complex types in .NET and makes it easier to deal with complicated configurations, you're not required to register the `DaprJobsClient` in this way. Rather, you can also elect to create an instance of it from a `DaprJobsClientBuilder` instance as demonstrated below: @@ -118,7 +196,6 @@ public class MySampleClass //Do something with the `daprJobsClient` } } - ``` ## Set up a endpoint to be invoked when the job is triggered diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/dotnet-jobsclient-usage.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/dotnet-jobsclient-usage.md index 4c28e6595..bbdbbdbe0 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/dotnet-jobsclient-usage.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-jobs/dotnet-jobsclient-usage.md @@ -2,23 +2,32 @@ type: docs title: "DaprJobsClient usage" linkTitle: "DaprJobsClient usage" -weight: 5000 +weight: 59000 description: Essential tips and advice for using DaprJobsClient --- ## Lifetime management -A `DaprJobsClient` is a version of the Dapr client that is dedicated to interacting with the Dapr Jobs API. It can be registered alongside a `DaprClient` without issue. +A `DaprJobsClient` is a version of the Dapr client that is dedicated to interacting with the Dapr Jobs API. It can be +registered alongside a `DaprClient` and other Dapr clients without issue. -It maintains access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar and implements `IDisposable` to support the eager cleanup of resources. +It maintains access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar and +implements `IDisposable` to support the eager cleanup of resources. -For best performance, create a single long-lived instance of `DaprJobsClient` and provide access to that shared instance throughout your application. `DaprJobsClient` instances are thread-safe and intended to be shared. +For best performance, create a single long-lived instance of `DaprJobsClient` and provide access to that shared instance +throughout your application. `DaprJobsClient` instances are thread-safe and intended to be shared. + +This can be aided by utilizing the dependency injection functionality. The registration method supports registration using +as a singleton, a scoped instance or as transient (meaning it's recreated every time it's injected), but also enables +registration to utilize values from an `IConfiguration` or other injected service in a way that's impractical when +creating the client from scratch in each of your classes. Avoid creating a `DaprJobsClient` for each operation and disposing it when the operation is complete. ## Configuring DaprJobsClient via the DaprJobsClientBuilder -A `DaprJobsClient` can be configured by invoking methods on the `DaprJobsClientBuilder` class before calling `.Build()` to create the client itself. The settings for each `DaprJobsClient` are separate +A `DaprJobsClient` can be configured by invoking methods on the `DaprJobsClientBuilder` class before calling `.Build()` +to create the client itself. The settings for each `DaprJobsClient` are separate and cannot be changed after calling `.Build()`. ```cs @@ -47,7 +56,8 @@ The SDK will read the following environment variables to configure the default v ### Configuring gRPC channel options -Dapr's use of `CancellationToken` for cancellation relies on the configuration of the gRPC channel options. If you need to configure these options yourself, make sure to enable the [ThrowOperationCanceledOnCancellation setting](https://grpc.github.io/grpc/csharp-dotnet/api/Grpc.Net.Client.GrpcChannelOptions.html#Grpc_Net_Client_GrpcChannelOptions_ThrowOperationCanceledOnCancellation). +Dapr's use of `CancellationToken` for cancellation relies on the configuration of the gRPC channel options. If you need +to configure these options yourself, make sure to enable the [ThrowOperationCanceledOnCancellation setting](https://grpc.github.io/grpc/csharp-dotnet/api/Grpc.Net.Client.GrpcChannelOptions.html#Grpc_Net_Client_GrpcChannelOptions_ThrowOperationCanceledOnCancellation). ```cs var daprJobsClient = new DaprJobsClientBuilder() @@ -55,19 +65,27 @@ var daprJobsClient = new DaprJobsClientBuilder() .Build(); ``` -## Using cancellation with DaprJobsClient +## Using cancellation with `DaprJobsClient` -The APIs on DaprJobsClient perform asynchronous operations and accept an optional `CancellationToken` parameter. This follows a standard .NET idiom for cancellable operations. Note that when cancellation occurs, there is no guarantee that the remote endpoint stops processing the request, only that the client has stopped waiting for completion. +The APIs on `DaprJobsClient` perform asynchronous operations and accept an optional `CancellationToken` parameter. This +follows a standard .NET practice for cancellable operations. Note that when cancellation occurs, there is no guarantee that +the remote endpoint stops processing the request, only that the client has stopped waiting for completion. When an operation is cancelled, it will throw an `OperationCancelledException`. -## Configuring DaprJobsClient via dependency injection +## Configuring `DaprJobsClient` via dependency injection -Using the built-in extension methods for registering the `DaprJobsClient` in a dependency injection container can provide the benefit of registering the long-lived service a single time, centralize complex configuration and improve performance by ensuring similarly long-lived resources are re-purposed when possible (e.g. `HttpClient` instances). +Using the built-in extension methods for registering the `DaprJobsClient` in a dependency injection container can +provide the benefit of registering the long-lived service a single time, centralize complex configuration and improve +performance by ensuring similarly long-lived resources are re-purposed when possible (e.g. `HttpClient` instances). -There are three overloads available to give the developer the greatest flexibility in configuring the client for their scenario. Each of these will register the `IHttpClientFactory` on your behalf if not already registered, and configure the `DaprJobsClientBuilder` to use it when creating the `HttpClient` instance in order to re-use the same instance as much as possible and avoid socket exhaution and other issues. +There are three overloads available to give the developer the greatest flexibility in configuring the client for their +scenario. Each of these will register the `IHttpClientFactory` on your behalf if not already registered, and configure +the `DaprJobsClientBuilder` to use it when creating the `HttpClient` instance in order to re-use the same instance as +much as possible and avoid socket exhaustion and other issues. -In the first approach, there's no configuration done by the developer and the `DaprJobsClient` is configured with the default settings. +In the first approach, there's no configuration done by the developer and the `DaprJobsClient` is configured with the +default settings. ```cs var builder = WebApplication.CreateBuilder(args); @@ -76,12 +94,14 @@ builder.Services.AddDaprJobsClient(); //Registers the `DaprJobsClient` to be inj var app = builder.Build(); ``` -Sometimes the developer will need to configure the created client using the various configuration options detailed above. This is done through an overload that passes in the `DaprJobsClientBuiler` and exposes methods for configuring the necessary options. +Sometimes the developer will need to configure the created client using the various configuration options detailed +above. This is done through an overload that passes in the `DaprJobsClientBuiler` and exposes methods for configuring +the necessary options. ```cs var builder = WebApplication.CreateBuilder(args); -builder.Services.AddDaprJobsClient(daprJobsClientBuilder => { +builder.Services.AddDaprJobsClient((_, daprJobsClientBuilder) => { //Set the API token daprJobsClientBuilder.UseDaprApiToken("abc123"); //Specify a non-standard HTTP endpoint @@ -91,7 +111,10 @@ builder.Services.AddDaprJobsClient(daprJobsClientBuilder => { var app = builder.Build(); ``` -Finally, it's possible that the developer may need to retrieve information from another service in order to populate these configuration values. That value may be provided from a `DaprClient` instance, a vendor-specific SDK or some local service, but as long as it's also registered in DI, it can be injected into this configuration operation via the last overload: +Finally, it's possible that the developer may need to retrieve information from another service in order to populate +these configuration values. That value may be provided from a `DaprClient` instance, a vendor-specific SDK or some +local service, but as long as it's also registered in DI, it can be injected into this configuration operation via the +last overload: ```cs var builder = WebApplication.CreateBuilder(args); @@ -113,9 +136,16 @@ var app = builder.Build(); ## Understanding payload serialization on DaprJobsClient -While there are many methods on the `DaprClient` that automatically serialize and deserialize data using the `System.Text.Json` serializer, this SDK takes a different philosophy. Instead, the relevant methods accept an optional payload of `ReadOnlyMemory` meaning that serialization is an exercise left to the developer and is not generally handled by the SDK. +While there are many methods on the `DaprClient` that automatically serialize and deserialize data using the +`System.Text.Json` serializer, this SDK takes a different philosophy. Instead, the relevant methods accept an optional +payload of `ReadOnlyMemory` meaning that serialization is an exercise left to the developer and is not +generally handled by the SDK. -That said, there are some helper extension methods available for each of the scheduling methods. If you know that you want to use a type that's JSON-serializable, you can use the `Schedule*WithPayloadAsync` method for each scheduling type that accepts an `object` as a payload and an optional `JsonSerializerOptions` to use when serializing the value. This will convert the value to UTF-8 encoded bytes for you as a convenience. Here's an example of what this might look like when scheduling a Cron expression: +That said, there are some helper extension methods available for each of the scheduling methods. If you know that you +want to use a type that's JSON-serializable, you can use the `Schedule*WithPayloadAsync` method for each scheduling +type that accepts an `object` as a payload and an optional `JsonSerializerOptions` to use when serializing the value. +This will convert the value to UTF-8 encoded bytes for you as a convenience. Here's an example of what this might +look like when scheduling a Cron expression: ```cs public sealed record Doodad (string Name, int Value); @@ -125,7 +155,9 @@ var doodad = new Doodad("Thing", 100); await daprJobsClient.ScheduleCronJobWithPayloadAsync("myJob", "5 * * * *", doodad); ``` -In the same vein, if you have a plain string value, you can use an overload of the same method to serialize a string-typed payload and the JSON serialization step will be skipped and it'll only be encoded to an array of UTF-8 encoded bytes. Here's an exampe of what this might look like when scheduling a one-time job: +In the same vein, if you have a plain string value, you can use an overload of the same method to serialize a +string-typed payload and the JSON serialization step will be skipped and it'll only be encoded to an array of +UTF-8 encoded bytes. Here's an example of what this might look like when scheduling a one-time job: ```cs var now = DateTime.UtcNow; @@ -133,7 +165,10 @@ var oneWeekFromNow = now.AddDays(7); await daprJobsClient.ScheduleOneTimeJobWithPayloadAsync("myOtherJob", oneWeekFromNow, "This is a test!"); ``` -The `JobDetails` type returns the data as a `ReadOnlyMemory?` so the developer has the freedom to deserialize as they wish, but there are again two helper extensions included that can deserialize this to either a JSON-compatible type or a string. Both methods assume that the developer encoded the originally scheduled job (perhaps using the helper serialization methods) as these methods will not force the bytes to represent something they're not. +The `JobDetails` type returns the data as a `ReadOnlyMemory?` so the developer has the freedom to deserialize +as they wish, but there are again two helper extensions included that can deserialize this to either a JSON-compatible +type or a string. Both methods assume that the developer encoded the originally scheduled job (perhaps using the +helper serialization methods) as these methods will not force the bytes to represent something they're not. To deserialize the bytes to a string, the following helper method can be used: ```cs @@ -143,7 +178,9 @@ if (jobDetails.Payload is not null) } ``` -To deserialize JSON-encoded UTF-8 bytes to the corresponding type, the following helper method can be used. An overload argument is available that permits the developer to pass in their own `JsonSerializerOptions` to be applied during deserialization. +To deserialize JSON-encoded UTF-8 bytes to the corresponding type, the following helper method can be used. An +overload argument is available that permits the developer to pass in their own `JsonSerializerOptions` to be applied +during deserialization. ```cs public sealed record Doodad (string Name, int Value); @@ -157,7 +194,12 @@ if (jobDetails.Payload is not null) ## Error handling -Methods on `DaprJobsClient` will throw a `DaprJobsServiceException` if an issue is encountered between the SDK and the Jobs API service running on the Dapr sidecar. If a failure is encountered because of a poorly formatted request made to the Jobs API service through this SDK, a `DaprMalformedJobException` will be thrown. In case of illegal argument values, the appropriate standard exception will be thrown (e.g. `ArgumentOutOfRangeException` or `ArgumentNullException`) with the name of the offending argument. And for anything else, a `DaprException` will be thrown. +Methods on `DaprJobsClient` will throw a `DaprJobsServiceException` if an issue is encountered between the SDK +and the Jobs API service running on the Dapr sidecar. If a failure is encountered because of a poorly formatted +request made to the Jobs API service through this SDK, a `DaprMalformedJobException` will be thrown. In case of +illegal argument values, the appropriate standard exception will be thrown (e.g. `ArgumentOutOfRangeException` +or `ArgumentNullException`) with the name of the offending argument. And for anything else, a `DaprException` +will be thrown. The most common cases of failure will be related to: diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-messaging/_index.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-messaging/_index.md new file mode 100644 index 000000000..7418f008a --- /dev/null +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-messaging/_index.md @@ -0,0 +1,17 @@ +--- +type: docs +title: "Dapr Messaging .NET SDK" +linkTitle: "Messaging" +weight: 60000 +description: Get up and running with the Dapr Messaging .NET SDK +--- + +With the Dapr Messaging package, you can interact with the Dapr messaging APIs from a .NET application. In the +v1.15 release, this package only contains the functionality corresponding to the +[streaming PubSub capability](https://docs.dapr.io/developing-applications/building-blocks/pubsub/howto-publish-subscribe/#subscribe-to-topics). + +Future Dapr .NET SDK releases will migrate existing messaging capabilities out from Dapr.Client to this +Dapr.Messaging package. This will be documented in the release notes, documentation and obsolete attributes in advance. + +To get started, walk through the [Dapr Messaging]({{< ref dotnet-messaging-pubsub-howto.md >}}) how-to guide and +refer to [best practices documentation]({{< ref dotnet-messaging-pubsub-usage.md >}}) for additional guidance. \ No newline at end of file diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-messaging/dotnet-messaging-pubsub-howto.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-messaging/dotnet-messaging-pubsub-howto.md new file mode 100644 index 000000000..b128d884e --- /dev/null +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-messaging/dotnet-messaging-pubsub-howto.md @@ -0,0 +1,268 @@ +--- +type: docs +title: "How to: Author and manage Dapr streaming subscriptions in the .NET SDK" +linkTitle: "How to: Author & manage streaming subscriptions" +weight: 61000 +description: Learn how to author and manage Dapr streaming subscriptions using the .NET SDK +--- + +Let's create a subscription to a pub/sub topic or queue at using the streaming capability. We'll use the +[simple example provided here](https://github.com/dapr/dotnet-sdk/tree/master/examples/Client/PublishSubscribe/StreamingSubscriptionExample), +for the following demonstration and walk through it as an explainer of how you can configure message handlers at +runtime and which do not require an endpoint to be pre-configured. In this guide, you will: + +- Deploy a .NET Web API application ([StreamingSubscriptionExample](https://github.com/dapr/dotnet-sdk/tree/master/examples/Client/PublishSubscribe/StreamingSubscriptionExample)) +- Utilize the Dapr .NET Messaging SDK to subscribe dynamically to a pub/sub topic. + +## Prerequisites +- [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) +- [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost) +- [.NET 6](https://dotnet.microsoft.com/download/dotnet/6.0), [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed +- [Dapr.Messaging](https://www.nuget.org/packages/Dapr.Messaging) NuGet package installed to your project + +{{% alert title="Note" color="primary" %}} + +Note that while .NET 6 is the minimum support version of .NET in Dapr v1.15, only .NET 8 and .NET 9 will continue to be supported by Dapr in v1.16 and later. + +{{% /alert %}} + +## Set up the environment +Clone the [.NET SDK repo](https://github.com/dapr/dotnet-sdk). + +```sh +git clone https://github.com/dapr/dotnet-sdk.git +``` + +From the .NET SDK root directory, navigate to the Dapr streaming PubSub example. + +```sh +cd examples/Client/PublishSubscribe +``` + +## Run the application locally + +To run the Dapr application, you need to start the .NET program and a Dapr sidecar. Navigate to the `StreamingSubscriptionExample` directory. + +```sh +cd StreamingSubscriptionExample +``` + +We'll run a command that starts both the Dapr sidecar and the .NET program at the same time. + +```sh +dapr run --app-id pubsubapp --dapr-grpc-port 4001 --dapr-http-port 3500 -- dotnet run +``` +> Dapr listens for HTTP requests at `http://localhost:3500` and internal Jobs gRPC requests at `http://localhost:4001`. + +## Register the Dapr PubSub client with dependency injection +The Dapr Messaging SDK provides an extension method to simplify the registration of the Dapr PubSub client. Before +completing the dependency injection registration in `Program.cs`, add the following line: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +//Add anywhere between these two +builder.Services.AddDaprPubSubClient(); //That's it + +var app = builder.Build(); +``` + +It's possible that you may want to provide some configuration options to the Dapr PubSub client that +should be present with each call to the sidecar such as a Dapr API token, or you want to use a non-standard +HTTP or gRPC endpoint. This be possible through use of an overload of the registration method that allows configuration +of a `DaprPublishSubscribeClientBuilder` instance: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddDaprPubSubClient((_, daprPubSubClientBuilder) => { + daprPubSubClientBuilder.UseDaprApiToken("abc123"); + daprPubSubClientBuilder.UseHttpEndpoint("http://localhost:8512"); //Non-standard sidecar HTTP endpoint +}); + +var app = builder.Build(); +``` + +Still, it's possible that whatever values you wish to inject need to be retrieved from some other source, itself registered as a dependency. There's one more overload you can use to inject an `IServiceProvider` into the configuration action method. In the following example, we register a fictional singleton that can retrieve secrets from somewhere and pass it into the configuration method for `AddDaprJobClient` so +we can retrieve our Dapr API token from somewhere else for registration here: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddSingleton(); +builder.Services.AddDaprPubSubClient((serviceProvider, daprPubSubClientBuilder) => { + var secretRetriever = serviceProvider.GetRequiredService(); + var daprApiToken = secretRetriever.GetSecret("DaprApiToken").Value; + daprPubSubClientBuilder.UseDaprApiToken(daprApiToken); + + daprPubSubClientBuilder.UseHttpEndpoint("http://localhost:8512"); +}); + +var app = builder.Build(); +``` + +## Use the Dapr PubSub client using IConfiguration +It's possible to configure the Dapr PubSub client using the values in your registered `IConfiguration` as well without +explicitly specifying each of the value overrides using the `DaprPublishSubscribeClientBuilder` as demonstrated in the previous +section. Rather, by populating an `IConfiguration` made available through dependency injection the `AddDaprPubSubClient()` +registration will automatically use these values over their respective defaults. + +Start by populating the values in your configuration. This can be done in several different ways as demonstrated below. + +### Configuration via `ConfigurationBuilder` +Application settings can be configured without using a configuration source and by instead populating the value in-memory +using a `ConfigurationBuilder` instance: + +```csharp +var builder = WebApplication.CreateBuilder(); + +//Create the configuration +var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { + { "DAPR_HTTP_ENDPOINT", "http://localhost:54321" }, + { "DAPR_API_TOKEN", "abc123" } + }) + .Build(); + +builder.Configuration.AddConfiguration(configuration); +builder.Services.AddDaprPubSubClient(); //This will automatically populate the HTTP endpoint and API token values from the IConfiguration +``` + +### Configuration via Environment Variables +Application settings can be accessed from environment variables available to your application. + +The following environment variables will be used to populate both the HTTP endpoint and API token used to register the +Dapr PubSub client. + +| Key | Value | +|--------------------|------------------------| +| DAPR_HTTP_ENDPOINT | http://localhost:54321 | +| DAPR_API_TOKEN | abc123 | + +```csharp +var builder = WebApplication.CreateBuilder(); + +builder.Configuration.AddEnvironmentVariables(); +builder.Services.AddDaprPubSubClient(); +``` + +The Dapr PubSub client will be configured to use both the HTTP endpoint `http://localhost:54321` and populate all outbound +requests with the API token header `abc123`. + +### Configuration via prefixed Environment Variables +However, in shared-host scenarios where there are multiple applications all running on the same machine without using +containers or in development environments, it's not uncommon to prefix environment variables. The following example +assumes that both the HTTP endpoint and the API token will be pulled from environment variables prefixed with the +value "myapp_". The two environment variables used in this scenario are as follows: + +| Key | Value | +|--------------------------|------------------------| +| myapp_DAPR_HTTP_ENDPOINT | http://localhost:54321 | +| myapp_DAPR_API_TOKEN | abc123 | + +These environment variables will be loaded into the registered configuration in the following example and made available +without the prefix attached. + +```csharp +var builder = WebApplication.CreateBuilder(); + +builder.Configuration.AddEnvironmentVariables(prefix: "myapp_"); +builder.Services.AddDaprPubSubClient(); +``` + +The Dapr PubSub client will be configured to use both the HTTP endpoint `http://localhost:54321` and populate all outbound +requests with the API token header `abc123`. + +## Use the Dapr PubSub client without relying on dependency injection +While the use of dependency injection simplifies the use of complex types in .NET and makes it easier to +deal with complicated configurations, you're not required to register the `DaprPublishSubscribeClient` in this way. +Rather, you can also elect to create an instance of it from a `DaprPublishSubscribeClientBuilder` instance as +demonstrated below: + +```cs + +public class MySampleClass +{ + public void DoSomething() + { + var daprPubSubClientBuilder = new DaprPublishSubscribeClientBuilder(); + var daprPubSubClient = daprPubSubClientBuilder.Build(); + + //Do something with the `daprPubSubClient` + } +} +``` + +## Set up message handler +The streaming subscription implementation in Dapr gives you greater control over handling backpressure from events by +leaving the messages in the Dapr runtime until your application is ready to accept them. The .NET SDK supports a +high-performance queue for maintaining a local cache of these messages in your application while processing is pending. +These messages will persist in the queue until processing either times out for each one or a response action is taken +for each (typically after processing succeeds or fails). Until this response action is received by the Dapr runtime, +the messages will be persisted by Dapr and made available in case of a service failure. + +The various response actions available are as follows: +| Response Action | Description | +| --- | --- | +| Retry | The event should be delivered again in the future. | +| Drop | The event should be deleted (or forwarded to a dead letter queue, if configured) and not attempted again. | +| Success | The event should be deleted as it was successfully processed. | + +The handler will receive only one message at a time and if a cancellation token is provided to the subscription, +this token will be provided during the handler invocation. + +The handler must be configured to return a `Task` indicating one of these operations, even if from +a try/catch block. If an exception is not caught by your handler, the subscription will use the response action configured +in the options during subscription registration. + +The following demonstrates the sample message handler provided in the example: + +```csharp +Task HandleMessageAsync(TopicMessage message, CancellationToken cancellationToken = default) +{ + try + { + //Do something with the message + Console.WriteLine(Encoding.UTF8.GetString(message.Data.Span)); + return Task.FromResult(TopicResponseAction.Success); + } + catch + { + return Task.FromResult(TopicResponseAction.Retry); + } +} +``` + +## Configure and subscribe to the PubSub topic +Configuration of the streaming subscription requires the name of the PubSub component registered with Dapr, the name +of the topic or queue being subscribed to, the `DaprSubscriptionOptions` providing the configuration for the subscription, +the message handler and an optional cancellation token. The only required argument to the `DaprSubscriptionOptions` is +the default `MessageHandlingPolicy` which consists of a per-event timeout and the `TopicResponseAction` to take when +that timeout occurs. + +Other options are as follows: + +| Property Name | Description | +|-----------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| +| Metadata | Additional subscription metadata | +| DeadLetterTopic | The optional name of the dead-letter topic to send dropped messages to. | +| MaximumQueuedMessages | By default, there is no maximum boundary enforced for the internal queue, but setting this | +| property would impose an upper limit. | | +| MaximumCleanupTimeout | When the subscription is disposed of or the token flags a cancellation request, this specifies | +| the maximum amount of time available to process the remaining messages in the internal queue. | | + +Subscription is then configured as in the following example: +```csharp +var messagingClient = app.Services.GetRequiredService(); + +var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(60)); //Override the default of 30 seconds +var options = new DaprSubscriptionOptions(new MessageHandlingPolicy(TimeSpan.FromSeconds(10), TopicResponseAction.Retry)); +var subscription = await messagingClient.SubscribeAsync("pubsub", "mytopic", options, HandleMessageAsync, cancellationTokenSource.Token); +``` + +## Terminate and clean up subscription +When you've finished with your subscription and wish to stop receiving new events, simply await a call to +`DisposeAsync()` on your subscription instance. This will cause the client to unregister from additional events and +proceed to finish processing all the events still leftover in the backpressure queue, if any, before disposing of any +internal resources. This cleanup will be limited to the timeout interval provided in the `DaprSubscriptionOptions` when +the subscription was registered and by default, this is set to 30 seconds. \ No newline at end of file diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-messaging/dotnet-messaging-pubsub-usage.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-messaging/dotnet-messaging-pubsub-usage.md new file mode 100644 index 000000000..8b3359d0c --- /dev/null +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-messaging/dotnet-messaging-pubsub-usage.md @@ -0,0 +1,130 @@ +--- +type: docs +title: "DaprPublishSubscribeClient usage" +linkTitle: "DaprPublishSubscribeClient usage" +weight: 69000 +description: Essential tips and advice for using DaprPublishSubscribeClient +--- + +## Lifetime management + +A `DaprPublishSubscribeClient` is a version of the Dapr client that is dedicated to interacting with the Dapr Messaging API. +It can be registered alongside a `DaprClient` and other Dapr clients without issue. + +It maintains access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar and implements +`IAsyncDisposable` to support the eager cleanup of resources. + +For best performance, create a single long-lived instance of `DaprPublishSubscribeClient` and provide access to that shared +instance throughout your application. `DaprPublishSubscribeClient` instances are thread-safe and intended to be shared. + +This can be aided by utilizing the dependency injection functionality. The registration method supports registration using +as a singleton, a scoped instance or as transient (meaning it's recreated every time it's injected), but also enables +registration to utilize values from an `IConfiguration` or other injected service in a way that's impractical when +creating the client from scratch in each of your classes. + +Avoid creating a `DaprPublishSubscribeClient` for each operation and disposing it when the operation is complete. It's +intended that the `DaprPublishSubscribeClient` should only be disposed when you no longer wish to receive events on the +subscription as disposing it will cancel the ongoing receipt of new events. + +## Configuring DaprPublishSubscribeClient via the DaprPublishSubscribeClientBuilder +A `DaprPublishSubscribeClient` can be configured by invoking methods on the `DaprPublishSubscribeClientBuilder` class +before calling `.Build()` to create the client itself. The settings for each `DaprPublishSubscribeClient` are separate +and cannot be changed after calling `.Build()`. + +```cs +var daprPubsubClient = new DaprPublishSubscribeClientBuilder() + .UseDaprApiToken("abc123") // Specify the API token used to authenticate to other Dapr sidecars + .Build(); +``` + +The `DaprPublishSubscribeClientBuilder` contains settings for: + +- The HTTP endpoint of the Dapr sidecar +- The gRPC endpoint of the Dapr sidecar +- The `JsonSerializerOptions` object used to configure JSON serialization +- The `GrpcChannelOptions` object used to configure gRPC +- The API token used to authenticate requests to the sidecar +- The factory method used to create the `HttpClient` instance used by the SDK +- The timeout used for the `HttpClient` instance when making requests to the sidecar + +The SDK will read the following environment variables to configure the default values: + +- `DAPR_HTTP_ENDPOINT`: used to find the HTTP endpoint of the Dapr sidecar, example: `https://dapr-api.mycompany.com` +- `DAPR_GRPC_ENDPOINT`: used to find the gRPC endpoint of the Dapr sidecar, example: `https://dapr-grpc-api.mycompany.com` +- `DAPR_HTTP_PORT`: if `DAPR_HTTP_ENDPOINT` is not set, this is used to find the HTTP local endpoint of the Dapr sidecar +- `DAPR_GRPC_PORT`: if `DAPR_GRPC_ENDPOINT` is not set, this is used to find the gRPC local endpoint of the Dapr sidecar +- `DAPR_API_TOKEN`: used to set the API token + +### Configuring gRPC channel options +Dapr's use of `CancellationToken` for cancellation relies on the configuration of the gRPC channel options. If you +need to configure these options yourself, make sure to enable the [ThrowOperationCanceledOnCancellation setting](https://grpc.github.io/grpc/csharp-dotnet/api/Grpc.Net.Client.GrpcChannelOptions.html#Grpc_Net_Client_GrpcChannelOptions_ThrowOperationCanceledOnCancellation). + +```cs +var daprPubsubClient = new DaprPublishSubscribeClientBuilder() + .UseGrpcChannelOptions(new GrpcChannelOptions { ... ThrowOperationCanceledOnCancellation = true }) + .Build(); +``` + +## Using cancellation with `DaprPublishSubscribeClient` + +The APIs on `DaprPublishSubscribeClient` perform asynchronous operations and accept an optional `CancellationToken` +parameter. This follows a standard .NET practice for cancellable operations. Note that when cancellation occurs, there is +no guarantee that the remote endpoint stops processing the request, only that the client has stopped waiting for completion. + +When an operation is cancelled, it will throw an `OperationCancelledException`. + +## Configuring `DaprPublishSubscribeClient` via dependency injection + +Using the built-in extension methods for registering the `DaprPublishSubscribeClient` in a dependency injection container +can provide the benefit of registering the long-lived service a single time, centralize complex configuration and improve +performance by ensuring similarly long-lived resources are re-purposed when possible (e.g. `HttpClient` instances). + +There are three overloads available to give the developer the greatest flexibility in configuring the client for their +scenario. Each of these will register the `IHttpClientFactory` on your behalf if not already registered, and configure +the `DaprPublishSubscribeClientBuilder` to use it when creating the `HttpClient` instance in order to re-use the same +instance as much as possible and avoid socket exhaustion and other issues. + +In the first approach, there's no configuration done by the developer and the `DaprPublishSubscribeClient` is configured with +the default settings. + +```cs +var builder = WebApplication.CreateBuilder(args); + +builder.Services.DaprPublishSubscribeClient(); //Registers the `DaprPublishSubscribeClient` to be injected as needed +var app = builder.Build(); +``` + +Sometimes the developer will need to configure the created client using the various configuration options detailed above. This is done through an overload that passes in the `DaprJobsClientBuiler` and exposes methods for configuring the necessary options. + +```cs +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddDaprJobsClient((_, daprPubSubClientBuilder) => { + //Set the API token + daprPubSubClientBuilder.UseDaprApiToken("abc123"); + //Specify a non-standard HTTP endpoint + daprPubSubClientBuilder.UseHttpEndpoint("http://dapr.my-company.com"); +}); + +var app = builder.Build(); +``` + +Finally, it's possible that the developer may need to retrieve information from another service in order to populate these configuration values. That value may be provided from a `DaprClient` instance, a vendor-specific SDK or some local service, but as long as it's also registered in DI, it can be injected into this configuration operation via the last overload: + +```cs +var builder = WebApplication.CreateBuilder(args); + +//Register a fictional service that retrieves secrets from somewhere +builder.Services.AddSingleton(); + +builder.Services.AddDaprPublishSubscribeClient((serviceProvider, daprPubSubClientBuilder) => { + //Retrieve an instance of the `SecretService` from the service provider + var secretService = serviceProvider.GetRequiredService(); + var daprApiToken = secretService.GetSecret("DaprApiToken").Value; + + //Configure the `DaprPublishSubscribeClientBuilder` + daprPubSubClientBuilder.UseDaprApiToken(daprApiToken); +}); + +var app = builder.Build(); +``` \ No newline at end of file diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-troubleshooting/_index.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-troubleshooting/_index.md index e7d8da774..5ffd7c7c6 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-troubleshooting/_index.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-troubleshooting/_index.md @@ -2,6 +2,6 @@ type: docs title: "How to troubleshoot and debug with the Dapr .NET SDK" linkTitle: "Troubleshooting" -weight: 100000 +weight: 120000 description: Tips, tricks, and guides for troubleshooting and debugging with the Dapr .NET SDKs --- \ No newline at end of file diff --git a/daprdocs/content/en/dotnet-sdk-docs/dotnet-workflow/dotnet-workflow-howto.md b/daprdocs/content/en/dotnet-sdk-docs/dotnet-workflow/dotnet-workflow-howto.md index 9be910234..e81efc340 100644 --- a/daprdocs/content/en/dotnet-sdk-docs/dotnet-workflow/dotnet-workflow-howto.md +++ b/daprdocs/content/en/dotnet-sdk-docs/dotnet-workflow/dotnet-workflow-howto.md @@ -20,13 +20,12 @@ In the .NET example project: - [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) - [Initialized Dapr environment](https://docs.dapr.io/getting-started/install-dapr-selfhost/) -- [.NET 7](https://dotnet.microsoft.com/download), [.NET 8](https://dotnet.microsoft.com/download) or [.NET 9](https://dotnet.microsoft.com/download) installed +- [.NET 7](https://dotnet.microsoft.com/download/dotnet/7.0), [.NET 8](https://dotnet.microsoft.com/download/dotnet/8.0) or [.NET 9](https://dotnet.microsoft.com/download/dotnet/9.0) installed {{% alert title="Note" color="primary" %}} -Note that while .NET 6 is generally supported as the minimum .NET requirement across the Dapr .NET SDK packages -and .NET 7 is the minimally supported version of .NET by Dapr.Workflows in Dapr v1.15, only .NET 8 and .NET 9 will -continue to be supported by Dapr in v1.16 and later. +Dapr.Workflows supports .NET 7 or newer in v1.15. However, following the release of Dapr v1.16, only +.NET 8 and .NET 9 will be supported. {{% /alert %}}