Skip to content

Workflow tutorials C# #1182

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

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open

Conversation

marcduiker
Copy link
Contributor

@marcduiker marcduiker commented Mar 21, 2025

Description

This PR adds workflow examples for C# in the tutorials folder.

Issue reference

We strive to have all PRs being opened based on an issue, where the problem or feature have been discussed prior to implementation.

Please reference the issue this PR will close: #1183

Checklist

Please make sure you've completed the relevant tasks for this PR, out of the following list:

  • The quickstart code compiles correctly
  • You've tested new builds of the quickstart if you changed quickstart code
  • You've updated the quickstart's README if necessary
  • If you have changed the steps for a quickstart be sure that you have updated the automated validation accordingly. All of our quickstarts have annotations that allow them to be executed automatically as code. For more information see mechanical-markdown. For user guide with examples see Examples.

Signed-off-by: Marc Duiker <[email protected]>
Signed-off-by: Marc Duiker <[email protected]>
Signed-off-by: Marc Duiker <[email protected]>
Signed-off-by: Marc Duiker <[email protected]>
Signed-off-by: Marc Duiker <[email protected]>
Signed-off-by: Marc Duiker <[email protected]>
@marcduiker marcduiker changed the title Workflow tutorials Workflow tutorials C# Mar 21, 2025
Signed-off-by: Marc Duiker <[email protected]>
Signed-off-by: Marc Duiker <[email protected]>
Signed-off-by: Marc Duiker <[email protected]>
Signed-off-by: Marc Duiker <[email protected]>
Signed-off-by: Marc Duiker <[email protected]>
Signed-off-by: Marc Duiker <[email protected]>
Signed-off-by: Marc Duiker <[email protected]>
Signed-off-by: Marc Duiker <[email protected]>
Signed-off-by: Marc Duiker <[email protected]>
Signed-off-by: Marc Duiker <[email protected]>
@marcduiker marcduiker marked this pull request as ready for review March 25, 2025 14:48
@marcduiker marcduiker requested review from a team as code owners March 25, 2025 14:48
@marcduiker marcduiker requested a review from WhitWaldo April 15, 2025 12:57
Copy link
Contributor

@WhitWaldo WhitWaldo left a comment

Choose a reason for hiding this comment

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

Several changes for .NET syntax and formatting changes (e.g. use var, primary constructors and static classes for constants), improvements to how instances should be injected from DI and

{
// Do use these deterministic methods and properties on the WorkflowContext instead.
// These operations create the same value when the workflow is replayed.
var orderId = context.NewGuid().ToString();
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't deterministic

public override async Task<InventoryResult> RunAsync(WorkflowActivityContext context, OrderItem orderItem)
{
// Beware of non-idempotent operations in an activity.
// Dapr Workflow guarantees at-least-once execution of activities, so activities might be executed more than once.
Copy link
Contributor

Choose a reason for hiding this comment

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

While true, I'd supplement with some context in that if the workflow activity successfully returns a value, it will not be run again as its value will be used from the workflow context. This makes for a better argument then that workflows should do as little as possible so they improve their success rate and minimize being executed more than once (but should they be, yes, designing for idempotency would be ideal).

}
}

internal sealed record OrderItem(string Id, string Description, double UnitPrice, int Quantity);
Copy link
Contributor

Choose a reason for hiding this comment

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

As the OrderItem's ID was previously created from a Guid, there's little reason the ID couldn't just remain a GUID all the way through.

{
public override async Task<int> RunAsync(WorkflowContext context, string orderItem)
{
int resultA = await context.CallActivityAsync<int>(
Copy link
Contributor

Choose a reason for hiding this comment

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

It's far more common for developers to use var as in var resultA instead of repeating the type.

/// If there are in-flight workflow instances that were started with the previous version
/// of this workflow, these will fail when the new version of the workflow is deployed
/// and the workflow name remains the same, since the runtime parameters do not match with the persisted state.
/// It is recommended to version workflows by creating a new workflow class with a new name:
Copy link
Contributor

Choose a reason for hiding this comment

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

Might also mention something about avoiding use of perpetual workflows as this removes the opportunity to swap out workflows.


app.MapPost("/terminate/{instanceId}", async (string instanceId) => {
await using var scope = app.Services.CreateAsyncScope();
var workflowClient = scope.ServiceProvider.GetRequiredService<DaprWorkflowClient>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Nope, see above

});

app.MapPost("/resume/{instanceId}", async (string instanceId) => {
await using var scope = app.Services.CreateAsyncScope();
Copy link
Contributor

Choose a reason for hiding this comment

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

Nope, see above


app.MapPost("/suspend/{instanceId}", async (string instanceId) => {
await using var scope = app.Services.CreateAsyncScope();
var workflowClient = scope.ServiceProvider.GetRequiredService<DaprWorkflowClient>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Nope, see above


app.MapGet("/status/{instanceId}", async (string instanceId) => {
await using var scope = app.Services.CreateAsyncScope();
var workflowClient = scope.ServiceProvider.GetRequiredService<DaprWorkflowClient>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Nope, see above

</PropertyGroup>

<ItemGroup>
<PackageReference Include="Dapr.Workflow" Version="1.15.2" />
Copy link
Contributor

Choose a reason for hiding this comment

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

Version bump

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add more Workflow examples
2 participants