File: RemoteHostServer.cs
Web Access
Project: src\src\Aspire.Hosting.RemoteHost\Aspire.Hosting.RemoteHost.csproj (Aspire.Hosting.RemoteHost)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Reflection;
using Aspire.Hosting.RemoteHost.Ats;
using Aspire.Hosting.RemoteHost.CodeGeneration;
using Aspire.Hosting.RemoteHost.Diagnostics;
using Aspire.Hosting.RemoteHost.Language;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
 
namespace Aspire.Hosting.RemoteHost;
 
/// <summary>
/// Entry point for running the RemoteHost server.
/// </summary>
public static class RemoteHostServer
{
    /// <summary>
    /// Runs the RemoteHost JSON-RPC server, loading ATS assemblies from configuration and available integration assemblies.
    /// </summary>
    /// <remarks>
    /// The server reads the "AtsAssemblies" section from appsettings.json to determine which
    /// assemblies to scan for <c>Aspire.Hosting.AspireExportAttribute</c> capabilities, and it also
    /// probes available <c>Aspire.Hosting*</c> assemblies from the application output and
    /// integration libraries. The appsettings.json should be in the current working directory.
    /// </remarks>
    /// <param name="args">Command line arguments.</param>
    /// <returns>A task that completes when the server has stopped.</returns>
    public static async Task RunAsync(string[] args)
    {
        var builder = CreateBuilder(args);
        using var host = builder.Build();
        var profilingTelemetry = host.Services.GetRequiredService<RemoteHostProfilingTelemetry>();
 
        using var activity = profilingTelemetry.StartRemoteHostRun();
        try
        {
            await host.RunAsync().ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            activity.SetError(ex);
            throw;
        }
    }
 
    internal static HostApplicationBuilder CreateBuilder(string[] args)
    {
        var builder = Host.CreateApplicationBuilder(args);
        ConfigureAppHostLogLevel(builder.Logging, builder.Configuration);
        ConfigureServices(builder.Services);
        ConfigureProfilingTelemetry(builder);
 
        return builder;
    }
 
    private static void ConfigureAppHostLogLevel(ILoggingBuilder logging, IConfiguration configuration)
    {
        var appHostLogLevelValue = configuration[KnownConfigNames.AppHostLogLevel];
        if (appHostLogLevelValue is not null && Enum.TryParse<LogLevel>(appHostLogLevelValue, ignoreCase: true, out var appHostLogLevel))
        {
            // Add a default filter rule after configuration binding so this setting has the
            // same precedence as Logging__LogLevel__Default without being inherited by child processes.
            logging.AddFilter((_, logLevel) => logLevel >= appHostLogLevel);
        }
    }
 
    private static void ConfigureServices(IServiceCollection services)
    {
        // Hosted services
        services.AddHostedService<OrphanDetector>();
        services.AddHostedService<JsonRpcServer>();
 
        // Singletons
        services.AddSingleton<RemoteHostProfilingTelemetry>();
        services.AddSingleton<AssemblyLoader>();
        services.AddSingleton<AtsContextFactory>();
        services.AddSingleton(sp => sp.GetRequiredService<AtsContextFactory>().GetContext());
        services.AddSingleton<CodeGeneratorResolver>();
        services.AddSingleton<LanguageSupportResolver>();
 
        // Scoped services
        services.AddScoped<CodeGenerationService>();
        services.AddScoped<LanguageService>();
        services.AddScoped<JsonRpcAuthenticationState>();
        services.AddScoped<HandleRegistry>();
        services.AddScoped<CancellationTokenRegistry>();
        services.AddScoped<JsonRpcCallbackInvoker>();
        services.AddScoped<ICallbackInvoker>(sp => sp.GetRequiredService<JsonRpcCallbackInvoker>());
        services.AddScoped<AtsCallbackProxyFactory>();
        // Register Lazy<T> for breaking circular dependency between AtsMarshaller and AtsCallbackProxyFactory
        services.AddScoped(sp => new Lazy<AtsCallbackProxyFactory>(() => sp.GetRequiredService<AtsCallbackProxyFactory>()));
        services.AddScoped<AtsMarshaller>();
        services.AddScoped<CapabilityDispatcher>();
        services.AddScoped<RemoteAppHostService>();
    }
 
    private static void ConfigureProfilingTelemetry(HostApplicationBuilder builder)
    {
        if (!RemoteHostProfilingTelemetry.ShouldConfigureExporter(builder.Configuration))
        {
            return;
        }
 
        var resourceBuilder = ResourceBuilder.CreateDefault()
            .AddService(
                serviceName: "aspire-remotehost",
                serviceVersion: GetRemoteHostServiceVersion());
 
        builder.Services.AddOpenTelemetry()
            .WithTracing(tracing =>
            {
                tracing
                    .AddSource(RemoteHostProfilingTelemetry.ActivitySourceName)
                    .SetResourceBuilder(resourceBuilder)
                    .AddOtlpExporter();
            });
    }
 
    private static string? GetRemoteHostServiceVersion()
    {
        return typeof(RemoteHostServer).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
    }
}