Add instance registration service for multi-client WebAPI integration
- Create RegistrationService to register VS instances with WebAPI - Registers on startup with: port, process ID, solution info - Reads WebApiUrl from ConfigurationService - Sends heartbeat every N seconds (configurable) - Unregisters on shutdown - Gracefully handles registration failures (VS extension works standalone) - Update RoslynBridgePackage to use RegistrationService - Creates BridgeServer and gets actual port used - Registers with WebAPI using discovered port - Cleans up registration on disposal This enables the WebAPI middleware to discover and route requests to the correct Visual Studio instance based on solution path, port, or PID. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ namespace RoslynBridge
|
||||
{
|
||||
public const string PackageGuidString = "b2c3d4e5-f6a7-4b5c-9d8e-0f1a2b3c4d5e";
|
||||
private BridgeServer? _bridgeServer;
|
||||
private Services.RegistrationService? _registrationService;
|
||||
|
||||
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
|
||||
{
|
||||
@@ -29,6 +30,10 @@ namespace RoslynBridge
|
||||
_bridgeServer = new BridgeServer(this);
|
||||
await _bridgeServer.StartAsync();
|
||||
|
||||
// Register with WebAPI
|
||||
_registrationService = new Services.RegistrationService(this, _bridgeServer.Port);
|
||||
await _registrationService.RegisterAsync();
|
||||
|
||||
await base.InitializeAsync(cancellationToken, progress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -43,6 +48,8 @@ namespace RoslynBridge
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_registrationService?.UnregisterAsync().Wait(TimeSpan.FromSeconds(5));
|
||||
_registrationService?.Dispose();
|
||||
_bridgeServer?.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
|
||||
138
RoslynBridge/Services/RegistrationService.cs
Normal file
138
RoslynBridge/Services/RegistrationService.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Shell;
|
||||
|
||||
namespace RoslynBridge.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for registering this VS instance with the WebAPI
|
||||
/// </summary>
|
||||
public class RegistrationService : IDisposable
|
||||
{
|
||||
private readonly string _webApiUrl;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly int _port;
|
||||
private readonly AsyncPackage _package;
|
||||
private System.Threading.Timer? _heartbeatTimer;
|
||||
private bool _isRegistered;
|
||||
|
||||
public RegistrationService(AsyncPackage package, int port)
|
||||
{
|
||||
_package = package;
|
||||
_port = port;
|
||||
_webApiUrl = ConfigurationService.Instance.WebApiUrl;
|
||||
_httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
|
||||
}
|
||||
|
||||
public async Task RegisterAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var dte = await GetDTEAsync();
|
||||
var solutionPath = dte?.Solution?.FullName;
|
||||
var solutionName = string.IsNullOrEmpty(solutionPath)
|
||||
? null
|
||||
: System.IO.Path.GetFileNameWithoutExtension(solutionPath);
|
||||
|
||||
var registrationData = new
|
||||
{
|
||||
port = _port,
|
||||
processId = Process.GetCurrentProcess().Id,
|
||||
solutionPath = string.IsNullOrEmpty(solutionPath) ? null : solutionPath,
|
||||
solutionName = solutionName,
|
||||
projects = new string[] { } // TODO: Get project names
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(registrationData);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
var response = await _httpClient.PostAsync($"{_webApiUrl}/api/instances/register", content);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
_isRegistered = true;
|
||||
Debug.WriteLine($"Successfully registered with WebAPI at {_webApiUrl}");
|
||||
|
||||
// Start heartbeat timer (configurable interval)
|
||||
var heartbeatInterval = TimeSpan.FromSeconds(ConfigurationService.Instance.HeartbeatIntervalSeconds);
|
||||
_heartbeatTimer = new System.Threading.Timer(
|
||||
async _ => await SendHeartbeatAsync(),
|
||||
null,
|
||||
heartbeatInterval,
|
||||
heartbeatInterval);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"Failed to register with WebAPI: {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error registering with WebAPI: {ex.Message}");
|
||||
// Don't throw - registration is optional, VS extension should work standalone
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendHeartbeatAsync()
|
||||
{
|
||||
if (!_isRegistered)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var processId = Process.GetCurrentProcess().Id;
|
||||
var response = await _httpClient.PostAsync(
|
||||
$"{_webApiUrl}/api/instances/heartbeat/{processId}",
|
||||
null);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Debug.WriteLine($"Heartbeat failed: {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error sending heartbeat: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UnregisterAsync()
|
||||
{
|
||||
if (!_isRegistered)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var processId = Process.GetCurrentProcess().Id;
|
||||
var response = await _httpClient.PostAsync(
|
||||
$"{_webApiUrl}/api/instances/unregister/{processId}",
|
||||
null);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
Debug.WriteLine("Successfully unregistered from WebAPI");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Error unregistering from WebAPI: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<EnvDTE.DTE?> GetDTEAsync()
|
||||
{
|
||||
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
|
||||
return await _package.GetServiceAsync(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_heartbeatTimer?.Dispose();
|
||||
_httpClient?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user