Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions daprdocs/content/en/dotnet-sdk-docs/dotnet-client/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,5 +199,58 @@ await foreach (var items in subscribeConfigurationResponse.Source.WithCancellati
}
```

## Sidecar APIs
### Sidecar Health
The .NET SDK provides a way to poll for the sidecar health, as well as a convenience method to wait for the sidecar to be ready.

#### Poll for health
This health endpoint returns true when both the sidecar and your application are up (fully initialized).
```csharp
var client = new DaprClientBuilder().Build();

var isDaprReady = await client.CheckHealthAsync();

if (isDaprReady)
{
// Execute Dapr dependent code.
}
```

#### Poll for health (outbound)
This health endpoint returns true when Dapr has initialized all its components, but may not have finished setting up a communication channel with your application.

This is best used when you want to utilize a Dapr component in your startup path, for instance, loading secrets from a secretstore.

```csharp
var client = new DaprClientBuilder().Build();

var isDaprComponentsReady = await client.CheckOutboundHealthAsync();

if (isDaprComponentsReady)
{
// Execute Dapr component dependent code.
}
```

#### Wait for sidecar
The `DaprClient` also provides a helper method to wait for the sidecar to become healthy (components only). When using this method, it is recommended to include a `CancellationToken` to
allow for the request to timeout. Below is an example of how this is used in the `DaprSecretStoreConfigurationProvider`.

```csharp
// Wait for the Dapr sidecar to report healthy before attempting use Dapr components.
using (var tokenSource = new CancellationTokenSource(sidecarWaitTimeout))
{
await client.WaitForSidecarAsync(tokenSource.Token);
}

// Perform Dapr component operations here i.e. fetching secrets.
```

### Shutdown the sidecar
```csharp
var client = new DaprClientBuilder().Build();
await client.ShutdownSidecarAsync();
```

## Related links
- [.NET SDK examples](https://github.com/dapr/dotnet-sdk/tree/master/examples)
7 changes: 7 additions & 0 deletions src/Dapr.Client/DaprClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,13 @@ public HttpRequestMessage CreateInvokeMethodRequest<TRequest>(string appId, stri
/// <param name="cancellationToken">A <see cref="CancellationToken" /> that can be used to cancel the operation.</param>
/// <returns>A <see cref="Task" /> that will return when the operation has completed.</returns>
public abstract Task WaitForSidecarAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Send a command to the Dapr Sidecar telling it to shutdown.
/// </summary>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> that can be used to cancel the operation.</param>
/// <returns>A <see cref="Task" /> that will return when the operation has completed.</returns>
public abstract Task ShutdownSidecarAsync(CancellationToken cancellationToken = default);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpicking:

Do we generally try to keep the func/method name the same across SDKs?

I'm asking this because on go-sdk and python-sdk we named it as Shutdown rather than ShutdownSidecar[Async]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something that we'll define in the sig-sdk group, but I do think it's important to keep naming consistent. I do worry that both of those are a little to lax on the name though.

Do you think it's worth being different here for clarity? Having the API just be Shutdown, to me, doesn't really tell the user what it's doing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong opinion here,

There are negative and positive effects of being explicit in this case, I do not feel much comfortable exposing the word Sidecar I would say that "ShutdownDaprd" sound better, and, if by any chance we would have more than one sidecar (probably won't happen) it becomes explicit which one is shutting down. But again, it's nitpicking and I agree with you that just Shutdown is too generic

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to everything said here. This seems like a good chance to be consistent on the terminology.


/// <summary>
/// Perform service invocation using the request provided by <paramref name="request" />. The response will
Expand Down
101 changes: 57 additions & 44 deletions src/Dapr.Client/DaprClientGrpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -289,49 +289,6 @@ public override HttpRequestMessage CreateInvokeMethodRequest<TRequest>(HttpMetho
request.Content = JsonContent.Create<TRequest>(data, options: this.JsonSerializerOptions);
return request;
}

public override async Task<bool> CheckHealthAsync(CancellationToken cancellationToken = default)
{
var path = "/v1.0/healthz";
var request = new HttpRequestMessage(HttpMethod.Get, new Uri(this.httpEndpoint, path));
try
{
var response = await this.httpClient.SendAsync(request, cancellationToken);
return response.IsSuccessStatusCode;
}
catch (HttpRequestException)
{
return false;
}
}

public override async Task<bool> CheckOutboundHealthAsync(CancellationToken cancellationToken = default)
{
var path = "/v1.0/healthz/outbound";
var request = new HttpRequestMessage(HttpMethod.Get, new Uri(this.httpEndpoint, path));
try
{
var response = await this.httpClient.SendAsync(request, cancellationToken);
return response.IsSuccessStatusCode;
}
catch (HttpRequestException)
{
return false;
}
}

public override async Task WaitForSidecarAsync(CancellationToken cancellationToken = default)
{
while (true)
{
var response = await CheckOutboundHealthAsync(cancellationToken);
if (response)
{
break;
}
await Task.Delay(TimeSpan.FromMilliseconds(500), cancellationToken);
}
}

public override async Task<HttpResponseMessage> InvokeMethodWithResponseAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -1356,7 +1313,63 @@ public async override Task<UnlockResponse> Unlock(
return new UnlockResponse(GetUnLockStatus(response.Status));
}

#endregion
#endregion

#region Dapr Sidecar Methods

/// <inheritdoc/>
public override async Task<bool> CheckHealthAsync(CancellationToken cancellationToken = default)
{
var path = "/v1.0/healthz";
var request = new HttpRequestMessage(HttpMethod.Get, new Uri(this.httpEndpoint, path));
try
{
using var response = await this.httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
return response.IsSuccessStatusCode;
}
catch (HttpRequestException)
{
return false;
}
}

/// <inheritdoc/>
public override async Task<bool> CheckOutboundHealthAsync(CancellationToken cancellationToken = default)
{
var path = "/v1.0/healthz/outbound";
var request = new HttpRequestMessage(HttpMethod.Get, new Uri(this.httpEndpoint, path));
try
{
using var response = await this.httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
return response.IsSuccessStatusCode;
}
catch (HttpRequestException)
{
return false;
}
}

/// <inheritdoc/>
public override async Task WaitForSidecarAsync(CancellationToken cancellationToken = default)
{
while (true)
{
var response = await CheckOutboundHealthAsync(cancellationToken);
if (response)
{
break;
}
await Task.Delay(TimeSpan.FromMilliseconds(500), cancellationToken);
}
}

/// <inheritdoc/>
public async override Task ShutdownSidecarAsync(CancellationToken cancellationToken = default)
{
await client.ShutdownAsync(new Empty(), CreateCallOptions(null, cancellationToken));
}

#endregion
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty simple :)


protected override void Dispose(bool disposing)
{
Expand Down
14 changes: 14 additions & 0 deletions test/Dapr.Client.Test/DaprClientTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// ------------------------------------------------------------------------

using System;
using System.Threading.Tasks;
using Xunit;

namespace Dapr.Client
Expand Down Expand Up @@ -82,5 +83,18 @@ public void GetDaprApiTokenHeader_ApiTokenNotSet_NullApiTokenHeader()
var entry = DaprClient.GetDaprApiTokenHeader(null);
Assert.Equal(default, entry);
}

[Fact]
public async Task TestShutdownApi()
{
await using var client = TestClient.CreateForDaprClient();

var request = await client.CaptureGrpcRequestAsync(async daprClient =>
{
await daprClient.ShutdownSidecarAsync();
});

request.Dismiss();
}
}
}