Skip to content

Initial integration of Durable Task Scheduler (i.e. emulator) #9294

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 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
63 changes: 63 additions & 0 deletions Aspire.sln
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApplication2", "playgrou
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenerateTestSummary", "tools\GenerateTestSummary\GenerateTestSummary.csproj", "{29950A00-A83A-48D3-8739-EE3D667B5229}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DurableTask", "DurableTask", "{4AC6FF77-B104-42B1-92F2-6D8E11B5CE22}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableTask.Scheduler.Worker", "playground\DurableTask\DurableTask.Scheduler.Worker\DurableTask.Scheduler.Worker.csproj", "{C13DFE4C-4A06-4A50-9B63-7296E214E883}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableTask.Scheduler.WebApi", "playground\DurableTask\DurableTask.Scheduler.WebApi\DurableTask.Scheduler.WebApi.csproj", "{40CF7365-191A-4E04-A0E1-6B6C10DF99F5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableTask.Scheduler.ExternalAppHost", "playground\DurableTask\DurableTask.Scheduler.ExternalAppHost\DurableTask.Scheduler.ExternalAppHost.csproj", "{37369502-9828-455D-82C7-57BEA24BF46F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DurableTask.Scheduler.AppHost", "playground\DurableTask\DurableTask.Scheduler.AppHost\DurableTask.Scheduler.AppHost.csproj", "{AD9E9337-1132-47A0-AF78-7E2CC812AE75}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -4060,6 +4070,54 @@ Global
{29950A00-A83A-48D3-8739-EE3D667B5229}.Release|x64.Build.0 = Release|Any CPU
{29950A00-A83A-48D3-8739-EE3D667B5229}.Release|x86.ActiveCfg = Release|Any CPU
{29950A00-A83A-48D3-8739-EE3D667B5229}.Release|x86.Build.0 = Release|Any CPU
{C13DFE4C-4A06-4A50-9B63-7296E214E883}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C13DFE4C-4A06-4A50-9B63-7296E214E883}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C13DFE4C-4A06-4A50-9B63-7296E214E883}.Debug|x64.ActiveCfg = Debug|Any CPU
{C13DFE4C-4A06-4A50-9B63-7296E214E883}.Debug|x64.Build.0 = Debug|Any CPU
{C13DFE4C-4A06-4A50-9B63-7296E214E883}.Debug|x86.ActiveCfg = Debug|Any CPU
{C13DFE4C-4A06-4A50-9B63-7296E214E883}.Debug|x86.Build.0 = Debug|Any CPU
{C13DFE4C-4A06-4A50-9B63-7296E214E883}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C13DFE4C-4A06-4A50-9B63-7296E214E883}.Release|Any CPU.Build.0 = Release|Any CPU
{C13DFE4C-4A06-4A50-9B63-7296E214E883}.Release|x64.ActiveCfg = Release|Any CPU
{C13DFE4C-4A06-4A50-9B63-7296E214E883}.Release|x64.Build.0 = Release|Any CPU
{C13DFE4C-4A06-4A50-9B63-7296E214E883}.Release|x86.ActiveCfg = Release|Any CPU
{C13DFE4C-4A06-4A50-9B63-7296E214E883}.Release|x86.Build.0 = Release|Any CPU
{40CF7365-191A-4E04-A0E1-6B6C10DF99F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{40CF7365-191A-4E04-A0E1-6B6C10DF99F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{40CF7365-191A-4E04-A0E1-6B6C10DF99F5}.Debug|x64.ActiveCfg = Debug|Any CPU
{40CF7365-191A-4E04-A0E1-6B6C10DF99F5}.Debug|x64.Build.0 = Debug|Any CPU
{40CF7365-191A-4E04-A0E1-6B6C10DF99F5}.Debug|x86.ActiveCfg = Debug|Any CPU
{40CF7365-191A-4E04-A0E1-6B6C10DF99F5}.Debug|x86.Build.0 = Debug|Any CPU
{40CF7365-191A-4E04-A0E1-6B6C10DF99F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{40CF7365-191A-4E04-A0E1-6B6C10DF99F5}.Release|Any CPU.Build.0 = Release|Any CPU
{40CF7365-191A-4E04-A0E1-6B6C10DF99F5}.Release|x64.ActiveCfg = Release|Any CPU
{40CF7365-191A-4E04-A0E1-6B6C10DF99F5}.Release|x64.Build.0 = Release|Any CPU
{40CF7365-191A-4E04-A0E1-6B6C10DF99F5}.Release|x86.ActiveCfg = Release|Any CPU
{40CF7365-191A-4E04-A0E1-6B6C10DF99F5}.Release|x86.Build.0 = Release|Any CPU
{37369502-9828-455D-82C7-57BEA24BF46F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{37369502-9828-455D-82C7-57BEA24BF46F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{37369502-9828-455D-82C7-57BEA24BF46F}.Debug|x64.ActiveCfg = Debug|Any CPU
{37369502-9828-455D-82C7-57BEA24BF46F}.Debug|x64.Build.0 = Debug|Any CPU
{37369502-9828-455D-82C7-57BEA24BF46F}.Debug|x86.ActiveCfg = Debug|Any CPU
{37369502-9828-455D-82C7-57BEA24BF46F}.Debug|x86.Build.0 = Debug|Any CPU
{37369502-9828-455D-82C7-57BEA24BF46F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{37369502-9828-455D-82C7-57BEA24BF46F}.Release|Any CPU.Build.0 = Release|Any CPU
{37369502-9828-455D-82C7-57BEA24BF46F}.Release|x64.ActiveCfg = Release|Any CPU
{37369502-9828-455D-82C7-57BEA24BF46F}.Release|x64.Build.0 = Release|Any CPU
{37369502-9828-455D-82C7-57BEA24BF46F}.Release|x86.ActiveCfg = Release|Any CPU
{37369502-9828-455D-82C7-57BEA24BF46F}.Release|x86.Build.0 = Release|Any CPU
{AD9E9337-1132-47A0-AF78-7E2CC812AE75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AD9E9337-1132-47A0-AF78-7E2CC812AE75}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD9E9337-1132-47A0-AF78-7E2CC812AE75}.Debug|x64.ActiveCfg = Debug|Any CPU
{AD9E9337-1132-47A0-AF78-7E2CC812AE75}.Debug|x64.Build.0 = Debug|Any CPU
{AD9E9337-1132-47A0-AF78-7E2CC812AE75}.Debug|x86.ActiveCfg = Debug|Any CPU
{AD9E9337-1132-47A0-AF78-7E2CC812AE75}.Debug|x86.Build.0 = Debug|Any CPU
{AD9E9337-1132-47A0-AF78-7E2CC812AE75}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD9E9337-1132-47A0-AF78-7E2CC812AE75}.Release|Any CPU.Build.0 = Release|Any CPU
{AD9E9337-1132-47A0-AF78-7E2CC812AE75}.Release|x64.ActiveCfg = Release|Any CPU
{AD9E9337-1132-47A0-AF78-7E2CC812AE75}.Release|x64.Build.0 = Release|Any CPU
{AD9E9337-1132-47A0-AF78-7E2CC812AE75}.Release|x86.ActiveCfg = Release|Any CPU
{AD9E9337-1132-47A0-AF78-7E2CC812AE75}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -4392,6 +4450,11 @@ Global
{E79A95EA-08D9-9947-377D-6F2213B36E1B} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{554D72B3-F0B0-FB9A-67ED-BBDF55A6DE81} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{29950A00-A83A-48D3-8739-EE3D667B5229} = {2136E31D-2CBB-41BB-8618-716FF8E46E9E}
{4AC6FF77-B104-42B1-92F2-6D8E11B5CE22} = {D173887B-AF42-4576-B9C1-96B9E9B3D9C0}
{C13DFE4C-4A06-4A50-9B63-7296E214E883} = {4AC6FF77-B104-42B1-92F2-6D8E11B5CE22}
{40CF7365-191A-4E04-A0E1-6B6C10DF99F5} = {4AC6FF77-B104-42B1-92F2-6D8E11B5CE22}
{37369502-9828-455D-82C7-57BEA24BF46F} = {4AC6FF77-B104-42B1-92F2-6D8E11B5CE22}
{AD9E9337-1132-47A0-AF78-7E2CC812AE75} = {4AC6FF77-B104-42B1-92F2-6D8E11B5CE22}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {47DCFECF-5631-4BDE-A1EC-BE41E90F60C4}
Expand Down
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@
<PackageVersion Include="KubernetesClient" Version="16.0.7" />
<PackageVersion Include="JsonPatch.Net" Version="3.3.0" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.0.2" />
<PackageVersion Include="Microsoft.DurableTask.Client.AzureManaged" Version="1.10.0-preview.1" />
<PackageVersion Include="Microsoft.DurableTask.Worker.AzureManaged" Version="1.10.0-preview.1" />
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.11.6" />
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.11.6" />
<PackageVersion Include="Milvus.Client" Version="2.3.0-preview.1" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<ItemGroup>
<Compile Include="..\..\KnownResourceNames.cs" Link="KnownResourceNames.cs" />
</ItemGroup>

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

<ItemGroup>
<ProjectReference Include="..\DurableTask.Scheduler.WebApi\DurableTask.Scheduler.WebApi.csproj" />
<ProjectReference Include="..\DurableTask.Scheduler.Worker\DurableTask.Scheduler.Worker.csproj" />
</ItemGroup>

</Project>
23 changes: 23 additions & 0 deletions playground/DurableTask/DurableTask.Scheduler.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Aspire.Hosting.Azure;

var builder = DistributedApplication.CreateBuilder(args);

var scheduler =
builder.AddDurableTaskScheduler("scheduler")
.RunAsEmulator(
options =>
{
options.WithDynamicTaskHubs();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this explicit call needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a demonstration of configuring options on the emulator. (Not every Aspire application will want to use dynamic task hub names.)

});

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

var webApi =
builder.AddProject<Projects.DurableTask_Scheduler_WebApi>("webapi")
.WithReference(taskHub);

builder.AddProject<Projects.DurableTask_Scheduler_Worker>("worker")
.WithReference(webApi)
.WithReference(taskHub);

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17222;http://localhost:15079",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21093",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22284"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15079",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19250",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20227"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<ItemGroup>
<Compile Include="..\..\KnownResourceNames.cs" Link="KnownResourceNames.cs" />
</ItemGroup>

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

<ItemGroup>
<ProjectReference Include="..\DurableTask.Scheduler.WebApi\DurableTask.Scheduler.WebApi.csproj" />
<ProjectReference Include="..\DurableTask.Scheduler.Worker\DurableTask.Scheduler.Worker.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Aspire.Hosting.Azure;

var builder = DistributedApplication.CreateBuilder(args);

var scheduler =
builder.AddDurableTaskScheduler("scheduler")
.RunAsExisting(builder.AddParameter("scheduler-connection-string"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this in the sample?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intent was to have examples that demonstrate the two main DTS scenarios: use of the DTS emulator and use of an existing DTS instance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I'm having a hard time understanding is the deployment story here. Is this an azure resource as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, DTS is an Azure resource (though I'm not intending to support deployment in this initial pass). I did a little experimenting with the Bicep base resource type that other Azure resources are built upon, but they rely on Azure provisioning libraries that do not exist, yet, for DTS.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this model is going to be:
AddAzureDurableTaskScheudler().RunAsEmulator() yes?


var taskHub =
scheduler.AddTaskHub("taskhub")
.WithTaskHubName(builder.AddParameter("taskhub-name"));

var webApi =
builder.AddProject<Projects.DurableTask_Scheduler_WebApi>("webapi")
.WithReference(taskHub);

builder.AddProject<Projects.DurableTask_Scheduler_Worker>("worker")
.WithReference(webApi)
.WithReference(taskHub);

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17222;http://localhost:15079",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21093",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22284"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15079",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19250",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20227"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
},
"Parameters": {
"scheduler-connection-string": "<connection string>",
"taskhub-name": "<name>"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

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

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

<ItemGroup>
<PackageReference Include="Microsoft.DurableTask.Client.AzureManaged" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@HostAddress = http://localhost:5142

POST {{HostAddress}}/create
Content-Type: application/json

{
"text": "hello world"
}

###

47 changes: 47 additions & 0 deletions playground/DurableTask/DurableTask.Scheduler.WebApi/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.AzureManaged;
using System.Text.Json.Serialization;

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

builder.Services.AddDurableTaskClient(
clientBuilder =>
{
clientBuilder.UseDurableTaskScheduler(
builder.Configuration.GetConnectionString("taskhub") ?? throw new InvalidOperationException("Scheduler connection string not configured."),
options =>
{
options.AllowInsecureCredentials = true;
});
Comment on lines +13 to +18
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we build a client integration?

Copy link
Contributor Author

@philliphoff philliphoff May 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...yes? It's complicated because consuming applications can be either DTS "clients" or "workers" (and sometimes both), and there are separate SDKs for each which means multiple client integrations. Also, there are two sets of SDKs that apps can use (and you might consider another scenario a third) which further expands the matrix.

I'd say we should start with client integrations for the "modern" SDK, worker first and then client, as the former is the most common. Then, if there's demand, look at integrations for the "older" SDKs. That said, any client integrations would be follow up PRs.

});

var app = builder.Build();

app.MapPost("/create", async ([FromBody] EchoValue value, [FromServices] DurableTaskClient durableTaskClient) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It magically knows!

Suggested change
app.MapPost("/create", async ([FromBody] EchoValue value, [FromServices] DurableTaskClient durableTaskClient) =>
app.MapPost("/create", async (EchoValue value, DurableTaskClient durableTaskClient) =>

{
string instanceId = await durableTaskClient.ScheduleNewOrchestrationInstanceAsync(
"Echo",
value);

await durableTaskClient.WaitForInstanceCompletionAsync(instanceId);

return Results.Ok();
})
.WithName("CreateOrchestration");

app.MapPost("/echo", ([FromBody] EchoValue value) =>
{
return new EchoValue { Text = $"Echoed: {value.Text}" };
})
.WithName("EchoText");

app.Run();

public record EchoValue
{
[JsonPropertyName("text")]
public required string Text { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5142",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">

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

<ItemGroup>
<PackageReference Include="Microsoft.DurableTask.Worker.AzureManaged" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>

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