GithubHelp home page GithubHelp logo

durabletask-dotnet's Introduction

Durable Task .NET Client SDK

Build status License: MIT

The Durable Task .NET Client SDK is a .NET Standard 2.0 library for implementing Durable Task orchestrations and activities. It's specifically designed to connect to a "sidecar" process, such as the Azure Functions .NET Isolated host, a special purpose sidecar container, or potentially even Dapr.

If you're looking to run fully self-hosted Durable Task Framework apps, see https://github.com/azure/durabletask.

Current Release: v1.0.0

NuGet packages

The following nuget packages are available for download.

Name Latest version Description
Azure Functions Extension NuGet version (Microsoft.Azure.Functions.Worker.Extensions.DurableTask) For Durable Functions in .NET isolated.
Abstractions SDK NuGet version (Microsoft.DurableTask.Abstractions) Contains base abstractions for Durable. Useful for writing re-usable libraries independent of the chosen worker or client.
Client SDK NuGet version (Microsoft.DurableTask.Client) Contains the core client logic for interacting with a Durable backend.
Client.Grpc SDK NuGet version (Microsoft.DurableTask.Client.Grpc) The gRPC client implementation.
Worker SDK NuGet version (Microsoft.DurableTask.Worker) Contains the core worker logic for having a IHostedService to process durable tasks.
Worker.Grpc SDK NuGet version (Microsoft.DurableTask.Worker.Grpc) The gRPC worker implementation.
Source Generators NuGet version (Microsoft.DurableTask.Generators) DurableTask source generators.

Usage with Azure Functions

This SDK can be used to build Durable Functions apps that run in the Azure Functions .NET Isolated worker process.

To get started, add the Microsoft.Azure.Functions.Worker.Extensions.DurableTask nuget package to your Function app project. Make sure you're using the latest .NET Worker SDK packages.

  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.10.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.0.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.7.0" OutputItemType="Analyzer" />
    <PackageReference Include="Microsoft.DurableTask.Generators" Version="1.0.0-preview.1" OutputItemType="Analyzer" />
  </ItemGroup>

You can then use the following code to define a simple "Hello, cities" durable orchestration, triggered by an HTTP request.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.Extensions.Logging;

namespace IsolatedFunctionApp1.Untyped;

static class HelloSequenceUntyped
{
    [Function(nameof(StartHelloCitiesUntyped))]
    public static async Task<HttpResponseData> StartHelloCitiesUntyped(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
        [DurableClient] DurableTaskClient client,
        FunctionContext executionContext)
    {
        ILogger logger = executionContext.GetLogger(nameof(StartHelloCitiesUntyped));

        string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(nameof(HelloCitiesUntyped));
        logger.LogInformation("Created new orchestration with instance ID = {instanceId}", instanceId);

        return client.CreateCheckStatusResponse(req, instanceId);
    }

    [Function(nameof(HelloCitiesUntyped))]
    public static async Task<string> HelloCitiesUntyped([OrchestrationTrigger] TaskOrchestrationContext context)
    {
        string result = "";
        result += await context.CallActivityAsync<string>(nameof(SayHelloUntyped), "Tokyo") + " ";
        result += await context.CallActivityAsync<string>(nameof(SayHelloUntyped), "London") + " ";
        result += await context.CallActivityAsync<string>(nameof(SayHelloUntyped), "Seattle");
        return result;
    }

    [Function(nameof(SayHelloUntyped))]
    public static string SayHelloUntyped([ActivityTrigger] string cityName, FunctionContext executionContext)
    {
        ILogger logger = executionContext.GetLogger(nameof(SayHelloUntyped));
        logger.LogInformation("Saying hello to {name}", cityName);
        return $"Hello, {cityName}!";
    }
}

You can find the full sample file, including detailed comments, at samples/AzureFunctionsApp/HelloCitiesUntyped.cs.

Class-based syntax

IMPORTANT: class based syntax in Durable Functions relies on a package reference to Microsoft.DurableTask.Generators. This is still in "preview" and may be subject to significant change before 1.0 or even post-1.0. It is recommended to stick with function-syntax for now.

A new feature in this version of Durable Functions for .NET Isolated is the ability to define orchestrators and activities as classes instead of as functions. When using the class-based syntax, source generators are used to generate function definitions behind the scenes to instantiate and invoke your classes.

The source generators also generate type-safe extension methods on the client and context objects, removing the need to reference other activities or orchestrations by name, or to use type parameters to declare the return type. The following sample demonstrates the same "Hello cities!" orchestration using the class-based syntax and source-generated extension methods.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.Extensions.Logging;

namespace IsolatedFunctionApp1.Typed;

public static class HelloCitiesTypedStarter
{
    [Function(nameof(StartHelloCitiesTyped))]
    public static async Task<HttpResponseData> StartHelloCitiesTyped(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
        [DurableClient] DurableTaskClient client,
        FunctionContext executionContext)
    {
        ILogger logger = executionContext.GetLogger(nameof(StartHelloCitiesTyped));

        string instanceId = await client.ScheduleNewHelloCitiesTypedInstanceAsync();
        logger.LogInformation("Created new orchestration with instance ID = {instanceId}", instanceId);

        return client.CreateCheckStatusResponse(req, instanceId);
    }
}

[DurableTask(nameof(HelloCitiesTyped))]
public class HelloCitiesTyped : TaskOrchestrator<string?, string>
{
    public async override Task<string> RunAsync(TaskOrchestrationContext context, string? input)
    {
        string result = "";
        result += await context.CallSayHelloTypedAsync("Tokyo") + " ";
        result += await context.CallSayHelloTypedAsync("London") + " ";
        result += await context.CallSayHelloTypedAsync("Seattle");
        return result;
    }
}

[DurableTask(nameof(SayHelloTyped))]
public class SayHelloTyped : TaskActivity<string, string>
{
    readonly ILogger? logger;

    public SayHelloTyped(ILoggerFactory? loggerFactory)
    {
        this.logger = loggerFactory?.CreateLogger<SayHelloTyped>();
    }

    public override Task<string> RunAsync(TaskActivityContext context, string cityName)
    {
        this.logger?.LogInformation("Saying hello to {name}", cityName);
        return Task.FromResult($"Hello, {cityName}!");
    }
}

You can find the full sample file, including detailed comments, at samples/AzureFunctionsApp/HelloCitiesTyped.cs.

Compatibility with Durable Functions in-process

This SDK is not compatible with Durable Functions for the .NET in-process worker. It only works with the newer out-of-process .NET Isolated worker.

There are also several features that aren't yet available:

  • Durable Entities is not yet supported.
  • APIs for calling HTTP endpoints are not yet available.
  • Several instance management APIs are not yet implemented.

Obtaining the Protobuf definitions

This project utilizes git submodules to obtain Protobuf definitions from durabletask-protobuf. You will need to obtain these to build the project.

To get the definitions, run git submodule update --init --recursive

Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.

When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

Trademarks

This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.

durabletask-dotnet's People

Contributors

allantargino avatar bachuv avatar cgillum avatar cliedeman avatar davidmrdavid avatar dependabot[bot] avatar jhueppauff avatar jorgelevy avatar jviau avatar kshyju avatar microsoft-github-policy-service[bot] avatar nytian avatar ryanlettieri avatar sebastianburckhardt avatar shivamkm07 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

durabletask-dotnet's Issues

Roadmap

Hi,
is there any roadmap or plan when to port Durable Entities and HTTP endpoint calls support?

Thank you

Activity and sub-orchestrator cancellation support

The TaskOptions class provided when scheduling activities and sub-orchestration calls supports providing a CancellationToken. However, it's not yet wired up to actually cancel the task. This issue tracks adding support for cancellation.

  • Implement the feature for activity calls, sub-orchestrator calls, and retry handlers.
  • Add detailed <remarks> documentation with examples for how cancellation can be used.
  • Add tests that cover all the scenarios supported by task cancellation

The type of namespace could not be found for POCO input and outputs

Appears that the generated code does not include a using statement to reference the namespace of the POCO types. A workaround can be to place the POCO types in the Microsoft.DurableTask namespace.

Example:

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace FunctionApp1_DotnetIsolated
{
    public class Person
    {
        public string Name { get; set; }
    }

    public class Message
    {
        public string Value { get; set; }
    }

    public class Function1
    {
        [Function(nameof(Function1))]
        public async Task<HttpResponseData> RunAsync([HttpTrigger] HttpRequestData req, [DurableClient] DurableClientContext durableContext)
        {
            var person = BinaryData.FromStream(req.Body).ToObjectFromJson<Person>();
            var instanceId = await durableContext.Client.ScheduleNewOrchestrationInstanceAsync(nameof(SayHelloOrchestrator), input: person);
            var result = await durableContext.Client.WaitForInstanceCompletionAsync(instanceId, CancellationToken.None, getInputsAndOutputs: true).ConfigureAwait(false);

            var response = req.CreateResponse();
            await response.WriteAsJsonAsync(result.ReadOutputAs<Message>()).ConfigureAwait(false);

            return response;
        }
    }

    public class SayHelloOrchestrator
    {
        [Function(nameof(SayHelloOrchestrator))]
        public async Task<Message> RunAsync([OrchestrationTrigger] TaskOrchestrationContext context)
        {
            var person = context.GetInput<Person>();
            var message = await context.CallActivityAsync<Message>(nameof(GetMessageActivity), person);
            return message;
        }
    }

    public class GetMessageActivity
    {
        [Function(nameof(GetMessageActivity))]
        public Task<Message> RunAsync([ActivityTrigger] Person person)
        {
            var message = new Message { Value = $"Hello {person.Name}!" };
            return Task.FromResult(message);
        }
    }
}

Generated code:

It is also interesting to note the double Task<Task<T>> return type, not sure if this is expected, seems strange though.

// <auto-generated/>
#nullable enable

using System;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.DurableTask
{
    public static class GeneratedDurableTaskExtensions
    {
        public static Task<Task<Message>> CallGetMessageActivityAsync(this TaskOrchestrationContext ctx, Person person, TaskOptions? options = null)
        {
            return ctx.CallActivityAsync<Task<Message>>("GetMessageActivity", person, options);
        }
    }
}

Package references:

<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="0.4.1-beta" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.ServiceBus" Version="5.4.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.7.0-preview2" OutputItemType="Analyzer" />
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.8.0" />

Query instance history support

Requirement

Orchestration clients should be able to query for an orchestration instance and also get back the history events for that orchestration, similar to the showHistory flag mentioned here.

Implementation notes

The .NET in-process Durable Functions implementation returned a JSON array. For this work-item, we should instead return something structured - e.g., an array of strongly typed objects.

Mac M1 Failing

Azure Functions

When using the DurableTask Extension on an arm64 (M1) Macbook, the following errors arise:

A host error has occurred during startup operation 'db8e7484-c9ee-4e34-9518-294d1409e04b'.
[2022-12-15T18:56:42.399Z] Grpc.Core: Error loading native library. Not found in any of the possible locations: /Users/jameswoodley/durabletask-dotnet/out/samples/bin/Debug/AzureFunctionsApp/net6.0/.azurefunctions/libgrpc_csharp_ext.arm64.dylib,/Users/jameswoodley/durabletask-dotnet/out/samples/bin/Debug/AzureFunctionsApp/net6.0/.azurefunctions/runtimes/osx-arm64/native/libgrpc_csharp_ext.arm64.dylib,/Users/jameswoodley/durabletask-dotnet/out/samples/bin/Debug/AzureFunctionsApp/net6.0/.azurefunctions/../../runtimes/osx-arm64/native/libgrpc_csharp_ext.arm64.dylib.

If I remove the [DurableClient] attribute from the function, then the project loads, but of course that means no more Durable Tasks..

Add missing integration tests

Not all features are currently tested. There are a set of TODOs here that we can use to help keep track of what still needs to be done.

Cannot execute orchestration written in F#

Hello, I'm trying to write durable azure functions using the isolated worker, and so far I had no luck with this. The orchestration throws an exception when triggered. The same solution written in C# works just fine. I created an example repository where the behavior can be observed https://github.com/landy/fsharp-isolated-durable-function.

The exception is

Executed 'Functions.HttpTestOrchestration' (Failed, Id=0660b3b4-f669-4b8c-ab3f-4a5066944186, Duration=163ms)
[2022-12-19T10:56:12.028Z] System.Private.CoreLib: Exception while executing function: Functions.HttpTestOrchestration. System.Private.CoreLib: Res
ult: Failure
Exception: System.InvalidOperationException: Unable to resolve service for type 'Microsoft.DurableTask.Client.IDurableTaskClientProvider' while att
empting to activate 'Microsoft.Azure.Functions.Worker.DefaultDurableClientContext+Converter'.
[2022-12-19T10:56:12.029Z]    at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider pro
vider)
[2022-12-19T10:56:12.030Z]    at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanc
eType, Object[] parameters)
[2022-12-19T10:56:12.031Z]    at Microsoft.Azure.Functions.Worker.Converters.DefaultInputConverterProvider.<GetOrCreateConverterInstance>b__7_0(Str
ing key, String converterTypeAssemblyQualifiedName) in D:\a\_work\1\s\src\DotNetWorker.Core\Converters\Converter\DefaultInputConverterProvider.cs:l
ine 85
[2022-12-19T10:56:12.032Z]    at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd[TArg](TKey key, Func`3 valueFactory, TArg factoryArg
ument)
[2022-12-19T10:56:12.033Z]    at Microsoft.Azure.Functions.Worker.Converters.DefaultInputConverterProvider.GetOrCreateConverterInstance(String conv
erterTypeName) in D:\a\_work\1\s\src\DotNetWorker.Core\Converters\Converter\DefaultInputConverterProvider.cs:line 73
[2022-12-19T10:56:12.035Z]    at Microsoft.Azure.Functions.Worker.Context.Features.DefaultInputConversionFeature.GetConverterFromContext(ConverterC
ontext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultInputConversionFeature.cs:line 108
[2022-12-19T10:56:12.036Z]    at Microsoft.Azure.Functions.Worker.Context.Features.DefaultInputConversionFeature.ConvertAsync(ConverterContext conv
erterContext) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultInputConversionFeature.cs:line 38
[2022-12-19T10:56:12.037Z]    at Microsoft.Azure.Functions.Worker.Context.Features.DefaultModelBindingFeature.BindFunctionInputAsync(FunctionContex
t context) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultModelBindingFeature.cs:line 79
[2022-12-19T10:56:12.038Z]    at Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor.ExecuteAsync(FunctionContext context) in D:\a\
_work\1\s\src\DotNetWorker.Core\Invocation\DefaultFunctionExecutor.cs:line 41
[2022-12-19T10:56:12.039Z]    at Microsoft.Azure.Functions.Worker.OutputBindings.OutputBindingsMiddleware.Invoke(FunctionContext context, FunctionE
xecutionDelegate next) in D:\a\_work\1\s\src\DotNetWorker.Core\OutputBindings\OutputBindingsMiddleware.cs:line 13
[2022-12-19T10:56:12.040Z]    at Microsoft.Azure.Functions.Worker.Handlers.InvocationHandler.InvokeAsync(InvocationRequest request, WorkerOptions w
orkerOptions) in D:\a\_work\1\s\src\DotNetWorker.Grpc\Handlers\InvocationHandler.cs:line 84
Stack:    at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
[2022-12-19T10:56:12.041Z]    at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanc
eType, Object[] parameters)
[2022-12-19T10:56:12.043Z]    at Microsoft.Azure.Functions.Worker.Converters.DefaultInputConverterProvider.<GetOrCreateConverterInstance>b__7_0(Str
ing key, String converterTypeAssemblyQualifiedName) in D:\a\_work\1\s\src\DotNetWorker.Core\Converters\Converter\DefaultInputConverterProvider.cs:l
ine 85
[2022-12-19T10:56:12.044Z]    at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd[TArg](TKey key, Func`3 valueFactory, TArg factoryArg
ument)
[2022-12-19T10:56:12.045Z]    at Microsoft.Azure.Functions.Worker.Converters.DefaultInputConverterProvider.GetOrCreateConverterInstance(String conv
erterTypeName) in D:\a\_work\1\s\src\DotNetWorker.Core\Converters\Converter\DefaultInputConverterProvider.cs:line 73
[2022-12-19T10:56:12.046Z]    at Microsoft.Azure.Functions.Worker.Context.Features.DefaultInputConversionFeature.GetConverterFromContext(ConverterC
ontext context) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultInputConversionFeature.cs:line 108
[2022-12-19T10:56:12.047Z]    at Microsoft.Azure.Functions.Worker.Context.Features.DefaultInputConversionFeature.ConvertAsync(ConverterContext conv
erterContext) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultInputConversionFeature.cs:line 38
[2022-12-19T10:56:12.049Z]    at Microsoft.Azure.Functions.Worker.Context.Features.DefaultModelBindingFeature.BindFunctionInputAsync(FunctionContex
t context) in D:\a\_work\1\s\src\DotNetWorker.Core\Context\Features\DefaultModelBindingFeature.cs:line 79
[2022-12-19T10:56:12.051Z]    at Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor.ExecuteAsync(FunctionContext context) in D:\a\
_work\1\s\src\DotNetWorker.Core\Invocation\DefaultFunctionExecutor.cs:line 41
[2022-12-19T10:56:12.053Z]    at Microsoft.Azure.Functions.Worker.OutputBindings.OutputBindingsMiddleware.Invoke(FunctionContext context, FunctionE
xecutionDelegate next) in D:\a\_work\1\s\src\DotNetWorker.Core\OutputBindings\OutputBindingsMiddleware.cs:line 13
[2022-12-19T10:56:12.055Z]    at Microsoft.Azure.Functions.Worker.Handlers.InvocationHandler.InvokeAsync(InvocationRequest request, WorkerOptions w
orkerOptions) in D:\a\_work\1\s\src\DotNetWorker.Grpc\Handlers\InvocationHandler.cs:line 84.

Source generator should sanitize method names

If the "name" value of the [DurableTask(name)] attribute isn't a valid C# identifier, the source generator will still generate a method using this invalid name.

For example, defining an activity like this:

[DurableTask("Foo.Bar")]
class FooBarActivity : TaskActivityBase<object, object>
{
    // ...
}

Results in generated output like this, which doesn't compile:

public static Task<object> CallFoo.BarAsync(this TaskOrchestrationContext ctx, object input, TaskOptions? options = null)
{
    return ctx.CallActivityAsync<object>("Foo.Bar", input, options);
}

Instead, the method name should be normalized into something valid, like this:

public static Task<object> CallFooBarAsync(this TaskOrchestrationContext ctx, object input, TaskOptions? options = null)
{
    return ctx.CallActivityAsync<object>("Foo.Bar", input, options);
}

Another preview release?

Just curious if another preview release is coming or will it be the final 1.0 version? I'm working on a port of some old webjobs code and am running into some blocking issues (mostly #35).

Count must be non-negative when purging durable metadata

Hi,

When I try to clean up the durable metadata but no instance matches the filter I get the following exception.
await client.Client.PurgeInstancesAsync(filter: new PurgeInstancesFilter(createdTimeFrom, createdTimeTo, states));

[2023-01-25T15:39:01.222Z] Executed 'Functions.CleanupOldWorkflows' (Failed, Id=ff84dea6-2dd1-4cf0-8e23-a3d6ba3bc491, Duration=5787ms)
[2023-01-25T15:39:01.223Z] System.Private.CoreLib: Exception while executing function: Functions.CleanupOldWorkflows. System.Private.CoreLib: Result: Failure
Exception: System.AggregateException: One or more errors occurred. (Count must be non-negative (Parameter 'count'))
[2023-01-25T15:39:01.224Z]  ---> System.ArgumentException: Count must be non-negative (Parameter 'count')
[2023-01-25T15:39:01.225Z]    at Microsoft.DurableTask.Check.Argument(Boolean condition, String name, String message, Object[] args)
[2023-01-25T15:39:01.226Z]    at Microsoft.DurableTask.Client.PurgeResult..ctor(Int32 count)
[2023-01-25T15:39:01.226Z]    at Microsoft.DurableTask.Client.Grpc.GrpcDurableTaskClient.PurgeInstancesCoreAsync(PurgeInstancesRequest request, CancellationToken cancellation)

It seems to be related to this line:
https://github.com/microsoft/durabletask-dotnet/blob/main/src/Client/Core/PurgeResult.cs#L17

Shouldn't the condition be count >= 0? This way an exception is thrown when no instance was removed.

I can also file a PR if needed for this :)

For Class-based syntax on Orchestrators / Activities that have no return value, how should we type them?

In our solution, we have orchestrators that have an input and no output... and we also have activities that have an input and sometimes no output

Using Class-based syntax requires a TInput, TOutput on the Base and in the activities not returning a value compiles fine but for Orchestrators a value must be returned

Our Orchestrators call sub orchestrators, downstream (4 total) with fan-in / fan-out on the activities

What is the best way to resolve this?

For example,

[DurableTask(nameof(FileUploadOrchestrator))]
public class FileUploadOrchestrator : TaskOrchestratorBase<FileUploadOrchestratorInput, CaptureRequestedOrchestratorInput>
{
    protected override async Task<CaptureRequestedOrchestratorInput> OnRunAsync(TaskOrchestrationContext taskOrchestrationContext,
        FileUploadOrchestratorInput input)
    {
        foreach (var blobMetadata in input.Blobs)
        {
            var captureRequestedOrchestratorInput =
                await taskOrchestrationContext.CallActivityAsync<CaptureRequestedOrchestratorInput>(nameof(FileUploadActivity), blobMetadata);

            await taskOrchestrationContext.CallSubOrchestratorAsync(nameof(CaptureRequestedOrchestrator), input: captureRequestedOrchestratorInput);
        }

        return null; //Nothing to return but required for compilation
    }
}

Target .NET Standard 2.0

There is a desire to support .NET Framework in the Azure Functions Isolated worker. To support Durable Functions, we'd need to also support .NET Framework in this SDK. That likely means switching from .NET 6 to .NET Standard 2.0.

Alternatively, we could add a net48 target, but this is less ideal since it would increase the size of the nuget package.

In order to continue to support C# 10 features, we'll need to add some code that implements the various classes, attributes, etc. that the C# compiler requires. Some useful discussion of this can be found here.

Durable Entities support?

Congrats on 1.0!

The readme calls out some missing features:

There are also several features that aren't yet available:
- Durable Entities is not yet supported.
- APIs for calling HTTP endpoints are not yet available.
- Several instance management APIs are not yet implemented.

Are these still missing in 1.0? Or is the documentation perhaps out of date?

Support for deterministic GUID generation

Just like we have for Durable Functions (see here), we need to add deterministic GUID generation for this SDK.

Also, we should replace our use of Guid.NewGuid() here with this new API to ensure sub-orchestration IDs are generated deterministically. This will be important for de-dupe purposes.

Support for suspend/resume operations

Assembly refactor and split

As a first step towards enabling devs to author re-usable libraries for DurableTask .NET sdk, we will split the assemblies up. This will allow libraries to take in a minimal amount of dependencies. Each assembly will correspond to a NuGet package of the same name. The rough assembly layout will be:

Name Description
Microsoft.DurableTask.Abstractions Contains base types for authoring and consuming orchestrations and activities
Microsoft.DurableTask.Generators Contains the source generators. note: will be bundled into abstractions nuget package.
Microsoft.DurableTask.Worker Contains the core framework for running a DTFx worker
Microsoft.DurableTask.Worker.Grpc Contains the gRPC specific worker implementation
Microsoft.DurableTask.Client Contains the durable task client and extensions.
Microsoft.DurableTask.Client.Grpc Contains the gRPC client implementation for interacting with the DTFx backend

Tasks

  • Split assemblies
  • Public shim factory in worker package
  • Common durable task builder --> enable services.AddDurableTask(b => b.UseGrpc()); secario
  • Allow for re-enterable builder --> enable multiple calls to services.AddDurableTask(b => ..), all adding to the same builder.
  • #73
  • Update code gen
  • Update samples.

Use Grpc.Net.Client when targeting non-netfx

Grpc.Core is out-of-support, replaced with Grpc.Net.Client. However, the new library drops netfx support. So, we will need to take on the follow work:

  • Submodule https://github.com/microsoft/durabletask-protobuf directly, as Microsoft.DurableTask.Sidecar.Protobuf references Grpc.Core.
  • Multi-target Microsoft.DurableTask.Client.Grpc and Microsoft.DurableTask.Worker.Grpc to netstandard2.0 and net6.0 (or net5.0?), and use Grpc.Core and Grpc.Net.Client under the two targets respectively.

Retry handlers don't work in Azure Functions

The discussion here made me realize that work hadn't been completed to enable structured error handling in the Azure Functions host. As a result, this documented sample doesn't work.

Here's a screenshot showing that the exception information was not correctly deserialized by the Durable Task SDK.

image

I suspect the problem was missed because the sidecar we use for integration tests is configured differently from the Azure Functions host.

Support for Durable Entities

Tasks

  1. durable-entities
    jviau
  2. durable-entities
    jviau
  3. durable-entities
    jviau
  4. durable-entities
    jviau
  5. durable-entities
  6. durable-entities
    jviau
  7. 18 of 18
    durable-entities
    jviau

Update README with getting started information

The README should include the following information:

  • A link to the Durable Functions documentation
  • Code snippets for .NET Isolated
  • Notes about source generators
  • Information about how this repo relates to the existing GitHub repos (azure/durabletask and azure/azure-functions-durable-extension)
  • Standard Microsoft repo README stuff

We can probably avoid talking about the sidecar for now to avoid confusing people.

[Request] Add extension to register all functions as services in DI container

Proposal

Introduce a new AddFuncitonsAsServices extension method on IFunctionsWorkerApplicationBuilder that automatically registers every function class detected by the functions framework in the dependency injection container.

This allows all function classes to participate in the container's self-checking logic during startup of the project, preventing runtime errors much later when actually executing specific functions if dependencies cannot be resolved.

Background

AspNetCore MVC provides a AddControllersAsServices() extension that registers all controllers in the container automatically.

This not only enables things such as being able to decorate controller classes via the container, but more importantly it allows controller classes to participate in the Microsoft DI container self-checking mechanism, which iterates through all services registered in the container and checks whether or not their dependencies can be resolved. This mechanism is enabled by default while in debug mode and allows developers to catch DI-related errors much earlier in the pipeline: the validation error is raised during container construction, instead of only during a specific call to the affected controller action.

My proposal is that a similar extension be created for functions/durable functions, which would allow consumers to easily add all functions as services. A potential name for such extension could be AddFuncitonsAsServices(), for consistency.

While consumers can register functions manually themselves and still have them participate in the container self-check process, this approach is error-prone, creates coupling with the durable tasks library, and potentially duplicates logic: users would be tasked with creating their own logic to "detect" all function classes, which is something that the functions framework already does to perform its main job. The alternative is to manually register each individual function as separate calls, which not only doesn't scale well, but is fairly noisy, leads to situations where some functions are forgotten to be registered (which could then potentially lead to runtime issues being detected only after deployment), and also forces developers to remember to add a registration whenever they create a new function.

If the team is resistant to adding such helper extension, the framework should at least expose discovered functions in some sort of callback so that consumers could then register those types themselves without having to replicate the function detection logic.

This is a spin-off of:

Source generator doesn't handle activity functions that return void

I have the following activity definition:

[Function(nameof(FlakeyActivity))]
public static void FlakeyActivity([ActivityTrigger] object _)
{
    throw new ApplicationException("Kah-BOOOOM!!!");
}

Source-gen tried to produce the following:

public static Task<void> CallFlakeyActivityAsync(this TaskOrchestrationContext ctx, object _, TaskOptions? options = null)
{
    return ctx.CallActivityAsync<void>("FlakeyActivity", _, options);
}

Obviously, this didn't compile. It seems we need to handle this as a special-case.

Allow for class-based durable in Azure Functions without relying on code-generator

One of our goals for is to allow for modular libraries (NuGet packages) consisting of usable orchestrations and activities. Specifically, we want these libraries to be usable in both Durable Standalone and Durable Functions without the library needing to have any Functions-sepcific dependencies included in it. This precludes us from relying on code generation to enable the "class-based" scenario in Durable Functions (as a library would not have had the required dependency to light up this code generator). We need to find an alternative way to discover and register class-based activities and orchestrations in the Durable Functions scenario.

Multi-instance query support

Requirement

Orchestration clients should be able to issue filtered queries to the state store for orchestration instances, as described here.

This will require work both in the SDK and sidecar projects.

One or more integration tests should also be written which covers this scenario.

Dependency Injection issues are only visible through API/Durable Monitor

Hi,

I just noticed an issue when using unresolved dependencies within the orchestrator.

When I reference a dependency, which was not injected in the program.cs. The function kick off the orchestrator but no indication is visible, that the dependency could not be loaded and the call is not successful.

private readonly ILogger _logger;
private readonly IService _service;

public FunctionOrchestrator(ILoggerFactory loggerFactory, IService service)
{
        _logger = loggerFactory.CreateLogger<FunctionTrigger>();
        _service = service;
}
[Function(nameof(HelloCitiesUntyped))]
public async Task<string> HelloCitiesUntyped([OrchestrationTrigger] TaskOrchestrationContext context)
{
        string result = "";
	result += await context.CallActivityAsync<string>(nameof(SayHelloUntyped), "Tokyo") + " ";
	result += await context.CallActivityAsync<string>(nameof(SayHelloUntyped), "London") + " ";
	result += await context.CallActivityAsync<string>(nameof(SayHelloUntyped), "Seattle");
	return result;
}

[Function(nameof(SayHelloUntyped))]
public string SayHelloUntyped([ActivityTrigger] string cityName, FunctionContext executionContext)
{
	ILogger logger = executionContext.GetLogger(nameof(SayHelloUntyped));
	logger.LogInformation("Saying hello to {name}", cityName);

	string hello = _service.Hello();

	return $"{hello}, {cityName}!";
}

image

Opening the Status Endpoint or the Durable Function Monitor shows exactly the issue:
image

However, with a "normal" Function, whenever there is a dependency which could not be resolved, we get an error message within the console telling the exact problem:
image

This behavior is quite frustrating as you can't really tell what the problem is unless you use the Status APIs.

You can find the code I created to reproduce here:
https://github.com/jhueppauff/DurableFunction-DependencyInjection

Purge instance support

Requirement

Orchestration clients should be able to purge orchestration data, as described here. Purging single instances and multiple instances using filters should be supported.

Note that this functionality also needs to be added to the sidecar.

One or more integration tests covering this scenario should also be added.

Implementation notes

The following integration test for the MSSQL backend can be used as a reference for writing tests in this project: https://github.com/microsoft/durabletask-mssql/blob/24fdfe6979f5af3d57b4f52aaa3eb30a2ba5038f/test/DurableTask.SqlServer.Tests/Integration/PurgeTests.cs#L35

Should TaskOrchestrationContext.ContinueAsNew accept null objects?

The current signature of TaskOrchestrationContext.ContinueAsNew implies nulls are not allowed. Should it be changed to this?

public abstract void ContinueAsNew(object? newInput = null, bool preserveUnprocessedEvents = true);

This would align with the signatures of TaskOrchestrationContext.CallSubOrchestratorAsync and TaskOrchestrationContext.CallActivityAsync.

My use case is running an eternal orchestration that doesn't take an input.

  public static async Task Run([OrchestrationTrigger] TaskOrchestrationContext context)
  {
      await DoStuff(context);
      await context.CreateTimer(TimeSpan.FromHours(1), CancellationToken.None);
      context.ContinueAsNew(null!);
  }

  private static async Task DoStuff(TaskOrchestrationContext context)
  ...

Avoid using input type when using typed functions

Is there a good way to not have to use an input type when using the Typed Durable Functions? I want to run a command and have it return. But it doesn't really have to have an input. I know this is a weird question but just checking

Running sample project on NET7.0 durable instance will never run

We have a copy of the sample project to check what is the impact of moving to Azure durable function.
When running Netazure functions with a durable task with the target framework:

<TargetFramework>net7.0</TargetFramework>

the orchestrator will never run. When we switch to

<TargetFramework>net7.0-windows</TargetFramework>

the problem is solved(on windows of course)

Switching back to :

<TargetFramework>net6.0</TargetFramework>

solves the problem at all.

as a test on both we tried to see what happens if we add:
await client.WaitForInstanceStartAsync(instanceId);

to the same logic. But in the end this will result in an error that the task cannot be started.

Can't use complex type as input & output with ActivityTrigger Attribute

I'm testing out durable tasks using isolated function in .NET 7

I seem to be having trouble building to project when I have a complex type as inputs parameters or return types for my activity functions.

[Function("CalculateBuyProbability")]
public static async Task<SentimentResult> Calculate([ActivityTrigger] int userId, ILogger log)
{
    ...
}

...

[Function("PrintReport")]
public static async Task Print([ActivityTrigger] List<SentimentResult> results, SentimentResult test, ILogger log)
{
   ...
}

SentimentResult here is a basic complex type

public class SentimentResult
{
    public int UserId { get; set; }
    public int Sentiment { get; set; }
}

I get build error:
C:\Users\niels\RiderProjects\TestApps\Company.FunctionApp1\Company.FunctionApp1\Microsoft.DurableTask.Generators\Microsoft.DurableTask.Generators.DurableTaskSourceGenerator\GeneratedDurableTaskExtensions.cs(13,33): error CS0246: The type or namespace name 'SentimentResult' could not be found (are you missing a using directive or an assembly reference?) [C:\Users\niels\RiderProjects\TestApps\Company.FunctionApp1\Company.FunctionApp1\Company.FunctionApp1.cspro
j]

What I've tried:

  • Removing the ActivityTriggerAttribute fixes the build error
  • Returning object instead of complex type fixes the build error

I'm using Rider as my IDE, but I don't think it's related as dotnet build via terminal returns the same.

In case it matters, my csproj with the package versions I'm using:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
        <AzureFunctionsVersion>V4</AzureFunctionsVersion>
        <OutputType>Exe</OutputType>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.11.0-preview2" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="0.4.1-beta" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
        <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.8.0-preview2" />
    </ItemGroup>
    <ItemGroup>
        <None Update="host.json">
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
    </ItemGroup>
</Project>

Activity output serialization uses mix of System.Text.Json and Newtonsoft.Json

Issue

When an activity function returns a value, this value is serialized using System.Text.Json. However, when an orchestration attempts to read this value, the reader uses Newtonsoft.Json. This inconsistency makes it possible for an activity to return a value that the orchestration can't read (for example, JsonElement objects).

This is also expected to be a problem for sub-orchestration outputs as well as external events.

A fix is needed which replaces the default message serializer used by DurableTask.Core with the data converter used by Microsoft.DurableTask.Client.

Review test cases and correctness of source generators

This issue is to track a review of the source generators. The work will include:

  • Adding test cases to ensure correctness
  • A pass on performance/reliability of the source generator
  • Refactoring to improve testability and behavior of the generator.
  • Evaluate migrating to IIncrementalGenerator.

Unable to cast object of type 'System.Text.Json.JsonElement' to type

There appears to be a bug when a POCO is used as an input it doesn't appear to deserialize the object to the POCO and JsonElement is not castable to other types. I observed this same issue with both the traditional function methods and the class-based tasks.

Error: One or more errors occurred. (Unable to cast object of type 'System.Text.Json.JsonElement' to type 'Microsoft.DurableTask.Person'.)

Example:

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using System;
using System.Threading;
using System.Threading.Tasks;

// POCO placed here because of https://github.com/microsoft/durabletask-dotnet/issues/35
namespace Microsoft.DurableTask
{
    public class Person
    {
        public string Name { get; set; }
    }

    public class Message
    {
        public string Value { get; set; }
    }
}

namespace FunctionApp1_DotnetIsolated
{
    public class Function1
    {
        [Function(nameof(Function1))]
        public async Task<HttpResponseData> RunAsync([HttpTrigger] HttpRequestData req, [DurableClient] DurableClientContext durableContext)
        {
            var person = BinaryData.FromStream(req.Body).ToObjectFromJson<Person>();
            var instanceId = await durableContext.Client.ScheduleNewOrchestrationInstanceAsync(nameof(SayHelloOrchestrator), input: person);
            var result = await durableContext.Client.WaitForInstanceCompletionAsync(instanceId, CancellationToken.None, getInputsAndOutputs: true).ConfigureAwait(false);

            var response = req.CreateResponse();
            await response.WriteAsJsonAsync(result.ReadOutputAs<Message>()).ConfigureAwait(false);

            return response;
        }
    }

    public class SayHelloOrchestrator
    {
        [Function(nameof(SayHelloOrchestrator))]
        public async Task<Message> RunAsync([OrchestrationTrigger] TaskOrchestrationContext context)
        {
            var person = context.GetInput<Person>();
            var message = await context.CallActivityAsync<Message>(nameof(GetMessageActivity), person);
            return message;
        }
    }

    public class GetMessageActivity
    {
        [Function(nameof(GetMessageActivity))]
        public Task<Message> RunAsync([ActivityTrigger] Person person)
        {
            var message = new Message { Value = $"Hello {person.Name}!" };
            return Task.FromResult(message);
        }
    }
}

Package references:

<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="0.4.1-beta" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.ServiceBus" Version="5.4.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.7.0-preview2" OutputItemType="Analyzer" />
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.8.0" />

Detailed API documentation

The SDK APIs need to be fully documented so that:

  1. Users can get rich information from Intellisense
  2. Documentation can be published to docs.microsoft.com

Backwards compatibility for DTFx

It would be useful to offer backwards compatibility for DTFx users who want to migrate away from self-hosting to Azure Functions hosting.

Orchestration restart client API

Durable Functions supports a RestartAsync API that makes it easy to restart an orchestration with the same input and either the same instance ID or a new instance ID. The implementation is here. This .NET SDK should also support this API.

Backwards compatibility for Durable Functions in-process

Users of today's Durable Functions may want to migrate their code to the .NET Isolated worker. To make this migration process easier, we should consider adding backwards compatibility support. This would ideally be implemented as a set of alternate interfaces that users can bind to whose implementations call into the "modern" APIs. These back-compat interfaces should also be isolated into a separate namespace, like Microsoft.DurableTask.Compatibility.Functions.

Change namespaces from DurableTask to Microsoft.DurableTask

This is to ensure the namespaces and nuget package names match. It's also to help customers understand that Microsoft owns and maintains the code, as opposed to some third party. This will also help distinguish the libraries from the legacy DurableTask.Core libraries, which shouldn't be used directly.

Custom serializer support

According to the documentation, Durable Functions in the isolated process don't support custom serializers. Is it on the roadmap but just hasn't been implemented yet, or will it simply not be supported?

I'd like the ability to define custom JsonConverters. Some of my durable inputs/outputs don't deserialize by default with System.Text.Json and are external classes, so decorating them with attributes isn't an option. I'm toying with two workarounds, none of which are ideal:

  • Define input/output DTOs specifically for serialization.
  • Make all my inputs and outputs a JsonDocument or JsonNode, and then de/serialize within the durable function.

Thank you for your consideration.

Provide replay-safe ILogger in orchestrations

At the moment, the orchestrator is created as a singleton using parameterless constructor.

static readonly ITaskOrchestrator singletonOrchestrator = new DurablePocoPayloadSample.Orchestrator();

Would it be possible to create it using ActivatorUtilities.CreateInstance instead so we could inject ILogger, IOptions and the like?

Rewind support

Durable Functions and the Azure Storage backend both support orchestration rewind, which allows restoring an orchestration instance back to its previous state after it has failed. This functionality should be made available to this SDK.

Rewind is currently in preview status. As part of this work, we should bring it onto the path to GA. This would likely require some changes to DTFx, such as completing the following work item: Azure/durabletask#731.

Checklist

  • Core DTFx updates
  • New DF API surface area
  • gRPC/Protobuf contract support
  • Integration tests

Cannot convert input parameter to type 'Microsoft.DurableTask.TaskOrchestrationContext' from type 'System.String'

I am trying to set durable functions as follow:

using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;

public class QAReportOrchestrationFunction
{
    [Function(nameof(TimerTriggerStart))]
    public static async Task TimerTriggerStart(
        [TimerTrigger("0 6 * * Mon", RunOnStartup = true)] TimerInfo timer,
        [DurableClient] DurableClientContext starter,
        FunctionContext functionContext)
    {
        var orchestrationId = await starter.Client.ScheduleNewOrchestrationInstanceAsync(nameof(RunOrchestrator));
    }

    [Function(nameof(RunOrchestrator))]
    public static async Task<string> RunOrchestrator(
        [OrchestrationTrigger] TaskOrchestrationContext context)
    {
        string result = "";
        result += await context.CallActivityAsync<string>(nameof(SayHello), "Tokyo") + " ";
        result += await context.CallActivityAsync<string>(nameof(SayHello), "London") + " ";
        result += await context.CallActivityAsync<string>(nameof(SayHello), "Seattle");
        return result;
    }

    [Function(nameof(SayHello))]
    public static string SayHello(
        [ActivityTrigger] string name)
    {
        return $"Hello {name}!";
    }
}
internal class Program
{
    public static async Task Main(string[] _)
    {
        var host = new HostBuilder()
            .ConfigureFunctionsWorkerDefaults()
            .Build();

        await host.RunAsync();
    }
}
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <AzureFunctionsVersion>v4</AzureFunctionsVersion>
    <OutputType>Exe</OutputType>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.8.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="0.4.1-beta" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Timer" Version="4.1.0" />
    <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.3.0" OutputItemType="Analyzer" />
  </ItemGroup>

  <ItemGroup>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
  </ItemGroup>

</Project>
{
  "IsEncrypted": false,
  "Values": {
    "ASPNETCORE_ENVIRONMENT": "Development",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
  }
}

However, when the client function tries to invoke the orchestrator function, I get this error:

[2022-08-31T10:42:51.972Z] Executing 'Functions.TimerTriggerStart' (Reason='Timer fired at 2022-08-31T12:42:51.9287484+02:00', Id=2722cf45-75e1-4c74-ba2b-f36733270863)
[2022-08-31T10:42:51.976Z] Trigger Details: UnscheduledInvocationReason: RunOnStartup
[2022-08-31T10:42:53.760Z] Worker process started and initialized.
[2022-08-31T10:42:56.558Z] Host lock lease acquired by instance ID '00000000000000000000000067BD87D1'.
[2022-08-31T10:43:19.964Z] Scheduling new RunOrchestrator orchestration with instance ID '0e90a778968a44d3ab39c924d01bc6c7' and 0 bytes of input data.
[2022-08-31T10:43:20.287Z] Executing 'Functions.RunOrchestrator' (Reason='(null)', Id=e4b48ac1-2255-45fd-9be8-10b7b0fd371e)
[2022-08-31T10:43:21.283Z] Executed 'Functions.TimerTriggerStart' (Succeeded, Id=2722cf45-75e1-4c74-ba2b-f36733270863, Duration=29336ms)
[2022-08-31T10:43:21.342Z] Executed 'Functions.RunOrchestrator' (Failed, Id=e4b48ac1-2255-45fd-9be8-10b7b0fd371e, Duration=1069ms)
[2022-08-31T10:43:21.345Z] System.Private.CoreLib: Exception while executing function: Functions.RunOrchestrator. System.Private.CoreLib: Result: Failure
Exception: Microsoft.Azure.Functions.Worker.FunctionInputConverterException: Error converting 1 input parameters for Function 'RunOrchestrator': Cannot convert input parameter 'context' to type 'Microsoft.DurableTask.TaskOrchestrationContext' from type 'System.String'. Error:System.Text.Json.JsonException: 'C' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
[2022-08-31T10:43:21.345Z]  ---> System.Text.Json.JsonReaderException: 'C' is an invalid start of a value. LineNumber: 0 | BytePositionInLine: 0.

I literally don't get where this string is coming from and what could be its value. This is a brand new project and I'm cleaning Azurite storage at each run. Tried both VS and VS Code

EDIT:
Changing the RunOrchestrator function parameter I can get the string value:

    [Function(nameof(RunOrchestrator))]
    public static async Task<string> RunOrchestrator(
        [OrchestrationTrigger] string context)
    {
        await Task.Delay(190);
        string result = "";
        return result;
    }

context value: CiAxZTZhYjQxMmMyMzc0Zjc5YWM4NzllMzFkNmE3ZDkwYSIaCP///////////wESCwiuqL2YBhCU2Ys5cgAigwEI////////////ARIMCK2ovZgGEOTKvcQDGmgKD1J1bk9yY2hlc3RyYXRvchIAGgcKBSJhYWEiIkoKIDFlNmFiNDEyYzIzNzRmNzlhYzg3OWUzMWQ2YTdkOTBhEiYKJDRlZDA4ZDE4LTg4NjMtNDMyNC05NzBhLWY1MjkwNDQ3YTEyYg==

Allow registering Function classes as services in the container

While converting my durable function to isolated in .NET7, I ran into a scenario that I'm pretty sure is caused by the fact that function class instances are constructed using something like ActivatorUtilities.CreateInstance instead of resolving the type from the container.

I have a simple activity that performs a DELETE Http call to one of my APIs:

[DurableTask]
public sealed class DeleteReservationTask : TaskActivityBase<Guid, HttpResponseMessage>
{
    private readonly HttpClient httpClient;

    public DeleteReservationTask(HttpClient httpClient)
        => this.httpClient = httpClient;

    protected override async Task<HttpResponseMessage?> OnRunAsync(TaskActivityContext context, Guid input)
        => await this.httpClient.DeleteAsync($"Reservations/{input}");
}

To configure this client, I'm using the AddHttpClient method like this:

        services
            .AddHttpClient<DeleteReservationTask>()
            .ConfigureHttpClient((provider, client) =>
            {
                var settings = provider.GetRequiredService<IOptions<MyApiSettings>>();
                client.BaseAddress = settings.Value.BaseAddress;
            })

When running the integration however, I get a service activation exception:

[2022-11-25T14:53:24.673Z] System.Private.CoreLib: Exception while executing function: Functions.DeleteReservationTask. System.Private.CoreLib: Result: Failure
Exception: System.AggregateException: One or more errors occurred. (Unable to resolve service for type 'System.Net.Http.HttpClient' while attempting to activate 'MyNamespace.DeleteReservationTask'.)

This is happening because AddHttpClient registers a named HttpClient instance that is resolved only when requesting the service it was registered with. Since the functions runtime is not taking the type from the container, and instead is building it up using ActivatorUtilities, it will ignore that registration and attempt to resolve a "nameless" HttpClient from the container when building the type, which will obviously never work.

This makes sense as a default, but I'd like to propose an alternative like the one used by MVC controllers that provide you with a AddControllersAsServices() extension that registers all controllers in the container directly, causing the controller activation logic to request the controller type from the container instead of building the type up manually with ActivatorUtilities.CreateInstance/CreateFactory. You could call it AddFuncitonsAsServices() or similar, for consistency. Once registered, function classes should then be resolved from the container instead of built-up on the fly, which would then respect whatever registrations are tied to it.

This of course affects other types of patterns other than the typed HttpClient registrations. Things such as decorators would also be impossible to register and use today if they target the function class type directly.

Another benefit of something like AddControllersAsServices is that it allows the DI container's built-in self-checking mechanism (enabled in debugging by default) to be able to detect missing dependencies before having to run each controller: when they are not registered as services, the container is oblivious to them and cannot perform this check for controller classes. The same benefit would exist for function classes as well.

As for workarounds if anyone is interested, I'll probably just inject the IHttpClientFactory and manually fetch the HttpClient from it using the class name (which is what happens automatically when the mechanism works as intended). Another workaround of course would be to create an indirection to a second WhateverService, that then relies on HttpClient and is injected into the function class.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.