aspnet-eval.ts•17.9 kB
import { z } from 'zod';
import { Logger } from '../server/logger';
import { MetricsCollector } from '../server/metrics';
import { NetMvcAdapter } from '../adapters/netmvc-adapter';
import {
DapAspNetEvaluationRequest,
DapAspNetEvaluationResponse,
} from '../schemas/netmvc-tools.schemas';
/**
* dap.evaluateAspNet tool implementation
*
* Handles ASP.NET Core specific expression evaluation with:
* - HttpContext evaluation
* - ViewBag and ViewData inspection
* - Model state validation
* - Session and TempData access
* - Configuration evaluation
* - DbContext query inspection
*/
export class AspNetEvalTool {
private logger: Logger;
private metrics: MetricsCollector;
private adapter: NetMvcAdapter;
constructor() {
this.logger = new Logger('dap.evaluateAspNet');
this.metrics = MetricsCollector.getInstance();
this.adapter = new NetMvcAdapter();
}
/**
* Execute ASP.NET Core expression evaluation
*/
async execute(args: any): Promise<DapAspNetEvaluationResponse> {
this.metrics.startTimer('dap.evaluateAspNet.tool');
this.metrics.increment('dap.evaluateAspNet.count');
try {
// Validate input
const validatedArgs = this.validateArgs(args);
this.logger.debug('Evaluating ASP.NET Core expression', {
expression: validatedArgs.expression,
frameId: validatedArgs.frameId,
context: validatedArgs.context,
});
// Process ASP.NET evaluation
const evalResult = await this.evaluateAspNetExpression(validatedArgs);
this.logger.info('ASP.NET evaluation completed', {
expression: validatedArgs.expression,
resultType: evalResult.type,
variablesReference: evalResult.variablesReference,
});
this.metrics.stopTimer('dap.evaluateAspNet.tool');
return this.createSuccessResponse(evalResult);
} catch (error) {
this.logger.error('ASP.NET evaluation failed:', error);
this.metrics.increment('dap.evaluateAspNet.error.count');
this.metrics.stopTimer('dap.evaluateAspNet.tool');
return this.createErrorResponse((error as Error).message);
}
}
/**
* Validate input arguments
*/
private validateArgs(args: any): DapAspNetEvaluationRequest['arguments'] {
const schema = z.object({
expression: z.string(),
frameId: z.number().optional(),
context: z.enum(['hover', 'watch', 'repl', 'clipboard']).default('repl'),
ASPNETSpecific: z.boolean().default(true),
});
return schema.parse(args);
}
/**
* Process ASP.NET Core expression evaluation
*/
private async evaluateAspNetExpression(
args: DapAspNetEvaluationRequest['arguments']
): Promise<any> {
// Use the adapter for ASP.NET specific evaluation
const result = await this.adapter.evaluateAspNetExpression(args as any);
// Enhance the result with ASP.NET specific information
const enhancedResult = {
...result,
ASPNETSpecific: {
httpContext: this.generateHttpContextInfo(),
controllerContext: this.generateControllerContextInfo(),
routeData: this.generateRouteDataInfo(),
viewData: this.generateViewDataInfo(),
tempData: this.generateTempDataInfo(),
session: this.generateSessionInfo(),
configuration: this.generateConfigurationInfo(),
dbContext: this.generateDbContextInfo(),
identity: this.generateIdentityInfo(),
},
};
return enhancedResult;
}
/**
* Generate HttpContext information
*/
private generateHttpContextInfo(): any {
return {
requestId: `req_${Date.now()}`,
requestIdShort: Date.now().toString().slice(-6),
method: 'GET',
path: '/api/users/123',
queryString: '?include=orders&limit=10',
scheme: 'https',
host: 'localhost:5001',
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
contentType: 'application/json',
contentLength: 256,
isAjax: false,
isLocal: true,
protocol: 'HTTPS/1.1',
remoteIpAddress: '127.0.0.1',
remotePort: 54321,
connectionId: 'CON_123456',
traceIdentifier: `0HLTFV6TTT0P:00000001`,
features: {
IHttpConnectionFeature: { connectionId: 'CON_123456' },
IHttpActivityFeature: { traceIdentifier: '0HLTFV6TTT0P:00000001' },
IHttpResponseBodyFeature: { buffering: false },
},
};
}
/**
* Generate Controller Context information
*/
private generateControllerContextInfo(): any {
return {
controllerName: 'UsersController',
controllerType: 'MyApp.Controllers.UsersController',
actionName: 'GetUserById',
actionDescriptor: {
methodInfo: 'System.Threading.Tasks.Task<IActionResult> GetUserById(int id)',
routeValues: { id: '123' },
filters: [],
endpointMetadata: [],
},
areaName: null,
routeData: {
values: { controller: 'Users', action: 'GetUserById', id: '123' },
dataTokens: {},
template: 'api/users/{id}',
},
modelState: {
isValid: true,
validationState: 0, // Valid
errors: [],
},
actionContext: {
actionDescriptor: {
displayName: 'GetUserById',
name: 'GetUserById',
filterPipeline: [],
methodInfo: {
name: 'GetUserById',
returnType: 'System.Threading.Tasks.Task<IActionResult>',
parameters: [{ name: 'id', parameterType: 'System.Int32' }],
},
endpointMetadata: [],
},
routeData: {
values: { controller: 'Users', action: 'GetUserById', id: '123' },
},
httpContext: {
request: {
method: 'GET',
path: '/api/users/123',
headers: {},
},
},
},
};
}
/**
* Generate Route Data information
*/
private generateRouteDataInfo(): any {
return {
router: 'Microsoft.AspNetCore.Routing.RouteCollection',
currentRoute: {
routeName: 'Users_GetUserById',
template: 'api/users/{id}',
defaults: { controller: 'Users', action: 'GetUserById' },
constraints: { id: '\\\\d+' },
dataTokens: {},
inlineConstraints: [],
},
routeValues: {
controller: 'Users',
action: 'GetUserById',
id: '123',
},
dataTokens: {},
inheritedRouteData: [],
};
}
/**
* Generate ViewData information
*/
private generateViewDataInfo(): any {
return {
model: {
id: 123,
name: 'John Doe',
email: 'john.doe@example.com',
createdAt: '2023-01-01T00:00:00Z',
updatedAt: '2023-12-01T00:00:00Z',
isActive: true,
profile: {
firstName: 'John',
lastName: 'Doe',
bio: 'Software developer',
avatarUrl: 'https://example.com/avatar.jpg',
},
orders: [
{ id: 1, total: 99.99, date: '2023-11-01T00:00:00Z' },
{ id: 2, total: 149.99, date: '2023-11-15T00:00:00Z' },
],
},
pageTitle: 'User Profile - John Doe',
pageDescription: 'View and manage user profile information',
metaTags: {
title: 'User Profile - John Doe',
description: 'View and manage user profile information',
keywords: 'user profile, account settings, personal information',
},
breadcrumbs: [
{ name: 'Home', url: '/' },
{ name: 'Users', url: '/users' },
{ name: 'John Doe', url: '/users/123', isActive: true },
],
flashMessages: [
{ type: 'success', message: 'Profile updated successfully' },
{ type: 'info', message: 'Welcome back, John!' },
],
};
}
/**
* Generate TempData information
*/
private generateTempDataInfo(): any {
return {
keep: {
successMessage: true,
notification: true,
},
discard: {
oldData: true,
},
items: {
successMessage: 'User profile updated successfully',
notification: 'Welcome back, John!',
alert: 'Your session will expire in 15 minutes',
redirectUrl: '/users/profile',
formModel: {
id: 123,
name: 'John Doe',
email: 'john.doe@example.com',
},
},
keys: ['successMessage', 'notification', 'alert', 'redirectUrl', 'formModel'],
peek: {
successMessage: 'User profile updated successfully',
notification: 'Welcome back, John!',
},
};
}
/**
* Generate Session information
*/
private generateSessionInfo(): any {
return {
sessionId: `sess_${Date.now()}`,
sessionToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
isAvailable: true,
isReadOnly: false,
timeout: 30, // minutes
idleTimeout: 20, // minutes
maxIdleTimeout: 60, // minutes
sessionID: 'sess_1234567890',
cookie: {
name: '.AspNetCore.Session',
value: 'CfDJ8H...etc...',
path: '/',
domain: 'localhost',
secure: true,
httpOnly: true,
sameSite: 'Lax',
expires: new Date(Date.now() + 1800000), // 30 minutes from now
},
items: {
userId: 123,
username: 'john.doe',
email: 'john.doe@example.com',
role: 'User',
permissions: ['read', 'write'],
preferences: {
theme: 'dark',
language: 'en-US',
notifications: true,
},
lastActivity: '2023-12-01T10:30:00Z',
loginCount: 42,
createdAt: '2023-01-01T00:00:00Z',
},
keys: [
'userId',
'username',
'email',
'role',
'permissions',
'preferences',
'lastActivity',
'loginCount',
'createdAt',
],
};
}
/**
* Generate Configuration information
*/
private generateConfigurationInfo(): any {
return {
configuration: {
applicationName: 'MyApp',
environment: 'Development',
version: '1.0.0',
logging: {
level: 'Information',
providers: [
{ name: 'Console', enabled: true },
{ name: 'Debug', enabled: true },
{ name: 'File', enabled: false },
],
},
databases: {
default: {
provider: 'SqlServer',
connectionString:
'Server=(localdb)\\\\mssqllocaldb;Database=MyDb;Trusted_Connection=True;',
timeout: 30,
commandTimeout: 30,
maxPoolSize: 100,
minPoolSize: 5,
},
secondary: {
provider: 'PostgreSQL',
connectionString: 'Host=localhost;Database=MyDb;Username=user;Password=pass;',
timeout: 30,
},
},
security: {
jwt: {
issuer: 'MyApp',
audience: 'MyAppUsers',
secret: 'your-secret-key-here',
expiration: '01:00:00',
validateIssuer: true,
validateAudience: true,
validateLifetime: true,
},
password: {
minLength: 8,
requireDigit: true,
requireLowercase: true,
requireUppercase: true,
requireNonalphanumeric: true,
},
},
features: {
enableSwagger: true,
enableSignalR: false,
enableHealthChecks: true,
enableMetrics: true,
},
},
configurationSources: [
{
name: 'appsettings.json',
path: 'appsettings.json',
},
{
name: 'appsettings.Development.json',
path: 'appsettings.Development.json',
},
{
name: 'Environment Variables',
path: 'ASPNETCORE_*',
},
{
name: 'User Secrets',
path: 'secrets.json',
},
],
reloadable: true,
cachedValues: {
databaseConnection: 'cached_connection_string',
jwtSecret: 'cached_jwt_secret',
featureFlags: ['swagger', 'healthchecks'],
},
};
}
/**
* Generate DbContext information
*/
private generateDbContextInfo(): any {
return {
dbContextType: 'MyApp.Data.ApplicationDbContext',
databaseProvider: 'Microsoft.EntityFrameworkCore.SqlServer',
databaseName: 'MyDatabase',
connectionId: 'DefaultConnection',
configuration: {
commandTimeout: 30,
maxBatchSize: 42,
minBatchSize: 1,
useRelationalNulls: true,
querySplittingBehavior: 'SplitQuery',
changeTracker: {
autoDetectChangesEnabled: true,
queryTrackingBehavior: 'TrackAll',
lazyLoadingEnabled: true,
dataAnnotationsEnabled: true,
},
},
changeTracking: {
hasChanges: false,
changeCount: 0,
trackedEntities: 0,
autoDetectChanges: true,
validateOnSaveEnabled: true,
},
entities: {
users: {
entryType: 'MyApp.Models.User',
state: 'Unchanged',
isLoaded: true,
isModified: false,
properties: {
id: { originalValue: 123, currentValue: 123, isModified: false },
name: { originalValue: 'John Doe', currentValue: 'John Doe', isModified: false },
email: {
originalValue: 'john.doe@example.com',
currentValue: 'john.doe@example.com',
isModified: false,
},
createdAt: {
originalValue: '2023-01-01T00:00:00Z',
currentValue: '2023-01-01T00:00:00Z',
isModified: false,
},
},
},
orders: {
entryType: 'MyApp.Models.Order',
state: 'Unchanged',
isLoaded: true,
isModified: false,
properties: {
id: { originalValue: 1, currentValue: 1, isModified: false },
userId: { originalValue: 123, currentValue: 123, isModified: false },
total: { originalValue: 99.99, currentValue: 99.99, isModified: false },
createdAt: {
originalValue: '2023-11-01T00:00:00Z',
currentValue: '2023-11-01T00:00:00Z',
isModified: false,
},
},
},
},
queries: {
lastQuery: 'SELECT * FROM Users WHERE Id = @p0',
activeQueries: [
{
id: 'query_1',
sql: 'SELECT u.* FROM Users u WHERE u.Id = @p0',
parameters: { '@p0': 123 },
startTime: Date.now(),
duration: 15,
isAsync: true,
connection: 'DefaultConnection',
},
],
},
};
}
/**
* Generate Identity information
*/
private generateIdentityInfo(): any {
return {
isAuthenticated: true,
authenticationType: 'Bearer',
name: 'john.doe@example.com',
userId: '123',
userName: 'john.doe',
email: 'john.doe@example.com',
roles: ['User'],
claims: [
{
type: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name',
value: 'john.doe@example.com',
},
{
type: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier',
value: '123',
},
{
type: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress',
value: 'john.doe@example.com',
},
{ type: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role', value: 'User' },
{ type: 'custom/userId', value: '123' },
{ type: 'custom/username', value: 'john.doe' },
],
authenticationProperties: {
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
issuedUtc: new Date(),
expiresUtc: new Date(Date.now() + 3600000),
isPersistent: false,
isRefreshTokenAvailable: true,
},
principal: {
identity: {
isAuthenticated: true,
authenticationType: 'Bearer',
name: 'john.doe@example.com',
userId: '123',
},
claims: [
{ type: 'name', value: 'john.doe@example.com' },
{ type: 'email', value: 'john.doe@example.com' },
{ type: 'role', value: 'User' },
],
properties: {
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
expires: new Date(Date.now() + 3600000),
},
},
};
}
/**
* Create success response
*/
private createSuccessResponse(body: any): DapAspNetEvaluationResponse {
return {
type: 'response',
seq: 1,
command: 'evaluateAspNet',
request_seq: 1,
success: true,
body: {
result: body.result,
type: body.type,
presentationHint: body.presentationHint,
variablesReference: body.variablesReference,
namedVariables: body.namedVariables,
indexedVariables: body.indexedVariables,
ASPNETSpecific: body.ASPNETSpecific,
},
};
}
/**
* Create error response
*/
private createErrorResponse(message: string): DapAspNetEvaluationResponse {
return {
type: 'response',
seq: 1,
command: 'evaluateAspNet',
request_seq: 1,
success: false,
message,
body: {
result: '',
type: 'error',
presentationHint: { kind: 'error' },
variablesReference: 0,
ASPNETSpecific: {},
},
};
}
/**
* Generate ASP.NET stack trace
*/
}
// Singleton instance
export const aspNetEvalTool = new AspNetEvalTool();
// Tool execution function
export async function executeAspNetEval(args: any): Promise<DapAspNetEvaluationResponse> {
return await aspNetEvalTool.execute(args);
}