File: TestResource.cs
Web Access
Project: src\playground\Stress\Stress.AppHost\Stress.AppHost.csproj (Stress.AppHost)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Globalization;
using Aspire.Dashboard.Model;
using Aspire.Hosting.Lifecycle;
using Microsoft.Extensions.Logging;
 
static class TestResourceExtensions
{
    public static IResourceBuilder<TestResource> AddTestResource(this IDistributedApplicationBuilder builder, string name)
    {
        builder.Services.TryAddLifecycleHook<TestResourceLifecycleHook>();
 
        var rb = builder.AddResource(new TestResource(name))
                      .WithInitialState(new()
                      {
                          ResourceType = "Test Resource",
                          State = "Starting",
                          Properties = [
                              new("P1", "P2"),
                              new(CustomResourceKnownProperties.Source, "Custom")
                          ]
                      })
                      .ExcludeFromManifest();
 
        return rb;
    }
 
    public static IResourceBuilder<TestNestedResource> AddNestedResource(this IDistributedApplicationBuilder builder, string name, IResource parent)
    {
        var rb = builder.AddResource(new TestNestedResource(name, parent))
                      .WithInitialState(new()
                      {
                          ResourceType = "Test Nested Resource",
                          State = "Starting",
                          Properties = [
                              new("P1", "P2"),
                              new(CustomResourceKnownProperties.Source, "Custom"),
                              new(KnownProperties.Resource.ParentName, parent.Name)
                          ]
                      })
                      .ExcludeFromManifest();
 
        return rb;
    }
}
 
internal sealed class TestResourceLifecycleHook(ResourceNotificationService notificationService, ResourceLoggerService loggerService) : IDistributedApplicationLifecycleHook, IAsyncDisposable
{
    private readonly CancellationTokenSource _tokenSource = new();
 
    public Task BeforeStartAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
    {
        foreach (var resource in appModel.Resources.OfType<TestResource>())
        {
            var states = new[] { "Starting", "Running", "Finished" };
 
            var logger = loggerService.GetLogger(resource);
 
            Task.Run(async () =>
            {
                var seconds = Random.Shared.Next(2, 12);
 
                logger.LogInformation("Starting test resource {ResourceName} with update interval {Interval} seconds", resource.Name, seconds);
 
                await notificationService.PublishUpdateAsync(resource, state => state with
                {
                    Properties = [.. state.Properties, new("Interval", seconds.ToString(CultureInfo.InvariantCulture))]
                });
 
                using var timer = new PeriodicTimer(TimeSpan.FromSeconds(seconds));
 
                while (await timer.WaitForNextTickAsync(_tokenSource.Token))
                {
                    var randomState = states[Random.Shared.Next(0, states.Length)];
 
                    await notificationService.PublishUpdateAsync(resource, state => state with
                    {
                        State = randomState
                    });
 
                    logger.LogInformation("Test resource {ResourceName} is now in state {State}", resource.Name, randomState);
                }
            },
            cancellationToken);
        }
 
        return Task.CompletedTask;
    }
 
    public ValueTask DisposeAsync()
    {
        _tokenSource.Cancel();
        return default;
    }
}
 
sealed class TestResource(string name) : Resource(name)
{
 
}
 
sealed class TestNestedResource(string name, IResource parent) : Resource(name), IResourceWithParent
{
    public IResource Parent { get; } = parent;
}