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
78 changes: 78 additions & 0 deletions TemporalioSamples.sln
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.NexusCanc
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NexusCancellation", "NexusCancellation", "{7123C63D-3158-4C9A-8EAD-6D4F1295BC04}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.SampleWorkflow", "src\AspireIntegrations\TemporalioSamples.SampleWorkflow\TemporalioSamples.SampleWorkflow.csproj", "{6BA6C609-6D33-425B-883F-88ECE2E3DDB9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AspireIntegrations", "AspireIntegrations", "{8781BE47-D710-408E-B143-4D5E20C356E2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.SampleWorker", "src\AspireIntegrations\TemporalioSamples.SampleWorker\TemporalioSamples.SampleWorker.csproj", "{FF13AD0E-4F24-4044-B8AD-5A57EF3AE398}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.SampleClient", "src\AspireIntegrations\TemporalioSamples.SampleClient\TemporalioSamples.SampleClient.csproj", "{035FF43C-D9C8-4CCE-A35A-E4ABF6F842C8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.SampleAppHost", "src\AspireIntegrations\TemporalioSamples.SampleAppHost\TemporalioSamples.SampleAppHost.csproj", "{CA136E75-FC34-44E1-B8B2-6E33D8AF520E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Temporal.Extensions.Aspire.Hosting", "src\AspireIntegrations\Temporal.Extensions.Aspire.Hosting\Temporal.Extensions.Aspire.Hosting.csproj", "{89D196AD-A6CE-42FB-BF46-C80BF579FE20}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -633,6 +645,66 @@ Global
{6D0BE4C4-9C4F-4A3D-78F1-B0B761568559}.Release|x64.Build.0 = Release|Any CPU
{6D0BE4C4-9C4F-4A3D-78F1-B0B761568559}.Release|x86.ActiveCfg = Release|Any CPU
{6D0BE4C4-9C4F-4A3D-78F1-B0B761568559}.Release|x86.Build.0 = Release|Any CPU
{6BA6C609-6D33-425B-883F-88ECE2E3DDB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6BA6C609-6D33-425B-883F-88ECE2E3DDB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6BA6C609-6D33-425B-883F-88ECE2E3DDB9}.Debug|x64.ActiveCfg = Debug|Any CPU
{6BA6C609-6D33-425B-883F-88ECE2E3DDB9}.Debug|x64.Build.0 = Debug|Any CPU
{6BA6C609-6D33-425B-883F-88ECE2E3DDB9}.Debug|x86.ActiveCfg = Debug|Any CPU
{6BA6C609-6D33-425B-883F-88ECE2E3DDB9}.Debug|x86.Build.0 = Debug|Any CPU
{6BA6C609-6D33-425B-883F-88ECE2E3DDB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6BA6C609-6D33-425B-883F-88ECE2E3DDB9}.Release|Any CPU.Build.0 = Release|Any CPU
{6BA6C609-6D33-425B-883F-88ECE2E3DDB9}.Release|x64.ActiveCfg = Release|Any CPU
{6BA6C609-6D33-425B-883F-88ECE2E3DDB9}.Release|x64.Build.0 = Release|Any CPU
{6BA6C609-6D33-425B-883F-88ECE2E3DDB9}.Release|x86.ActiveCfg = Release|Any CPU
{6BA6C609-6D33-425B-883F-88ECE2E3DDB9}.Release|x86.Build.0 = Release|Any CPU
{FF13AD0E-4F24-4044-B8AD-5A57EF3AE398}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FF13AD0E-4F24-4044-B8AD-5A57EF3AE398}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF13AD0E-4F24-4044-B8AD-5A57EF3AE398}.Debug|x64.ActiveCfg = Debug|Any CPU
{FF13AD0E-4F24-4044-B8AD-5A57EF3AE398}.Debug|x64.Build.0 = Debug|Any CPU
{FF13AD0E-4F24-4044-B8AD-5A57EF3AE398}.Debug|x86.ActiveCfg = Debug|Any CPU
{FF13AD0E-4F24-4044-B8AD-5A57EF3AE398}.Debug|x86.Build.0 = Debug|Any CPU
{FF13AD0E-4F24-4044-B8AD-5A57EF3AE398}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF13AD0E-4F24-4044-B8AD-5A57EF3AE398}.Release|Any CPU.Build.0 = Release|Any CPU
{FF13AD0E-4F24-4044-B8AD-5A57EF3AE398}.Release|x64.ActiveCfg = Release|Any CPU
{FF13AD0E-4F24-4044-B8AD-5A57EF3AE398}.Release|x64.Build.0 = Release|Any CPU
{FF13AD0E-4F24-4044-B8AD-5A57EF3AE398}.Release|x86.ActiveCfg = Release|Any CPU
{FF13AD0E-4F24-4044-B8AD-5A57EF3AE398}.Release|x86.Build.0 = Release|Any CPU
{035FF43C-D9C8-4CCE-A35A-E4ABF6F842C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{035FF43C-D9C8-4CCE-A35A-E4ABF6F842C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{035FF43C-D9C8-4CCE-A35A-E4ABF6F842C8}.Debug|x64.ActiveCfg = Debug|Any CPU
{035FF43C-D9C8-4CCE-A35A-E4ABF6F842C8}.Debug|x64.Build.0 = Debug|Any CPU
{035FF43C-D9C8-4CCE-A35A-E4ABF6F842C8}.Debug|x86.ActiveCfg = Debug|Any CPU
{035FF43C-D9C8-4CCE-A35A-E4ABF6F842C8}.Debug|x86.Build.0 = Debug|Any CPU
{035FF43C-D9C8-4CCE-A35A-E4ABF6F842C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{035FF43C-D9C8-4CCE-A35A-E4ABF6F842C8}.Release|Any CPU.Build.0 = Release|Any CPU
{035FF43C-D9C8-4CCE-A35A-E4ABF6F842C8}.Release|x64.ActiveCfg = Release|Any CPU
{035FF43C-D9C8-4CCE-A35A-E4ABF6F842C8}.Release|x64.Build.0 = Release|Any CPU
{035FF43C-D9C8-4CCE-A35A-E4ABF6F842C8}.Release|x86.ActiveCfg = Release|Any CPU
{035FF43C-D9C8-4CCE-A35A-E4ABF6F842C8}.Release|x86.Build.0 = Release|Any CPU
{CA136E75-FC34-44E1-B8B2-6E33D8AF520E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CA136E75-FC34-44E1-B8B2-6E33D8AF520E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CA136E75-FC34-44E1-B8B2-6E33D8AF520E}.Debug|x64.ActiveCfg = Debug|Any CPU
{CA136E75-FC34-44E1-B8B2-6E33D8AF520E}.Debug|x64.Build.0 = Debug|Any CPU
{CA136E75-FC34-44E1-B8B2-6E33D8AF520E}.Debug|x86.ActiveCfg = Debug|Any CPU
{CA136E75-FC34-44E1-B8B2-6E33D8AF520E}.Debug|x86.Build.0 = Debug|Any CPU
{CA136E75-FC34-44E1-B8B2-6E33D8AF520E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CA136E75-FC34-44E1-B8B2-6E33D8AF520E}.Release|Any CPU.Build.0 = Release|Any CPU
{CA136E75-FC34-44E1-B8B2-6E33D8AF520E}.Release|x64.ActiveCfg = Release|Any CPU
{CA136E75-FC34-44E1-B8B2-6E33D8AF520E}.Release|x64.Build.0 = Release|Any CPU
{CA136E75-FC34-44E1-B8B2-6E33D8AF520E}.Release|x86.ActiveCfg = Release|Any CPU
{CA136E75-FC34-44E1-B8B2-6E33D8AF520E}.Release|x86.Build.0 = Release|Any CPU
{89D196AD-A6CE-42FB-BF46-C80BF579FE20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{89D196AD-A6CE-42FB-BF46-C80BF579FE20}.Debug|Any CPU.Build.0 = Debug|Any CPU
{89D196AD-A6CE-42FB-BF46-C80BF579FE20}.Debug|x64.ActiveCfg = Debug|Any CPU
{89D196AD-A6CE-42FB-BF46-C80BF579FE20}.Debug|x64.Build.0 = Debug|Any CPU
{89D196AD-A6CE-42FB-BF46-C80BF579FE20}.Debug|x86.ActiveCfg = Debug|Any CPU
{89D196AD-A6CE-42FB-BF46-C80BF579FE20}.Debug|x86.Build.0 = Debug|Any CPU
{89D196AD-A6CE-42FB-BF46-C80BF579FE20}.Release|Any CPU.ActiveCfg = Release|Any CPU
{89D196AD-A6CE-42FB-BF46-C80BF579FE20}.Release|Any CPU.Build.0 = Release|Any CPU
{89D196AD-A6CE-42FB-BF46-C80BF579FE20}.Release|x64.ActiveCfg = Release|Any CPU
{89D196AD-A6CE-42FB-BF46-C80BF579FE20}.Release|x64.Build.0 = Release|Any CPU
{89D196AD-A6CE-42FB-BF46-C80BF579FE20}.Release|x86.ActiveCfg = Release|Any CPU
{89D196AD-A6CE-42FB-BF46-C80BF579FE20}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -686,5 +758,11 @@ Global
{AF077751-E4B9-4696-93CB-74653F0BB6C4} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC}
{6D0BE4C4-9C4F-4A3D-78F1-B0B761568559} = {7123C63D-3158-4C9A-8EAD-6D4F1295BC04}
{7123C63D-3158-4C9A-8EAD-6D4F1295BC04} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC}
{8781BE47-D710-408E-B143-4D5E20C356E2} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC}
{6BA6C609-6D33-425B-883F-88ECE2E3DDB9} = {8781BE47-D710-408E-B143-4D5E20C356E2}
{FF13AD0E-4F24-4044-B8AD-5A57EF3AE398} = {8781BE47-D710-408E-B143-4D5E20C356E2}
{035FF43C-D9C8-4CCE-A35A-E4ABF6F842C8} = {8781BE47-D710-408E-B143-4D5E20C356E2}
{CA136E75-FC34-44E1-B8B2-6E33D8AF520E} = {8781BE47-D710-408E-B143-4D5E20C356E2}
{89D196AD-A6CE-42FB-BF46-C80BF579FE20} = {8781BE47-D710-408E-B143-4D5E20C356E2}
EndGlobalSection
EndGlobal
3 changes: 3 additions & 0 deletions src/AspireIntegrations/.aspire/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"appHostPath": "../TemporalioSamples.SampleAppHost/TemporalioSamples.SampleAppHost.csproj"
}
235 changes: 235 additions & 0 deletions src/AspireIntegrations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
# Temporal Extensions for .NET Aspire

## Overview

This project provides custom Aspire resource definitions that enable developers to integrate Temporal workflow servers into their Aspire applications with minimal configuration. It supports three deployment models:

- **Local Testing** - Temporal server using `Temporalio.Testing.WorkflowEnvironment` for fast local development and testing
- **Container-based** - Docker container running the official Temporal server image for development and staging environments
- **CLI-based** - Temporal CLI server for environments where Docker isn't available

### Key Features

- ✅ **Service Discovery** - Automatic environment variable injection for dependent services
- ✅ **Health Checks** - Built-in health checks integrated into Aspire's health pipeline
- ✅ **Resource Management** - Start/Stop commands in the Aspire dashboard with proper state management

## Prerequisites

### Required
- **.NET 10.0** or later
- **Aspire 13.0** or later

### For Container-based Setup
- **Docker** - Required to run the Temporal container
- **Docker image** - `temporalio/temporal:latest` (automatically pulled)

### For CLI-based Setup
- **Temporal CLI** - Install via [Temporal CLI documentation](https://docs.temporal.io/cli/install)

## Running the Project

Using the Aspire CLI

1. **Navigate to the AppHost project directory:**
```bash
cd src/AspireIntegrations/
```

2. **Run the project using the Aspire CLI:**
```bash
aspire run
```

> You can also run the project directly with `dotnet run` from the AppHost directory, or use your IDE's run configuration.

## Setup Options

### Local Server

The local server setup uses a Temporal environment for fast testing without external dependencies. It will download and run the necessary Temporal server binaries.

**AppHost.cs:**
```csharp
using Temporal.Extensions.Aspire.Hosting;

var builder = DistributedApplication.CreateBuilder(args);

// Add Temporal local server
var temporal = builder.AddTemporalLocalTestServer();

// Add a worker project that depends on Temporal
builder.AddProject<Projects.SampleWorker>("worker")
.WaitFor(temporal)
.WithReference(temporal);

builder.Build().Run();
```

**Advanced Configuration:**
```csharp
var temporal = builder.AddTemporalLocalTestServer(configure: options =>
{
// Port configuration
options.UIPort = 8233; // Web UI port
options.MetricsPort = 9233; // Metrics endpoint port

// Network binding
options.TargetHost = "0.0.0.0:7233";

// Namespace configuration
options.Namespace = "default";
options.AdditionalNamespaces = ["orders", "analytics"];

// Use existing Temporal server binary
options.DevServerOptions.ExistingPath = "/usr/local/bin/temporal";

// UI configuration
options.UI = true;

// Search attributes for custom workflows
options.SearchAttributes = new[]
{
new SearchAttribute { Name = "Environment", ValueType = "Text" },
new SearchAttribute { Name = "UserId", ValueType = "Text" },
new SearchAttribute { Name = "ProcessingTime", ValueType = "Int" }
};

// Dynamic configuration values
options.DynamicConfigValues = [
"persistence.cassandra.hosts = cassandra-host:9042"
];
});
```

> Use `ExistingPath` to leverage a pre-installed Temporal binary

---

### Container-based

Deploy Temporal using the CLI Docker container.

**AppHost.cs:**
```csharp
using Temporal.Extensions.Aspire.Hosting;

var builder = DistributedApplication.CreateBuilder(args);

// Add Temporal as a Docker container
var temporal = builder.AddTemporalDevContainer(
configure: options =>
{
options.ImageTag = "latest"; // Use specific version if needed
options.UI = true; // Enable Web UI
options.Namespace = "default";
});

// Add dependent projects that require Temporal
builder.AddProject<Projects.SampleWorker>("worker")
.WaitFor(temporal)
.WithReference(temporal);

builder.Build().Run();
```

**Advanced Configuration:**
```csharp
var temporal = builder.AddTemporalDevContainer(configure: options =>
{
// Network configuration
options.TargetHost = "127.0.0.1:7233";

// Namespaces
options.AdditionalNamespaces = ["default", "custom-ns"];

// Logging
options.DevServerOptions.LogLevel = "info";
options.DevServerOptions.LogFormat = "json";

// Search attributes for custom workflows
options.SearchAttributes = new[]
{
new SearchAttribute { Name = "CustomField", ValueType = "Text" },
new SearchAttribute { Name = "CustomInt", ValueType = "Int" }
};
});
```
---

### CLI-based

Use the Temporal CLI server for environments without Docker.

**AppHost.cs:**
```csharp
using Temporal.Extensions.Aspire.Hosting;

var builder = DistributedApplication.CreateBuilder(args);

// Add Temporal CLI server
var temporal = builder.AddTemporalCliServer(
configure: options =>
{
options.Namespace = "default";
options.UI = true;
});

// Add dependent projects that require Temporal
builder.AddProject<Projects.SampleWorker>("worker")
.WaitFor(temporal)
.WithReference(temporal);

builder.Build().Run();
```

**Requirements:**
- Temporal CLI must be installed and available in PATH
- Run `temporal --version` to verify installation

---

### Connection Strings

Dependent projects can access Temporal connection information via environment variables:

```csharp
var temporalAddress = Environment.GetEnvironmentVariable("TEMPORAL_ADDRESS");
var temporalUiAddress = Environment.GetEnvironmentVariable("TEMPORAL_UI_ADDRESS");
```
---

## Configuration Options

### TemporalResourceOptions

The base configuration class for all resource types:

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `Namespace` | string | "default" | Primary namespace for workflows |
| `AdditionalNamespaces` | List<string> | ["default"] | Additional namespaces to register |
| `Port` | int | 7233 | gRPC service port |
| `UIPort` | int | 8233 | Web UI port |
| `MetricsPort` | int | 9233 | Metrics endpoint port |
| `UI` | bool | true | Enable Web UI |
| `TargetHost` | string | "0.0.0.0:7233" | Bind address (IP:port format) |
| `SearchAttributes` | List<SearchAttribute> | null | Custom search attributes |
| `DynamicConfigValues` | List<string> | [] | Dynamic configuration |
| `CodecEndpoint` | string | null | Codec server endpoint |
| `CodecAuth` | string | null | Codec authentication token |

### Container-specific Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `ImageTag` | string | "latest" | Docker image tag version |

---

## Resources

- [Temporal Documentation](https://docs.temporal.io/)
- [Temporal .NET SDK](https://github.com/temporalio/sdk-dotnet)
- [.NET Aspire](https://learn.microsoft.com/dotnet/aspire)
- [Aspire Integrations](https://learn.microsoft.com/dotnet/aspire/integrations)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);SA1010;SA1116;SA1413;SA1117;CA1031;SA1503;CS0109;CA1002;CA2227;CA1305</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.1" />
<PackageReference Update="Microsoft.Extensions.Logging" Version="10.0.3" />
<PackageReference Update="Microsoft.Extensions.Logging.Console" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.3" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Temporal.Extensions.Aspire.Hosting;

public class TemporalCliServerResource(string name, string workingDirectory = "./")
: ExecutableResource(name, "temporal", workingDirectory), IResourceWithConnectionString,
IResourceWithServiceDiscovery
{
private EndpointReference? primaryEndpoint;

public EndpointReference PrimaryEndpoint =>
primaryEndpoint ??= new(this, TemporalResourceConstants.ServiceEndpointName);

public ReferenceExpression ConnectionStringExpression =>
ReferenceExpression.Create(
$"{PrimaryEndpoint.Property(EndpointProperty.Url)}");

public TemporalResourceOptions Options { get; set; } = new();
}
Loading
Loading