Problems Running Temporal Workflow from .NET WebAPI

I’m trying to do some internal demos of the temporal dotnet-sdk for a project but am having issues triggering a workflow from an API call. I am sure I am just going about things the wrong way but would appreciate some guidance.

I am using the MoneyTransfer example workflow and just trying to throw the trigger into an API POST call that looks like:

using Microsoft.AspNetCore.Mvc;
using Temporalio.MoneyTransferProject.MoneyTransferWorker;
using Temporalio.Client;
using TemporalTestAPI.Models;

namespace TemporalTestAPI.Controllers
{
    [Route("api/workflow")]
    [ApiController]
    public class WorkflowController(ITemporalClient client) : ControllerBase
    {
        private const string TASK_QUEUE = "MONEY_TRANSFER_TASK_QUEUE";

        [HttpPost("start")]
        public async Task<IActionResult> Start([FromBody] WorkflowStartDto dto)
        {
            var details = new PaymentDetails(
                SourceAccount: dto.SourceAccount,
                TargetAccount: dto.TargetAccount,
                Amount: dto.Amount,
                ReferenceId: dto.ReferenceId
            );

            string workflowId = $"pay-invoice-{Guid.NewGuid()}";

            try
            {
                WorkflowHandle handle = await client.StartWorkflowAsync(
                    (MoneyTransferWorkflow wf) => wf.RunAsync(details),
                    new WorkflowOptions(id: workflowId, taskQueue: TASK_QUEUE)
                );

                return Ok(handle);
            }
            catch (Exception e)
            {
                return BadRequest(e);
            }
        }
    }
}

My program.cs looks like:

using Microsoft.AspNetCore.StaticFiles;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers(options =>
    {
        options.ReturnHttpNotAcceptable = true;
    }).AddNewtonsoftJson()
    .AddXmlDataContractSerializerFormatters();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddProblemDetails(options =>
{
    options.CustomizeProblemDetails = ctx =>
    {
        ctx.ProblemDetails.Extensions.Add("server", Environment.MachineName);
    };
});

builder.Services.AddTemporalClient("localhost:7233", "default");

WebApplication app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

When I run this (with an external worker) I keep getting a loop saying “withdrawing $400 from account 85-150” which is just the first step of the workflow, it never continue past that point. I just want the endpoint to return the workflow id and the run id.

I appreciate any guidance!

What does the UI and history show? Is the activity failing?

I noticed just now in the UI as I was going to get a screenshot for you that there was actually an exception being thrown by the first step of the workflow and even though it was in the NonRetryableErrorTypes it was still retrying so the workflow never failed due to me throwing the error incorrectly, I just assumed it was stuck!

Can you give me more details? I ran the steps in the README at GitHub - temporalio/money-transfer-project-template-dotnet and the workflow completed successfully.

Yeah, so running the steps in that README worked perfectly fine, for me what happened was I am trying to demo using a REST API call in .net to trigger a workflow. The above endpoint took the PaymentDetails object properties from the request body and then starts the workflow, now since I pass in the PaymentDetails I can make them whatever I want and the SourceAccount I used didn’t match anything in the object at: MoneyTransferWorker/BankingService/BankingService.cs#L10
so it threw an error.

Now I just recreated the retrying workflow by what seems like an error in the example. On this line it defines the NonRetryableErrorTypes but the error thrown doesn’t include that type so it will alway retry.

The line that was throwing was here

I fixed it so it properly doesn’t retry by changing that line to: throw new ApplicationFailureException("Withdrawal failed", ex, errorType: ex.GetType().Name);

Makes sense, I was concerned when you said “even though it was in the NonRetryableErrorTypes it was still retrying”, but yes, if an exception is not marked non-retryable it will retry.

Alternatively can do throw new ApplicationFailureException("Withdrawal failed", ex, nonRetryable: true);