Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dapr client registration consolidation #1417

Closed
wants to merge 10 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,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<string, string> {
{ "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:
Expand Down
2 changes: 2 additions & 0 deletions src/Dapr.Common/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
[assembly: InternalsVisibleTo("Dapr.AspNetCore, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Client, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Jobs, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Messaging, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Extensions.Configuration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Workflow, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]

Expand All @@ -40,3 +41,4 @@
[assembly: InternalsVisibleTo("Dapr.E2E.Test.App.ReentrantActors, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Extensions.Configuration.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Jobs.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
[assembly: InternalsVisibleTo("Dapr.Messaging.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1f597635c44597fcecb493e2b1327033b29b1a98ac956a1a538664b68f87d45fbaada0438a15a6265e62864947cc067d8da3a7d93c5eb2fcbb850e396c8684dba74ea477d82a1bbb18932c0efb30b64ff1677f85ae833818707ac8b49ad8062ca01d2c89d8ab1843ae73e8ba9649cd28666b539444dcdee3639f95e2a099bb2")]
77 changes: 77 additions & 0 deletions src/Dapr.Common/DaprClientUtilities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// ------------------------------------------------------------------------
// Copyright 2024 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

using System.Net.Http.Headers;
using System.Reflection;
using Grpc.Core;

namespace Dapr.Common;

internal static class DaprClientUtilities
{
/// <summary>
/// Provisions the gRPC call options used to provision the various Dapr clients.
/// </summary>
/// <param name="daprApiToken">The Dapr API token, if any.</param>
/// <param name="assembly">The assembly the user agent is built from.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The gRPC call options.</returns>
internal static CallOptions ConfigureGrpcCallOptions(Assembly assembly, string? daprApiToken, CancellationToken cancellationToken = default)
{
var callOptions = new CallOptions(headers: new Metadata(), cancellationToken: cancellationToken);

//Add the user-agent header to the gRPC call options
var assemblyVersion = assembly
.GetCustomAttributes<AssemblyInformationalVersionAttribute>()
.FirstOrDefault()?
.InformationalVersion;
var userAgent = new ProductInfoHeaderValue("dapr-sdk-dotnet", $"v{assemblyVersion}").ToString();
callOptions.Headers!.Add("User-Agent", userAgent);

//Add the API token to the headers as well if it's populated
if (daprApiToken is not null)
{
var apiTokenHeader = GetDaprApiTokenHeader(daprApiToken);
if (apiTokenHeader is not null)
{
callOptions.Headers.Add(apiTokenHeader.Value.Key, apiTokenHeader.Value.Value);
}
}

return callOptions;
}

/// <summary>
/// Used to create the user-agent from the assembly attributes.
/// </summary>
/// <param name="assembly">The assembly the client is being built for.</param>
/// <returns>The header value containing the user agent information.</returns>
public static ProductInfoHeaderValue GetUserAgent(Assembly assembly)
{
var assemblyVersion = assembly
.GetCustomAttributes<AssemblyInformationalVersionAttribute>()
.FirstOrDefault()?
.InformationalVersion;
return new ProductInfoHeaderValue("dapr-sdk-dotnet", $"v{assemblyVersion}");
}

/// <summary>
/// Used to provision the header used for the Dapr API token on the HTTP or gRPC connection.
/// </summary>
/// <param name="daprApiToken">The value of the Dapr API token.</param>
/// <returns>If a Dapr API token exists, the key/value pair to use for the header; otherwise null.</returns>
public static KeyValuePair<string, string>? GetDaprApiTokenHeader(string? daprApiToken) =>
string.IsNullOrWhiteSpace(daprApiToken)
? null
: new KeyValuePair<string, string>("dapr-api-token", daprApiToken);
}
52 changes: 47 additions & 5 deletions src/Dapr.Common/DaprGenericClientBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
using System.Text.Json;
// ------------------------------------------------------------------------
// Copyright 2024 The Dapr Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------------

using System.Reflection;
using System.Text.Json;
using System.Threading.Channels;
using Grpc.Net.Client;
using Microsoft.Extensions.Configuration;

Expand Down Expand Up @@ -170,8 +185,9 @@ public DaprGenericClientBuilder<TClientBuilder> UseTimeout(TimeSpan timeout)
/// Builds out the inner DaprClient that provides the core shape of the
/// runtime gRPC client used by the consuming package.
/// </summary>
/// <param name="assembly">The assembly the dependencies are being built for.</param>
/// <exception cref="InvalidOperationException"></exception>
protected (GrpcChannel channel, HttpClient httpClient, Uri httpEndpoint) BuildDaprClientDependencies()
protected (GrpcChannel channel, HttpClient httpClient, Uri httpEndpoint, string daprApiToken) BuildDaprClientDependencies(Assembly assembly)
{
var grpcEndpoint = new Uri(this.GrpcEndpoint);
if (grpcEndpoint.Scheme != "http" && grpcEndpoint.Scheme != "https")
Expand All @@ -184,22 +200,48 @@ public DaprGenericClientBuilder<TClientBuilder> UseTimeout(TimeSpan timeout)
// Set correct switch to make secure gRPC service calls. This switch must be set before creating the GrpcChannel.
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
}

var httpEndpoint = new Uri(this.HttpEndpoint);
if (httpEndpoint.Scheme != "http" && httpEndpoint.Scheme != "https")
{
throw new InvalidOperationException("The HTTP endpoint must use http or https.");
}

var channel = GrpcChannel.ForAddress(this.GrpcEndpoint, this.GrpcChannelOptions);
//Configure the HTTP client
var httpClient = ConfigureHttpClient(assembly);
this.GrpcChannelOptions.HttpClient = httpClient;

var channel = GrpcChannel.ForAddress(this.GrpcEndpoint, this.GrpcChannelOptions);
return (channel, httpClient, httpEndpoint, this.DaprApiToken);
}

/// <summary>
/// Configures the HTTP client.
/// </summary>
/// <param name="assembly">The assembly the user agent is built from.</param>
/// <returns>The HTTP client to interact with the Dapr runtime with.</returns>
private HttpClient ConfigureHttpClient(Assembly assembly)
{
var httpClient = HttpClientFactory is not null ? HttpClientFactory() : new HttpClient();

//Set the timeout as necessary
if (this.Timeout > TimeSpan.Zero)
{
httpClient.Timeout = this.Timeout;
}

//Set the user agent
var userAgent = DaprClientUtilities.GetUserAgent(assembly);
httpClient.DefaultRequestHeaders.Add("User-Agent", userAgent.ToString());

//Set the API token
var apiTokenHeader = DaprClientUtilities.GetDaprApiTokenHeader(this.DaprApiToken);
if (apiTokenHeader is not null)
{
httpClient.DefaultRequestHeaders.Add(apiTokenHeader.Value.Key, apiTokenHeader.Value.Value);
}

return (channel, httpClient, httpEndpoint);
return httpClient;
}

/// <summary>
Expand Down
16 changes: 11 additions & 5 deletions src/Dapr.Jobs/DaprJobsClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// ------------------------------------------------------------------------

using Dapr.Common;
using Microsoft.Extensions.Configuration;
using Autogenerated = Dapr.Client.Autogen.Grpc.v1;

namespace Dapr.Jobs;
Expand All @@ -21,17 +22,22 @@ namespace Dapr.Jobs;
/// </summary>
public sealed class DaprJobsClientBuilder : DaprGenericClientBuilder<DaprJobsClient>
{
/// <summary>
/// Used to initialize a new instance of the <see cref="DaprJobsClientBuilder"/>.
/// </summary>
/// <param name="configuration">An optional instance of <see cref="IConfiguration"/>.</param>
public DaprJobsClientBuilder(IConfiguration? configuration = null) : base(configuration)
{
}

/// <summary>
/// Builds the client instance from the properties of the builder.
/// </summary>
/// <returns>The Dapr client instance.</returns>
public override DaprJobsClient Build()
{
var daprClientDependencies = this.BuildDaprClientDependencies();

var daprClientDependencies = this.BuildDaprClientDependencies(typeof(DaprJobsClient).Assembly);
var client = new Autogenerated.Dapr.DaprClient(daprClientDependencies.channel);
var apiTokenHeader = this.DaprApiToken is not null ? DaprJobsClient.GetDaprApiTokenHeader(this.DaprApiToken) : null;

return new DaprJobsGrpcClient(client, daprClientDependencies.httpClient, apiTokenHeader);
return new DaprJobsGrpcClient(client, daprClientDependencies.httpClient, daprClientDependencies.daprApiToken);
}
}
Loading
Loading