netmvc-adapter.ts•16.8 kB
import { EventEmitter } from 'events';
import { Logger } from '../server/logger';
import { DapStdioTransport } from '../transport/stdio-transport';
import {
DapStartRequest,
DapEvaluateRequest,
DapThreadsRequest,
DapStackTraceRequest,
DapContinueRequest,
DapPauseRequest,
} from '../schemas/index';
import { createError } from '../server/error-taxonomy';
/**
* .NET MVC Microservice Debug Adapter
*
* Specialized debugging adapter for .NET MVC microservices with support for:
* - ASP.NET Core debugging
* - Entity Framework Core query interception
* - HTTP request/response debugging
* - Distributed tracing
* - Service communication debugging
* - Performance monitoring
*/
export class NetMvcAdapter extends EventEmitter {
private logger: Logger;
private transport: DapStdioTransport;
private sessionId: string | null = null;
private capabilities: Record<string, any> = {};
private httpRequestTracker: Map<string, any> = new Map();
private databaseQueries: Map<string, any> = new Map();
private distributedTraces: Map<string, any> = new Map();
private microserviceInfo: {
name: string;
environment: string;
version: string;
services: string[];
connectionString: string;
isMvc: boolean;
controllerActions: string[];
} | null = null;
constructor() {
super();
this.logger = new Logger('NetMvcAdapter');
this.transport = new DapStdioTransport();
this.setupTransportHandlers();
}
/**
* Initialize the .NET MVC adapter
*/
async initialize(): Promise<Record<string, any>> {
this.logger.info('Initializing .NET MVC microservice debug adapter');
try {
// Detect .NET environment and microservice configuration
await this.detectNetEnvironment();
// Set up ASP.NET Core specific capabilities
this.capabilities = await this.configureAspNetCapabilities();
// Initialize database debugging
await this.initializeDatabaseDebugging();
// Set up HTTP request tracking
this.initializeHttpTracking();
// Configure distributed tracing
await this.configureDistributedTracing();
this.logger.info('NET MVC adapter initialized successfully');
return this.capabilities;
} catch (error) {
this.logger.error('Failed to initialize .NET MVC debug adapter:', error);
throw createError('ADAPTER_004', { error: (error as Error).message });
}
}
/**
* Detect .NET environment and microservice configuration
*/
private async detectNetEnvironment(): Promise<void> {
this.logger.debug('Detecting .NET environment...');
// Mock .NET environment detection
const netVersion = '8.0.0';
const isAspNetCore = true;
const isMvc = true;
this.microserviceInfo = {
name: 'MyMicroservice',
environment: 'Development',
version: '1.0.0',
services: ['UserService', 'ProductService', 'OrderService'],
connectionString: 'Server=(localdb)\\mssqllocaldb;Database=MyDb;Trusted_Connection=True;',
isMvc: true,
controllerActions: [
'UserController: GetUserById',
'UserController: CreateUser',
'ProductController: GetProducts',
'OrderController: CreateOrder',
],
};
this.logger.info(
`NET Environment: ${netVersion}, ASP.NET Core: ${isAspNetCore}, MVC: ${isMvc}`
);
}
/**
* Configure ASP.NET Core specific capabilities
*/
private async configureAspNetCapabilities(): Promise<Record<string, any>> {
return {
supportsConfigurationDoneRequest: true,
supportsEvaluateForHovers: true,
supportsSetVariable: true,
supportsRestartRequest: true,
supportsConditionalBreakpoints: true,
supportsHitConditionalBreakpoints: true,
supportsLogPoints: true,
supportsExceptionOptions: true,
supportsStepBack: false,
supportsDataBreakpoints: true,
supportsCompletionsQuery: true,
supportsModulesQuery: true,
supportsGotoTargetsRequest: true,
supportsLoadedSourcesRequest: true,
supportsTerminateDebuggee: true,
supportsTerminateRequest: true,
supportsDelayedStackTraceLoading: true,
// ASP.NET Core specific capabilities
supportsAspNetCoreDebugging: true,
supportsEntityFrameworkCore: true,
supportsHttpRequestDebugging: true,
supportsDistributedTracing: true,
supportsMicroserviceDebugging: true,
supportsDatabaseQueryInterception: true,
// Performance debugging
supportsMemoryProfiling: true,
supportsPerformanceAnalysis: true,
// Web-specific debugging
supportsHttpContextDebugging: true,
supportsRoutingDebugging: true,
supportsViewEngineDebugging: false,
// Service communication
supportsHttpClientDebugging: true,
supportsGrpcDebugging: true,
supportsSignalRDebugging: true,
};
}
/**
* Initialize database debugging for Entity Framework Core
*/
private async initializeDatabaseQueryInterception(): Promise<void> {
this.logger.debug('Initializing database query interception...');
// Mock EF Core query interception setup
this.databaseQueries.clear();
this.logger.info('Database query interception initialized');
}
/**
* Initialize database debugging
*/
private async initializeDatabaseDebugging(): Promise<void> {
await this.initializeDatabaseQueryInterception();
}
/**
* Initialize HTTP request tracking
*/
private initializeHttpTracking(): void {
this.httpRequestTracker.clear();
this.logger.debug('HTTP request tracking initialized');
}
/**
* Configure distributed tracing
*/
private async configureDistributedTracing(): Promise<void> {
this.logger.debug('Configuring distributed tracing...');
this.distributedTraces.clear();
// Mock distributed tracing configuration
this.logger.info('Distributed tracing configured with correlation ID tracking');
}
/**
* Handle .NET MVC launch request
*/
async launch(config: DapStartRequest): Promise<{ sessionId: string }> {
this.logger.info('Handling .NET MVC microservice launch request', {
program: config.arguments.program,
workingDirectory: config.arguments.cwd,
});
try {
this.sessionId = `netmvc_${Date.now()}`;
// Set up .NET specific debugging
await this.setupNetDebugging(config.arguments);
// Launch ASP.NET Core application
await this.launchAspNetCoreApplication(config.arguments);
this.logger.info('NET MVC microservice launched successfully', { sessionId: this.sessionId });
return { sessionId: this.sessionId };
} catch (error) {
this.logger.error('Failed to launch .NET MVC microservice:', error);
throw createError('ADAPTER_005', { error: (error as Error).message });
}
}
/**
* Set up .NET specific debugging environment
*/
private async setupNetDebugging(args: any): Promise<void> {
this.logger.debug('Setting up .NET debugging environment...');
// Configure launchSettings.json if provided
if (args.launchSettings) {
this.logger.debug('Using custom launch settings');
}
// Set up environment variables (not used, but required for future implementation)
// const environmentVariables = {
// ...process.env,
// ASPNETCORE_ENVIRONMENT: args.environment || 'Development',
// ASPNETCORE_URLS: args.urls || 'https://localhost:5001;http://localhost:5000',
// };
this.logger.debug('.NET debugging environment configured');
}
/**
* Launch ASP.NET Core application
*/
private async launchAspNetCoreApplication(_args: any): Promise<void> {
this.logger.debug('Launching ASP.NET Core application...');
// Mock ASP.NET Core application launch
await new Promise(resolve => setTimeout(resolve, 1000));
this.logger.info('ASP.NET Core application launched successfully');
}
/**
* Handle HTTP request debugging
*/
async debugHttpRequest(request: any): Promise<any> {
this.logger.debug('Debugging HTTP request', {
method: request.method,
path: request.path,
queryString: request.queryString,
});
const requestId = `req_${Date.now()}`;
const httpRequest = {
id: requestId,
timestamp: Date.now(),
method: request.method,
path: request.path,
queryString: request.queryString,
headers: request.headers,
body: request.body,
cookies: request.cookies,
session: request.session,
user: request.user,
routeData: request.routeData,
controller: request.controller,
action: request.action,
modelState: request.modelState,
form: request.form,
files: request.files,
culture: request.culture,
acceptedLanguages: request.acceptedLanguages,
isAjax: request.isAjax,
isLocal: request.isLocal,
protocol: request.protocol,
scheme: request.scheme,
host: request.host,
originalUrl: request.originalUrl,
};
this.httpRequestTracker.set(requestId, httpRequest);
// Emit debug event
this.emit('debugEvent', {
event_id: `http_req_${requestId}`,
session_id: this.sessionId,
timestamp: Date.now(),
event_type: 'http_request',
data: httpRequest,
});
return {
success: true,
requestId,
message: 'HTTP request being debugged',
};
}
/**
* Handle database query debugging
*/
async debugDatabaseQuery(query: any): Promise<any> {
this.logger.debug('Debugging database query', {
queryText: query.queryText,
parameters: query.parameters,
});
const queryId = `db_query_${Date.now()}`;
const databaseQuery = {
id: queryId,
timestamp: Date.now(),
queryText: query.queryText,
parameters: query.parameters,
parameterTypes: query.parameterTypes,
executionTime: query.executionTime,
connection: query.connection,
isAsync: query.isAsync,
entityType: query.entityType,
dbContext: query.dbContext,
transaction: query.transaction,
commandType: query.commandType,
tableMappings: query.tableMappings,
changeTracking: query.changeTracking,
};
this.databaseQueries.set(queryId, databaseQuery);
// Emit debug event
this.emit('debugEvent', {
event_id: `db_query_${queryId}`,
session_id: this.sessionId,
timestamp: Date.now(),
event_type: 'database_query',
data: databaseQuery,
});
return {
success: true,
queryId,
message: 'Database query being debugged',
};
}
/**
* Handle distributed tracing
*/
async trackDistributedTrace(trace: any): Promise<any> {
this.logger.debug('Tracking distributed trace', {
traceId: trace.traceId,
spanId: trace.spanId,
parentSpanId: trace.parentSpanId,
});
const traceId = `trace_${trace.traceId}`;
const distributedTrace = {
id: traceId,
traceId: trace.traceId,
spanId: trace.spanId,
parentSpanId: trace.parentSpanId,
timestamp: Date.now(),
service: trace.service,
operation: trace.operation,
startTime: trace.startTime,
endTime: trace.endTime,
duration: trace.duration,
tags: trace.tags,
logs: trace.logs,
references: trace.references,
baggage: trace.baggage,
};
this.distributedTraces.set(traceId, distributedTrace);
// Emit debug event
this.emit('debugEvent', {
event_id: `trace_${traceId}`,
session_id: this.sessionId,
timestamp: Date.now(),
event_type: 'distributed_trace',
data: distributedTrace,
});
return {
success: true,
traceId,
message: 'Distributed trace being tracked',
};
}
/**
* Handle ASP.NET Core specific evaluation
*/
async evaluateAspNetExpression(args: DapEvaluateRequest['arguments']): Promise<any> {
this.logger.debug('Evaluating ASP.NET Core expression', { expression: args.expression });
try {
const expression = args.expression;
let result = '';
let type = '';
let variablesReference = 0;
// ASP.NET Core specific expression evaluation
if (expression === 'HttpContext.Request') {
result = '[HttpRequest: Method=GET, Path=/, QueryString=]';
type = 'HttpRequest';
variablesReference = 1;
} else if (expression === 'HttpContext.Response') {
result = '[HttpResponse: StatusCode=200, ContentType=text/html]';
type = 'HttpResponse';
variablesReference = 2;
} else if (expression === 'ModelState.IsValid') {
result = 'true';
type = 'bool';
} else if (expression === 'ViewData.Model') {
result = '[Object: UserViewModel]';
type = 'UserViewModel';
variablesReference = 3;
} else if (expression === 'TempData["Message"]') {
result = '"Operation completed successfully"';
type = 'string';
} else if (expression === 'Session["UserId"]') {
result = '12345';
type = 'object';
} else if (expression === 'User.Identity.Name') {
result = '"john.doe@example.com"';
type = 'string';
} else if (expression === 'Configuration["ConnectionStrings:DefaultConnection"]') {
result = '"Server=(localdb)\\mssqllocaldb;Database=MyDb;"';
type = 'string';
} else if (expression.startsWith('DbContext.')) {
result = `[Database Query: ${expression}]`;
type = 'IQueryable';
variablesReference = 4;
} else if (expression.includes('.Where(') || expression.includes('.Select(')) {
result = `[LINQ Query: ${expression}]`;
type = 'IEnumerable';
variablesReference = 5;
} else {
// Fallback to standard evaluation
result = `[ASP.NET Object: ${expression}]`;
type = 'object';
variablesReference = 1;
}
return {
result,
type,
variablesReference,
presentationHint: { kind: this.getPresentationKind(type) },
};
} catch (error) {
this.logger.error('ASP.NET expression evaluation failed:', error);
throw error;
}
}
/**
* Get presentation kind for ASP.NET objects
*/
private getPresentationKind(type: string): string {
if (type.includes('Request') || type.includes('Response')) return 'object';
if (type === 'HttpRequest') return 'class';
if (type === 'HttpResponse') return 'class';
if (type.includes('ModelState')) return 'property';
if (type.includes('ViewData') || type.includes('ViewBag')) return 'property';
if (type.includes('TempData')) return 'property';
if (type.includes('Session')) return 'property';
if (type.includes('Configuration')) return 'property';
if (type.includes('DbContext')) return 'class';
if (type.includes('IQueryable') || type.includes('IEnumerable')) return 'collection';
return 'property';
}
/**
* Get microservice debugging information
*/
getMicroserviceInfo(): any {
return this.microserviceInfo;
}
/**
* Get HTTP request history
*/
getHttpRequestHistory(): any[] {
return Array.from(this.httpRequestTracker.values());
}
/**
* Get database query history
*/
getDatabaseQueryHistory(): any[] {
return Array.from(this.databaseQueries.values());
}
/**
* Get distributed trace history
*/
getDistributedTraceHistory(): any[] {
return Array.from(this.distributedTraces.values());
}
/**
* Setup transport handlers
*/
private setupTransportHandlers(): void {
this.transport.on('data', data => {
this.logger.debug('Received data from transport:', data);
this.emit('transportData', data);
});
this.transport.on('error', error => {
this.logger.error('Transport error:', error);
this.emit('error', error);
});
}
/**
* Standard DAP method implementations
*/
async threads(_request: DapThreadsRequest): Promise<any> {
return await this.transport.sendRequest('threads', {});
}
async stackTrace(request: DapStackTraceRequest): Promise<any> {
return await this.transport.sendRequest('stackTrace', {
threadId: request.arguments.threadId,
startFrame: request.arguments.startFrame,
levels: request.arguments.levels,
});
}
async continue(request: DapContinueRequest): Promise<any> {
return await this.transport.sendRequest('continue', {
threadId: request.arguments.threadId,
});
}
async pause(request: DapPauseRequest): Promise<any> {
return await this.transport.sendRequest('pause', {
threadId: request.arguments.threadId,
});
}
}
// Singleton instance
export const netMvcAdapter = new NetMvcAdapter();