Add WebAPI middleware for multi-instance Roslyn Bridge routing
Add RoslynBridge.WebApi - ASP.NET Core 8.0 middleware that: - Provides a centralized REST API for accessing multiple VS instances - Manages instance registry with discovery by port, solution, or PID - Proxies requests to the appropriate VS instance - Tracks request/response history for debugging - Auto-cleanup of stale instances via background service Features: - Health endpoints: /api/health, /api/health/ping - Roslyn endpoints: /api/roslyn/projects, /api/roslyn/diagnostics, etc. - Instance management: /api/instances (register, heartbeat, unregister) - History tracking: /api/history, /api/history/stats - Swagger UI at root (/) for API documentation - CORS enabled for web applications Services: - InstanceRegistryService: Thread-safe registry of VS instances - HistoryService: In-memory request/response history (max 1000 entries) - InstanceCleanupService: Background service to remove stale instances - RoslynBridgeClient: HTTP client for proxying to VS instances Update RoslynBridge.sln to include RoslynBridge.WebApi project. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
72
RoslynBridge.WebApi/Controllers/HealthController.cs
Normal file
72
RoslynBridge.WebApi/Controllers/HealthController.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RoslynBridge.WebApi.Models;
|
||||
using RoslynBridge.WebApi.Services;
|
||||
|
||||
namespace RoslynBridge.WebApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Health check and status controller
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class HealthController : ControllerBase
|
||||
{
|
||||
private readonly IRoslynBridgeClient _bridgeClient;
|
||||
private readonly ILogger<HealthController> _logger;
|
||||
|
||||
public HealthController(IRoslynBridgeClient bridgeClient, ILogger<HealthController> logger)
|
||||
{
|
||||
_bridgeClient = bridgeClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the health status of the middleware and Visual Studio plugin
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Health status information</returns>
|
||||
/// <response code="200">Service is healthy</response>
|
||||
/// <response code="503">Service is unhealthy</response>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(HealthCheckResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(HealthCheckResponse), StatusCodes.Status503ServiceUnavailable)]
|
||||
public async Task<ActionResult<HealthCheckResponse>> GetHealth(CancellationToken cancellationToken)
|
||||
{
|
||||
var response = new HealthCheckResponse();
|
||||
|
||||
try
|
||||
{
|
||||
var isVsPluginHealthy = await _bridgeClient.IsHealthyAsync(null, cancellationToken);
|
||||
response.VsPluginStatus = isVsPluginHealthy ? "Connected" : "Disconnected";
|
||||
|
||||
if (!isVsPluginHealthy)
|
||||
{
|
||||
response.Status = "Degraded";
|
||||
_logger.LogWarning("Visual Studio plugin is not accessible");
|
||||
return StatusCode(StatusCodes.Status503ServiceUnavailable, response);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Health check passed");
|
||||
return Ok(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Health check failed");
|
||||
response.Status = "Unhealthy";
|
||||
response.VsPluginStatus = "Error";
|
||||
return StatusCode(StatusCodes.Status503ServiceUnavailable, response);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple ping endpoint
|
||||
/// </summary>
|
||||
/// <returns>Pong response</returns>
|
||||
[HttpGet("ping")]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||||
public IActionResult Ping()
|
||||
{
|
||||
return Ok(new { message = "pong", timestamp = DateTime.UtcNow });
|
||||
}
|
||||
}
|
||||
117
RoslynBridge.WebApi/Controllers/HistoryController.cs
Normal file
117
RoslynBridge.WebApi/Controllers/HistoryController.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RoslynBridge.WebApi.Models;
|
||||
using RoslynBridge.WebApi.Services;
|
||||
|
||||
namespace RoslynBridge.WebApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Controller for accessing query history
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class HistoryController : ControllerBase
|
||||
{
|
||||
private readonly IHistoryService _historyService;
|
||||
private readonly ILogger<HistoryController> _logger;
|
||||
|
||||
public HistoryController(IHistoryService historyService, ILogger<HistoryController> logger)
|
||||
{
|
||||
_historyService = historyService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all history entries
|
||||
/// </summary>
|
||||
/// <returns>List of all history entries</returns>
|
||||
/// <response code="200">Returns the list of history entries</response>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<QueryHistoryEntry>), StatusCodes.Status200OK)]
|
||||
public ActionResult<IEnumerable<QueryHistoryEntry>> GetAll()
|
||||
{
|
||||
var entries = _historyService.GetAll();
|
||||
return Ok(entries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a specific history entry by ID
|
||||
/// </summary>
|
||||
/// <param name="id">The history entry ID</param>
|
||||
/// <returns>The history entry</returns>
|
||||
/// <response code="200">Returns the history entry</response>
|
||||
/// <response code="404">Entry not found</response>
|
||||
[HttpGet("{id}")]
|
||||
[ProducesResponseType(typeof(QueryHistoryEntry), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public ActionResult<QueryHistoryEntry> GetById(string id)
|
||||
{
|
||||
var entry = _historyService.GetById(id);
|
||||
if (entry == null)
|
||||
{
|
||||
return NotFound(new { message = $"History entry {id} not found" });
|
||||
}
|
||||
|
||||
return Ok(entry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get recent history entries
|
||||
/// </summary>
|
||||
/// <param name="count">Number of entries to return (default: 50, max: 500)</param>
|
||||
/// <returns>List of recent history entries</returns>
|
||||
/// <response code="200">Returns the list of recent entries</response>
|
||||
[HttpGet("recent")]
|
||||
[ProducesResponseType(typeof(IEnumerable<QueryHistoryEntry>), StatusCodes.Status200OK)]
|
||||
public ActionResult<IEnumerable<QueryHistoryEntry>> GetRecent([FromQuery] int count = 50)
|
||||
{
|
||||
if (count > 500) count = 500;
|
||||
if (count < 1) count = 1;
|
||||
|
||||
var entries = _historyService.GetRecent(count);
|
||||
return Ok(entries);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get history statistics
|
||||
/// </summary>
|
||||
/// <returns>Statistics about history entries</returns>
|
||||
[HttpGet("stats")]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||||
public ActionResult GetStats()
|
||||
{
|
||||
var entries = _historyService.GetAll();
|
||||
var stats = new
|
||||
{
|
||||
totalEntries = _historyService.GetCount(),
|
||||
successfulRequests = entries.Count(e => e.Success),
|
||||
failedRequests = entries.Count(e => !e.Success),
|
||||
averageDurationMs = entries.Any() ? entries.Average(e => e.DurationMs) : 0,
|
||||
oldestEntry = entries.Any() ? entries.Last().Timestamp : (DateTime?)null,
|
||||
newestEntry = entries.Any() ? entries.First().Timestamp : (DateTime?)null,
|
||||
topPaths = entries
|
||||
.GroupBy(e => e.Path)
|
||||
.OrderByDescending(g => g.Count())
|
||||
.Take(10)
|
||||
.Select(g => new { path = g.Key, count = g.Count() })
|
||||
};
|
||||
|
||||
return Ok(stats);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all history entries
|
||||
/// </summary>
|
||||
/// <returns>Confirmation message</returns>
|
||||
/// <response code="200">History cleared successfully</response>
|
||||
[HttpDelete]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||||
public ActionResult Clear()
|
||||
{
|
||||
var count = _historyService.GetCount();
|
||||
_historyService.Clear();
|
||||
_logger.LogInformation("History cleared: {Count} entries removed", count);
|
||||
|
||||
return Ok(new { message = $"History cleared: {count} entries removed" });
|
||||
}
|
||||
}
|
||||
168
RoslynBridge.WebApi/Controllers/InstancesController.cs
Normal file
168
RoslynBridge.WebApi/Controllers/InstancesController.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RoslynBridge.WebApi.Models;
|
||||
using RoslynBridge.WebApi.Services;
|
||||
|
||||
namespace RoslynBridge.WebApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Controller for managing Visual Studio instance registrations
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class InstancesController : ControllerBase
|
||||
{
|
||||
private readonly IInstanceRegistryService _registryService;
|
||||
private readonly ILogger<InstancesController> _logger;
|
||||
|
||||
public InstancesController(
|
||||
IInstanceRegistryService registryService,
|
||||
ILogger<InstancesController> logger)
|
||||
{
|
||||
_registryService = registryService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a new Visual Studio instance
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance information</param>
|
||||
/// <returns>Registration result</returns>
|
||||
[HttpPost("register")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public IActionResult Register([FromBody] VSInstanceInfo instance)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
_registryService.Register(instance);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
message = "Instance registered successfully",
|
||||
processId = instance.ProcessId,
|
||||
port = instance.Port
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister a Visual Studio instance
|
||||
/// </summary>
|
||||
/// <param name="processId">Process ID of the instance to unregister</param>
|
||||
/// <returns>Unregistration result</returns>
|
||||
[HttpPost("unregister/{processId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public IActionResult Unregister(int processId)
|
||||
{
|
||||
var removed = _registryService.Unregister(processId);
|
||||
|
||||
if (!removed)
|
||||
{
|
||||
return NotFound(new { success = false, message = "Instance not found" });
|
||||
}
|
||||
|
||||
return Ok(new { success = true, message = "Instance unregistered successfully" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update heartbeat for a Visual Studio instance
|
||||
/// </summary>
|
||||
/// <param name="processId">Process ID of the instance</param>
|
||||
/// <returns>Heartbeat update result</returns>
|
||||
[HttpPost("heartbeat/{processId}")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public IActionResult Heartbeat(int processId)
|
||||
{
|
||||
var updated = _registryService.UpdateHeartbeat(processId);
|
||||
|
||||
if (!updated)
|
||||
{
|
||||
return NotFound(new { success = false, message = "Instance not found" });
|
||||
}
|
||||
|
||||
return Ok(new { success = true, message = "Heartbeat updated" });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all registered Visual Studio instances
|
||||
/// </summary>
|
||||
/// <returns>List of registered instances</returns>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<VSInstanceInfo>), StatusCodes.Status200OK)]
|
||||
public IActionResult GetAll()
|
||||
{
|
||||
var instances = _registryService.GetAllInstances();
|
||||
return Ok(instances);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get instance by process ID
|
||||
/// </summary>
|
||||
/// <param name="processId">Process ID</param>
|
||||
/// <returns>Instance information</returns>
|
||||
[HttpGet("by-pid/{processId}")]
|
||||
[ProducesResponseType(typeof(VSInstanceInfo), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public IActionResult GetByProcessId(int processId)
|
||||
{
|
||||
var instance = _registryService.GetByProcessId(processId);
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
return NotFound(new { success = false, message = "Instance not found" });
|
||||
}
|
||||
|
||||
return Ok(instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get instance by solution path
|
||||
/// </summary>
|
||||
/// <param name="solutionPath">Solution file path</param>
|
||||
/// <returns>Instance information</returns>
|
||||
[HttpGet("by-solution")]
|
||||
[ProducesResponseType(typeof(VSInstanceInfo), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public IActionResult GetBySolutionPath([FromQuery] string solutionPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(solutionPath))
|
||||
{
|
||||
return BadRequest(new { success = false, message = "Solution path is required" });
|
||||
}
|
||||
|
||||
var instance = _registryService.GetBySolutionPath(solutionPath);
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
return NotFound(new { success = false, message = "No instance found for this solution" });
|
||||
}
|
||||
|
||||
return Ok(instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get instance by port
|
||||
/// </summary>
|
||||
/// <param name="port">Port number</param>
|
||||
/// <returns>Instance information</returns>
|
||||
[HttpGet("by-port/{port}")]
|
||||
[ProducesResponseType(typeof(VSInstanceInfo), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public IActionResult GetByPort(int port)
|
||||
{
|
||||
var instance = _registryService.GetByPort(port);
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
return NotFound(new { success = false, message = "Instance not found" });
|
||||
}
|
||||
|
||||
return Ok(instance);
|
||||
}
|
||||
}
|
||||
283
RoslynBridge.WebApi/Controllers/RoslynController.cs
Normal file
283
RoslynBridge.WebApi/Controllers/RoslynController.cs
Normal file
@@ -0,0 +1,283 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RoslynBridge.WebApi.Models;
|
||||
using RoslynBridge.WebApi.Services;
|
||||
|
||||
namespace RoslynBridge.WebApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Controller for Roslyn code analysis operations
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class RoslynController : ControllerBase
|
||||
{
|
||||
private readonly IRoslynBridgeClient _bridgeClient;
|
||||
private readonly ILogger<RoslynController> _logger;
|
||||
|
||||
public RoslynController(IRoslynBridgeClient bridgeClient, ILogger<RoslynController> logger)
|
||||
{
|
||||
_bridgeClient = bridgeClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a Roslyn query
|
||||
/// </summary>
|
||||
/// <param name="request">The query request</param>
|
||||
/// <param name="instancePort">Optional: specific VS instance port to target</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>The query result</returns>
|
||||
/// <response code="200">Query executed successfully</response>
|
||||
/// <response code="400">Invalid request</response>
|
||||
/// <response code="500">Internal server error</response>
|
||||
[HttpPost("query")]
|
||||
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<ActionResult<RoslynQueryResponse>> ExecuteQuery(
|
||||
[FromBody] RoslynQueryRequest request,
|
||||
[FromQuery] int? instancePort = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Received query request: {QueryType}", request.QueryType);
|
||||
|
||||
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
_logger.LogWarning("Query failed: {Error}", result.Error);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all projects in the solution
|
||||
/// </summary>
|
||||
/// <param name="instancePort">Optional: specific VS instance port to target</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>List of projects</returns>
|
||||
[HttpGet("projects")]
|
||||
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<RoslynQueryResponse>> GetProjects(
|
||||
[FromQuery] int? instancePort = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var request = new RoslynQueryRequest { QueryType = "getprojects" };
|
||||
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get solution overview
|
||||
/// </summary>
|
||||
/// <param name="instancePort">Optional: specific VS instance port to target</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Solution statistics and overview</returns>
|
||||
[HttpGet("solution/overview")]
|
||||
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<RoslynQueryResponse>> GetSolutionOverview(
|
||||
[FromQuery] int? instancePort = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var request = new RoslynQueryRequest { QueryType = "getsolutionoverview" };
|
||||
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get diagnostics (errors and warnings)
|
||||
/// </summary>
|
||||
/// <param name="filePath">Optional file path to filter diagnostics</param>
|
||||
/// <param name="instancePort">Optional: specific VS instance port to target</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>List of diagnostics</returns>
|
||||
[HttpGet("diagnostics")]
|
||||
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<RoslynQueryResponse>> GetDiagnostics(
|
||||
[FromQuery] string? filePath = null,
|
||||
[FromQuery] int? instancePort = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var request = new RoslynQueryRequest
|
||||
{
|
||||
QueryType = "getdiagnostics",
|
||||
FilePath = filePath
|
||||
};
|
||||
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get symbol information at a specific position
|
||||
/// </summary>
|
||||
/// <param name="filePath">File path</param>
|
||||
/// <param name="line">Line number (1-based)</param>
|
||||
/// <param name="column">Column number (0-based)</param>
|
||||
/// <param name="instancePort">Optional: specific VS instance port to target</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Symbol information</returns>
|
||||
[HttpGet("symbol")]
|
||||
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<RoslynQueryResponse>> GetSymbol(
|
||||
[FromQuery] string filePath,
|
||||
[FromQuery] int line,
|
||||
[FromQuery] int column,
|
||||
[FromQuery] int? instancePort = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var request = new RoslynQueryRequest
|
||||
{
|
||||
QueryType = "getsymbol",
|
||||
FilePath = filePath,
|
||||
Line = line,
|
||||
Column = column
|
||||
};
|
||||
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all references to a symbol
|
||||
/// </summary>
|
||||
/// <param name="filePath">File path</param>
|
||||
/// <param name="line">Line number (1-based)</param>
|
||||
/// <param name="column">Column number (0-based)</param>
|
||||
/// <param name="instancePort">Optional: specific VS instance port to target</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>List of references</returns>
|
||||
[HttpGet("references")]
|
||||
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<RoslynQueryResponse>> FindReferences(
|
||||
[FromQuery] string filePath,
|
||||
[FromQuery] int line,
|
||||
[FromQuery] int column,
|
||||
[FromQuery] int? instancePort = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var request = new RoslynQueryRequest
|
||||
{
|
||||
QueryType = "findreferences",
|
||||
FilePath = filePath,
|
||||
Line = line,
|
||||
Column = column
|
||||
};
|
||||
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search for symbols by name
|
||||
/// </summary>
|
||||
/// <param name="symbolName">Symbol name or pattern</param>
|
||||
/// <param name="kind">Optional symbol kind filter</param>
|
||||
/// <param name="instancePort">Optional: specific VS instance port to target</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>List of matching symbols</returns>
|
||||
[HttpGet("symbol/search")]
|
||||
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<RoslynQueryResponse>> FindSymbol(
|
||||
[FromQuery] string symbolName,
|
||||
[FromQuery] string? kind = null,
|
||||
[FromQuery] int? instancePort = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var request = new RoslynQueryRequest
|
||||
{
|
||||
QueryType = "findsymbol",
|
||||
SymbolName = symbolName
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(kind))
|
||||
{
|
||||
request.Parameters = new Dictionary<string, string> { ["kind"] = kind };
|
||||
}
|
||||
|
||||
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format a document
|
||||
/// </summary>
|
||||
/// <param name="filePath">File path to format</param>
|
||||
/// <param name="instancePort">Optional: specific VS instance port to target</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Format operation result</returns>
|
||||
[HttpPost("format")]
|
||||
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<RoslynQueryResponse>> FormatDocument(
|
||||
[FromBody] string filePath,
|
||||
[FromQuery] int? instancePort = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var request = new RoslynQueryRequest
|
||||
{
|
||||
QueryType = "formatdocument",
|
||||
FilePath = filePath
|
||||
};
|
||||
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a NuGet package to a project
|
||||
/// </summary>
|
||||
/// <param name="projectName">Project name</param>
|
||||
/// <param name="packageName">NuGet package name</param>
|
||||
/// <param name="version">Optional package version</param>
|
||||
/// <param name="instancePort">Optional: specific VS instance port to target</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Operation result</returns>
|
||||
[HttpPost("project/package/add")]
|
||||
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<RoslynQueryResponse>> AddNuGetPackage(
|
||||
[FromQuery] string projectName,
|
||||
[FromQuery] string packageName,
|
||||
[FromQuery] string? version = null,
|
||||
[FromQuery] int? instancePort = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var request = new RoslynQueryRequest
|
||||
{
|
||||
QueryType = "addnugetpackage",
|
||||
ProjectName = projectName,
|
||||
PackageName = packageName,
|
||||
Version = version
|
||||
};
|
||||
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a project
|
||||
/// </summary>
|
||||
/// <param name="projectName">Project name</param>
|
||||
/// <param name="configuration">Build configuration (Debug/Release)</param>
|
||||
/// <param name="instancePort">Optional: specific VS instance port to target</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>Build result</returns>
|
||||
[HttpPost("project/build")]
|
||||
[ProducesResponseType(typeof(RoslynQueryResponse), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<RoslynQueryResponse>> BuildProject(
|
||||
[FromQuery] string projectName,
|
||||
[FromQuery] string? configuration = null,
|
||||
[FromQuery] int? instancePort = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var request = new RoslynQueryRequest
|
||||
{
|
||||
QueryType = "buildproject",
|
||||
ProjectName = projectName,
|
||||
Configuration = configuration
|
||||
};
|
||||
var result = await _bridgeClient.ExecuteQueryAsync(request, instancePort, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user