Skip to content
Open
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
6 changes: 6 additions & 0 deletions Aspire.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<Project Path="src/Components/Aspire.Microsoft.Azure.Cosmos/Aspire.Microsoft.Azure.Cosmos.csproj" />
<Project Path="src/Components/Aspire.Microsoft.Azure.StackExchangeRedis/Aspire.Microsoft.Azure.StackExchangeRedis.csproj" />
<Project Path="src/Components/Aspire.Microsoft.Data.SqlClient/Aspire.Microsoft.Data.SqlClient.csproj" />
<Project Path="src/Components/Aspire.Microsoft.DurableTask.AzureManaged/Aspire.Microsoft.DurableTask.AzureManaged.csproj" />
<Project Path="src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/Aspire.Microsoft.EntityFrameworkCore.Cosmos.csproj" />
<Project Path="src/Components/Aspire.Microsoft.EntityFrameworkCore.SqlServer/Aspire.Microsoft.EntityFrameworkCore.SqlServer.csproj" />
<Project Path="src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.csproj" />
Expand Down Expand Up @@ -147,6 +148,10 @@
<Project Path="playground/AzureFunctionsWithDts/AzureFunctionsWithDts.AppHost/AzureFunctionsWithDts.AppHost.csproj" />
<Project Path="playground/AzureFunctionsWithDts/AzureFunctionsWithDts.Functions/AzureFunctionsWithDts.Functions.csproj" />
</Folder>
<Folder Name="/playground/DurableTaskWorkerWithDts/">
<Project Path="playground/DurableTaskWorkerWithDts/DurableTaskWorkerWithDts.AppHost/DurableTaskWorkerWithDts.AppHost.csproj" />
<Project Path="playground/DurableTaskWorkerWithDts/DurableTaskWorkerWithDts.Worker/DurableTaskWorkerWithDts.Worker.csproj" />
</Folder>
<Folder Name="/playground/ProjectResourceExtensions/">
<Project Path="playground/ProjectResourceExtensions/ProjectResourceExtensions.AppHost/ProjectResourceExtensions.AppHost.csproj" />
<Project Path="playground/ProjectResourceExtensions/StandardService/StandardService.csproj" />
Expand Down Expand Up @@ -436,6 +441,7 @@
<Project Path="tests/Aspire.Keycloak.Authentication.Tests/Aspire.Keycloak.Authentication.Tests.csproj" />
<Project Path="tests/Aspire.Microsoft.Azure.Cosmos.Tests/Aspire.Microsoft.Azure.Cosmos.Tests.csproj" />
<Project Path="tests/Aspire.Microsoft.Azure.StackExchangeRedis.Tests/Aspire.Microsoft.Azure.StackExchangeRedis.Tests.csproj" />
<Project Path="tests/Aspire.Microsoft.DurableTask.AzureManaged.Tests/Aspire.Microsoft.DurableTask.AzureManaged.Tests.csproj" />
<Project Path="tests/Aspire.Microsoft.Data.SqlClient.Tests/Aspire.Microsoft.Data.SqlClient.Tests.csproj" />
<Project Path="tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests.csproj" />
<Project Path="tests/Aspire.Microsoft.EntityFrameworkCore.SqlServer.Tests/Aspire.Microsoft.EntityFrameworkCore.SqlServer.Tests.csproj" />
Expand Down
3 changes: 3 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@
<!-- playground apps dependencies for AzureFunctionsWithDts -->
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.11.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" Version="1.0.1" />
<!-- Durable Task Scheduler dependencies (component + playground) -->
<PackageVersion Include="Microsoft.DurableTask.Worker.AzureManaged" Version="1.23.3" />
<PackageVersion Include="Microsoft.DurableTask.Client.AzureManaged" Version="1.23.3" />
<!-- playground apps dependencies for javascript -->
<PackageVersion Include="Swashbuckle.AspNetCore" Version="9.0.6" />
<!-- Pinned versions for Component Governance - Remove when root dependencies are updated -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

builder.AddAzureFunctionsProject<Projects.AzureFunctionsWithDts_Functions>("funcapp")
.WithHostStorage(storage)
.WithReference(taskHub);
.WithReference(taskHub)
.WaitFor(taskHub);

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
</PropertyGroup>

<ItemGroup>
<AspireProjectOrPackageReference Include="Aspire.Hosting.Azure.Functions" />
<AspireProjectOrPackageReference Include="Aspire.Hosting.AppHost" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DurableTaskWorkerWithDts.Worker\DurableTaskWorkerWithDts.Worker.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
var builder = DistributedApplication.CreateBuilder(args);

var scheduler = builder.AddDurableTaskScheduler("scheduler")
.RunAsEmulator();

var taskHub = scheduler.AddTaskHub("taskhub");

builder.AddProject<Projects.DurableTaskWorkerWithDts_Worker>("worker")
.WithReference(taskHub)
.WaitFor(taskHub);

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17245;http://localhost:15055",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21004",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22111"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15055",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19011",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20126"
}
},
"generate-manifest": {
"commandName": "Project",
"launchBrowser": true,
"dotnetRunMessages": true,
"commandLineArgs": "--publisher manifest --output-path aspire-manifest.json",
"applicationUrl": "http://localhost:15889",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16176"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Microsoft.DurableTask;

public class ChainingOrchestrator : TaskOrchestrator<object?, List<string>>
{
public override async Task<List<string>> RunAsync(TaskOrchestrationContext context, object? input)
{
ILogger logger = context.CreateReplaySafeLogger<ChainingOrchestrator>();
logger.LogInformation("Saying hello.");

var outputs = new List<string>
{
await context.CallActivityAsync<string>(nameof(SayHelloActivity), "Tokyo"),
await context.CallActivityAsync<string>(nameof(SayHelloActivity), "Seattle"),
await context.CallActivityAsync<string>(nameof(SayHelloActivity), "London")
};

// returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"]
return outputs;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">

<PropertyGroup>
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<AspireProjectOrPackageReference Include="Aspire.Microsoft.DurableTask.AzureManaged" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Playground.ServiceDefaults\Playground.ServiceDefaults.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.DurableTask.Worker;

var builder = Host.CreateApplicationBuilder(args);

builder.AddServiceDefaults();

builder.AddDurableTaskSchedulerWorker("taskhub", worker =>
{
worker.AddTasks(r =>
{
r.AddOrchestrator<ChainingOrchestrator>();
r.AddActivity<SayHelloActivity>();
});
});

var host = builder.Build();
host.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.DurableTask;

public class SayHelloActivity : TaskActivity<string, string>
{
private readonly ILogger<SayHelloActivity> _logger;

public SayHelloActivity(ILogger<SayHelloActivity> logger)
{
_logger = logger;
}

public override Task<string> RunAsync(TaskActivityContext context, string input)
{
_logger.LogInformation("Saying hello to {Name}", input);
return Task.FromResult($"Hello {input}!");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ internal static IResourceBuilder<DurableTaskSchedulerResource> RunAsExistingCore
/// <param name="builder">The resource builder for the scheduler.</param>
/// <param name="configureContainer">Callback that exposes underlying container used for emulation to allow for customization.</param>
/// <returns>The same <see cref="IResourceBuilder{DurableTaskSchedulerResource}"/> instance for chaining.</returns>
/// <remarks>
/// The Durable Task Scheduler emulator stores all orchestration and entity state in memory.
/// State is lost when the container is stopped. To preserve the container (and its in-memory state)
/// across application restarts, use <see cref="ContainerResourceBuilderExtensions.WithLifetime{T}(IResourceBuilder{T}, ContainerLifetime)"/>
/// with <see cref="ContainerLifetime.Persistent"/> in the <paramref name="configureContainer"/> callback.
/// </remarks>
/// <example>
/// Run the scheduler locally using the emulator:
/// <code>
Expand All @@ -120,6 +126,14 @@ internal static IResourceBuilder<DurableTaskSchedulerResource> RunAsExistingCore
/// .RunAsEmulator();
/// </code>
/// </example>
/// <example>
/// Persist the emulator container to preserve in-memory state across application restarts:
/// <code>
/// var builder = DistributedApplication.CreateBuilder(args);
/// var scheduler = builder.AddDurableTaskScheduler("scheduler")
/// .RunAsEmulator(c => c.WithLifetime(ContainerLifetime.Persistent));
/// </code>
/// </example>
[AspireExport(Description = "Configures the Durable Task scheduler to run using the local emulator.", RunSyncOnBackgroundThread = true)]
public static IResourceBuilder<DurableTaskSchedulerResource> RunAsEmulator(this IResourceBuilder<DurableTaskSchedulerResource> builder, Action<IResourceBuilder<DurableTaskSchedulerEmulatorResource>>? configureContainer = null)
{
Expand All @@ -136,6 +150,7 @@ public static IResourceBuilder<DurableTaskSchedulerResource> RunAsEmulator(this
builder.WithHttpEndpoint(name: "grpc", targetPort: 8080)
.WithEndpoint("grpc", endpoint => endpoint.Transport = "http2")
.WithHttpEndpoint(name: "http", targetPort: 8081)
.WithHttpHealthCheck(endpointName: "http", path: "/healthz", statusCode: 204)
.WithHttpEndpoint(name: "dashboard", targetPort: 8082)
.WithUrlForEndpoint("dashboard", c => c.DisplayText = "Scheduler Dashboard")
.WithAnnotation(new ContainerImageAnnotation
Expand Down Expand Up @@ -281,4 +296,79 @@ internal static IResourceBuilder<DurableTaskHubResource> WithTaskHubNameCore(
IResourceBuilder<ParameterResource> parameter => builder.WithTaskHubName(parameter),
_ => throw new ArgumentException($"Unexpected task hub name type: {taskHubName.GetType().Name}", nameof(taskHubName))
};

/// <summary>
/// Modifies the host port that the Durable Task Scheduler emulator listens on for gRPC requests.
/// </summary>
/// <param name="builder">The emulator resource builder.</param>
/// <param name="port">Host port to use.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the emulator.</returns>
/// <example>
/// Set the gRPC host port:
/// <code>
/// var builder = DistributedApplication.CreateBuilder(args);
/// var scheduler = builder.AddDurableTaskScheduler("scheduler")
/// .RunAsEmulator(c => c.WithGrpcPort(18080));
/// </code>
/// </example>
[AspireExport(Description = "Sets the host port for gRPC requests on the Durable Task Scheduler emulator")]
public static IResourceBuilder<DurableTaskSchedulerEmulatorResource> WithGrpcPort(this IResourceBuilder<DurableTaskSchedulerEmulatorResource> builder, int port)
{
ArgumentNullException.ThrowIfNull(builder);

return builder.WithEndpoint("grpc", endpoint =>
{
endpoint.Port = port;
});
}

/// <summary>
/// Modifies the host port that the Durable Task Scheduler emulator listens on for HTTP requests.
/// </summary>
/// <param name="builder">The emulator resource builder.</param>
/// <param name="port">Host port to use.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the emulator.</returns>
/// <example>
/// Set the HTTP host port:
/// <code>
/// var builder = DistributedApplication.CreateBuilder(args);
/// var scheduler = builder.AddDurableTaskScheduler("scheduler")
/// .RunAsEmulator(c => c.WithHttpPort(18081));
/// </code>
/// </example>
[AspireExport(Description = "Sets the host port for HTTP requests on the Durable Task Scheduler emulator")]
public static IResourceBuilder<DurableTaskSchedulerEmulatorResource> WithHttpPort(this IResourceBuilder<DurableTaskSchedulerEmulatorResource> builder, int port)
{
ArgumentNullException.ThrowIfNull(builder);

return builder.WithEndpoint("http", endpoint =>
{
endpoint.Port = port;
});
}

/// <summary>
/// Modifies the host port that the Durable Task Scheduler emulator listens on for dashboard requests.
/// </summary>
/// <param name="builder">The emulator resource builder.</param>
/// <param name="port">Host port to use.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/> for the emulator.</returns>
/// <example>
/// Set the dashboard host port:
/// <code>
/// var builder = DistributedApplication.CreateBuilder(args);
/// var scheduler = builder.AddDurableTaskScheduler("scheduler")
/// .RunAsEmulator(c => c.WithDashboardPort(18082));
/// </code>
/// </example>
[AspireExport(Description = "Sets the host port for dashboard requests on the Durable Task Scheduler emulator")]
public static IResourceBuilder<DurableTaskSchedulerEmulatorResource> WithDashboardPort(this IResourceBuilder<DurableTaskSchedulerEmulatorResource> builder, int port)
{
ArgumentNullException.ThrowIfNull(builder);

return builder.WithEndpoint("dashboard", endpoint =>
{
endpoint.Port = port;
});
}
}
9 changes: 9 additions & 0 deletions src/Aspire.Hosting.Azure.Functions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ When a Scheduler runs as an emulator, Aspire automatically provides:
- A "Task Hub Dashboard" URL for each Task Hub resource.
- A `DTS_TASK_HUB_NAMES` environment variable on the emulator container listing the Task Hub names associated with that scheduler.

> **Note:** The DTS emulator stores all orchestration and entity state in memory. State is lost when the
> container is stopped. The emulator does not support volume-based persistence. To preserve in-memory state
> across application restarts, configure the container with a persistent lifetime:
>
> ```csharp
> var scheduler = builder.AddDurableTaskScheduler("scheduler")
> .RunAsEmulator(c => c.WithLifetime(ContainerLifetime.Persistent));
> ```

### Use an existing Scheduler

If you already have a Scheduler instance, configure the resource using its connection string:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(AllTargetFrameworks)</TargetFrameworks>
<IsPackable>true</IsPackable>
<PackageTags>$(ComponentCommonPackageTags) durabletask durable-task orchestration workflow</PackageTags>
<Description>A Durable Task Scheduler client that integrates with Aspire, including health checks, logging, and telemetry.</Description>
<!-- New package, no baseline to validate against yet. -->
<EnablePackageValidation>false</EnablePackageValidation>
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="$(AssemblyName).Tests" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\Common\ConfigurationSchemaAttributes.cs" Link="ConfigurationSchemaAttributes.cs" />
<Compile Include="..\Common\HealthChecksExtensions.cs" Link="HealthChecksExtensions.cs" />
<Compile Include="..\Common\ConnectionStringValidation.cs" Link="ConnectionStringValidation.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.DurableTask.Worker.AzureManaged" />
<PackageReference Include="Microsoft.DurableTask.Client.AzureManaged" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
</ItemGroup>

</Project>
Loading
Loading