File: Browser\BrowserRefreshServerFactory.cs
Web Access
Project: ..\..\..\src\BuiltInTools\dotnet-watch\dotnet-watch.csproj (dotnet-watch)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics.CodeAnalysis;
using Microsoft.Build.Graph;
using Microsoft.DotNet.HotReload;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.DotNet.Watch;
 
/// <summary>
/// Creates <see cref="BrowserRefreshServer"/> instances.
///
/// An instance is created for each project that supports browser launching.
/// When the project is rebuilt and restarted we reuse the same refresh server and browser instance.
/// Reload message is sent to the browser in that case.
///
/// The instances are also reused if the project file is updated or the project graph is reloaded.
/// </summary>
internal sealed class BrowserRefreshServerFactory : IDisposable
{
    private readonly Lock _serversGuard = new();
 
    // Null value is cached for project instances that are not web projects or do not support browser refresh for other reason.
    private readonly Dictionary<ProjectInstanceId, BrowserRefreshServer?> _servers = [];
 
    public void Dispose()
    {
        BrowserRefreshServer?[] serversToDispose;
 
        lock (_serversGuard)
        {
            serversToDispose = [.. _servers.Values];
            _servers.Clear();
        }
 
        foreach (var server in serversToDispose)
        {
            server?.Dispose();
        };
    }
 
    public async ValueTask<BrowserRefreshServer?> GetOrCreateBrowserRefreshServerAsync(ProjectGraphNode projectNode, WebApplicationAppModel appModel, CancellationToken cancellationToken)
    {
        BrowserRefreshServer? server;
        bool hasExistingServer;
 
        var key = projectNode.GetProjectInstanceId();
 
        lock (_serversGuard)
        {
            hasExistingServer = _servers.TryGetValue(key, out server);
            if (!hasExistingServer)
            {
                server = appModel.TryCreateRefreshServer(projectNode);
                _servers.Add(key, server);
            }
        }
 
        if (server == null)
        {
            // browser refresh server isn't supported
            return null;
        }
 
        if (!hasExistingServer)
        {
            // Start the server we just created:
            await server.StartAsync(cancellationToken);
        }
 
        return server;
    }
 
    public bool TryGetRefreshServer(ProjectGraphNode projectNode, [NotNullWhen(true)] out BrowserRefreshServer? server)
    {
        var key = projectNode.GetProjectInstanceId();
 
        lock (_serversGuard)
        {
            return _servers.TryGetValue(key, out server) && server != null;
        }
    }
}