diff --git a/CHANGELOG.md b/CHANGELOG.md
index 418fe8729..55f9d0172 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## v1.9.0 (unreleased)
+
+- Introduce default version setting to DurableTaskClient and expose to orchestrator ([#393](https://github.com/microsoft/durabletask-dotnet/pull/393))
+
## v1.8.1
- Add timeout to gRPC workitem streaming ([#390](https://github.com/microsoft/durabletask-dotnet/pull/390))
diff --git a/src/Abstractions/TaskOrchestrationContext.cs b/src/Abstractions/TaskOrchestrationContext.cs
index b37b46d36..4eb087bae 100644
--- a/src/Abstractions/TaskOrchestrationContext.cs
+++ b/src/Abstractions/TaskOrchestrationContext.cs
@@ -59,6 +59,11 @@ public abstract class TaskOrchestrationContext
///
public abstract bool IsReplaying { get; }
+ ///
+ /// Gets the version of the current orchestration instance, which was set when the instance was created.
+ ///
+ public abstract string Version { get; }
+
///
/// Gets the entity feature, for interacting with entities.
///
diff --git a/src/Client/Core/DependencyInjection/DurableTaskClientBuilderExtensions.cs b/src/Client/Core/DependencyInjection/DurableTaskClientBuilderExtensions.cs
index bb274ad08..5c8547592 100644
--- a/src/Client/Core/DependencyInjection/DurableTaskClientBuilderExtensions.cs
+++ b/src/Client/Core/DependencyInjection/DurableTaskClientBuilderExtensions.cs
@@ -89,4 +89,16 @@ public static IDurableTaskClientBuilder UseBuildTarget(this I
});
return builder;
}
+
+ ///
+ /// Sets the default version for this builder. This version will be applied by default to all orchestrations if set.
+ ///
+ /// The builder to set the version for.
+ /// The version that will be used as the default version.
+ /// The original builder, for call chaining.
+ public static IDurableTaskClientBuilder UseDefaultVersion(this IDurableTaskClientBuilder builder, string version)
+ {
+ builder.Configure(options => options.DefaultVersion = version);
+ return builder;
+ }
}
diff --git a/src/Client/Core/DurableTaskClientOptions.cs b/src/Client/Core/DurableTaskClientOptions.cs
index 296b750db..05f19e7bd 100644
--- a/src/Client/Core/DurableTaskClientOptions.cs
+++ b/src/Client/Core/DurableTaskClientOptions.cs
@@ -13,6 +13,14 @@ public class DurableTaskClientOptions
DataConverter dataConverter = JsonDataConverter.Default;
bool enableEntitySupport;
+ ///
+ /// Gets or sets the version of orchestrations that will be created.
+ ///
+ ///
+ /// Currently, this is sourced from the AzureManaged client options.
+ ///
+ public string DefaultVersion { get; set; } = string.Empty;
+
///
/// Gets or sets the data converter. Default value is .
///
@@ -95,6 +103,11 @@ internal void ApplyTo(DurableTaskClientOptions other)
{
other.EnableEntitySupport = this.EnableEntitySupport;
}
+
+ if (!string.IsNullOrWhiteSpace(this.DefaultVersion))
+ {
+ other.DefaultVersion = this.DefaultVersion;
+ }
}
}
}
diff --git a/src/Client/Grpc/GrpcDurableTaskClient.cs b/src/Client/Grpc/GrpcDurableTaskClient.cs
index a408151d7..e7fd1da92 100644
--- a/src/Client/Grpc/GrpcDurableTaskClient.cs
+++ b/src/Client/Grpc/GrpcDurableTaskClient.cs
@@ -78,10 +78,20 @@ public override async Task ScheduleNewOrchestrationInstanceAsync(
{
Check.NotEntity(this.options.EnableEntitySupport, options?.InstanceId);
+ string version = string.Empty;
+ if (!string.IsNullOrEmpty(orchestratorName.Version))
+ {
+ version = orchestratorName.Version;
+ }
+ else if (!string.IsNullOrEmpty(this.options.DefaultVersion))
+ {
+ version = this.options.DefaultVersion;
+ }
+
var request = new P.CreateInstanceRequest
{
Name = orchestratorName.Name,
- Version = orchestratorName.Version,
+ Version = version,
InstanceId = options?.InstanceId ?? Guid.NewGuid().ToString("N"),
Input = this.DataConverter.Serialize(input),
};
diff --git a/src/Worker/Core/Shims/TaskOrchestrationContextWrapper.cs b/src/Worker/Core/Shims/TaskOrchestrationContextWrapper.cs
index 7151a4e0f..8ce7d5125 100644
--- a/src/Worker/Core/Shims/TaskOrchestrationContextWrapper.cs
+++ b/src/Worker/Core/Shims/TaskOrchestrationContextWrapper.cs
@@ -81,6 +81,9 @@ public override TaskOrchestrationEntityFeature Entities
}
}
+ ///
+ public override string Version => this.innerContext.Version;
+
///
/// Gets the DataConverter to use for inputs, outputs, and entity states.
///
diff --git a/test/Client/Grpc.Tests/DependencyInjection/DurableTaskClientBuilderExtensionsTests.cs b/test/Client/Grpc.Tests/DependencyInjection/DurableTaskClientBuilderExtensionsTests.cs
index 8c0498eb7..59c03d98c 100644
--- a/test/Client/Grpc.Tests/DependencyInjection/DurableTaskClientBuilderExtensionsTests.cs
+++ b/test/Client/Grpc.Tests/DependencyInjection/DurableTaskClientBuilderExtensionsTests.cs
@@ -67,6 +67,20 @@ public void UseGrpc_Callback_Sets()
options.Address.Should().BeNull();
}
+ [Fact]
+ public void UseDefaultVersion_DefaultVersion_Sets()
+ {
+ ServiceCollection services = new();
+ DefaultDurableTaskClientBuilder builder = new(null, services);
+ builder.UseDefaultVersion("0.1")
+ .UseGrpc();
+
+ IServiceProvider provider = services.BuildServiceProvider();
+ GrpcDurableTaskClientOptions options = provider.GetOptions();
+
+ options.DefaultVersion.Should().Be("0.1");
+ }
+
#if NET6_0_OR_GREATER
static GrpcChannel GetChannel() => GrpcChannel.ForAddress("http://localhost:9001");
#endif
diff --git a/test/Grpc.IntegrationTests/IntegrationTestBase.cs b/test/Grpc.IntegrationTests/IntegrationTestBase.cs
index fd63613f4..d6a915c92 100644
--- a/test/Grpc.IntegrationTests/IntegrationTestBase.cs
+++ b/test/Grpc.IntegrationTests/IntegrationTestBase.cs
@@ -2,13 +2,13 @@
// Licensed under the MIT License.
using System.Diagnostics;
-using Microsoft.DurableTask.Tests.Logging;
using Microsoft.DurableTask.Client;
+using Microsoft.DurableTask.Tests.Logging;
using Microsoft.DurableTask.Worker;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Xunit.Abstractions;
-using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.DurableTask.Grpc.Tests;
@@ -43,9 +43,9 @@ void IDisposable.Dispose()
GC.SuppressFinalize(this);
}
- protected async Task StartWorkerAsync(Action configure)
+ protected async Task StartWorkerAsync(Action workerConfigure, Action? clientConfigure = null)
{
- IHost host = this.CreateHostBuilder(configure).Build();
+ IHost host = this.CreateHostBuilder(workerConfigure, clientConfigure).Build();
await host.StartAsync(this.TimeoutToken);
return new HostTestLifetime(host, this.TimeoutToken);
}
@@ -53,8 +53,9 @@ protected async Task StartWorkerAsync(Action
/// Creates a configured to output logs to xunit logging infrastructure.
///
- /// Configures the durable task builder.
- protected IHostBuilder CreateHostBuilder(Action configure)
+ /// Configures the durable task worker builder.
+ /// Configures the durable task client builder.
+ protected IHostBuilder CreateHostBuilder(Action workerConfigure, Action? clientConfigure)
{
return Host.CreateDefaultBuilder()
.ConfigureLogging(b =>
@@ -68,13 +69,14 @@ protected IHostBuilder CreateHostBuilder(Action confi
services.AddDurableTaskWorker(b =>
{
b.UseGrpc(this.sidecarFixture.Channel);
- configure(b);
+ workerConfigure(b);
});
services.AddDurableTaskClient(b =>
{
b.UseGrpc(this.sidecarFixture.Channel);
b.RegisterDirectly();
+ clientConfigure?.Invoke(b);
});
});
}
diff --git a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs
index 071dbad5c..3cbc0d793 100644
--- a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs
+++ b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs
@@ -3,11 +3,11 @@
using System.Text.Json;
using System.Text.Json.Nodes;
-using Microsoft.DurableTask.Worker;
+using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Tests.Logging;
+using Microsoft.DurableTask.Worker;
using Microsoft.Extensions.DependencyInjection;
using Xunit.Abstractions;
-using Microsoft.DurableTask.Client;
namespace Microsoft.DurableTask.Grpc.Tests;
@@ -561,8 +561,37 @@ public async Task SpecialSerialization()
Assert.Equal("new value", output?["newProperty"]?.ToString());
}
+ // TODO: Additional versioning tests
+ [Fact]
+ public async Task OrchestrationVersionPassedThroughContext()
+ {
+ var version = "0.1";
+ await using HostTestLifetime server = await this.StartWorkerAsync(b =>
+ {
+ b.AddTasks(tasks => tasks
+ .AddOrchestratorFunc("Versioned_Orchestration", (ctx, input) =>
+ {
+ return ctx.CallActivityAsync("Versioned_Activity", ctx.Version);
+ })
+ .AddActivityFunc("Versioned_Activity", (ctx, input) =>
+ {
+ return $"Orchestration version: {input}";
+ }));
+ }, c =>
+ {
+ c.UseDefaultVersion(version);
+ });
+
+ var instanceId = await server.Client.ScheduleNewOrchestrationInstanceAsync("Versioned_Orchestration", input: string.Empty);
+ var result = await server.Client.WaitForInstanceCompletionAsync(instanceId, getInputsAndOutputs: true, this.TimeoutToken);
+ var output = result.ReadOutputAs();
+
+ Assert.NotNull(output);
+ Assert.Equal(output, $"Orchestration version: {version}");
+
+ }
+
// TODO: Test for multiple external events with the same name
// TODO: Test for ContinueAsNew with external events that carry over
// TODO: Test for catching activity exceptions of specific types
- // TODO: Versioning tests
}