1+ using Microsoft . AspNetCore . Builder ;
2+ using Microsoft . AspNetCore . Http ;
3+ using Microsoft . AspNetCore . Mvc ;
4+ using Microsoft . AspNetCore . Routing ;
5+ using Microsoft . Extensions . DependencyInjection ;
6+ using Microsoft . Extensions . Diagnostics . HealthChecks ;
7+ using Microsoft . Extensions . Logging ;
8+ using System . Text . Json ;
9+
10+ namespace NLWebNet . Endpoints ;
11+
12+ /// <summary>
13+ /// Minimal API endpoints for health checks and monitoring
14+ /// </summary>
15+ public static class HealthEndpoints
16+ {
17+ /// <summary>
18+ /// Maps health check endpoints to the application
19+ /// </summary>
20+ /// <param name="app">The endpoint route builder</param>
21+ /// <returns>The endpoint route builder for chaining</returns>
22+ public static IEndpointRouteBuilder MapHealthEndpoints ( this IEndpointRouteBuilder app )
23+ {
24+ // Basic health check endpoint
25+ app . MapGet ( "/health" , GetBasicHealthAsync )
26+ . WithName ( "GetHealth" )
27+ . WithTags ( "Health" )
28+ . WithOpenApi ( operation => new ( operation )
29+ {
30+ Summary = "Basic health check" ,
31+ Description = "Returns the basic health status of the NLWebNet service"
32+ } )
33+ . Produces < HealthCheckResponse > ( StatusCodes . Status200OK )
34+ . Produces < HealthCheckResponse > ( StatusCodes . Status503ServiceUnavailable ) ;
35+
36+ // Detailed health check endpoint
37+ app . MapGet ( "/health/detailed" , GetDetailedHealthAsync )
38+ . WithName ( "GetDetailedHealth" )
39+ . WithTags ( "Health" )
40+ . WithOpenApi ( operation => new ( operation )
41+ {
42+ Summary = "Detailed health check" ,
43+ Description = "Returns detailed health status including individual service checks"
44+ } )
45+ . Produces < DetailedHealthCheckResponse > ( StatusCodes . Status200OK )
46+ . Produces < DetailedHealthCheckResponse > ( StatusCodes . Status503ServiceUnavailable ) ;
47+
48+ return app ;
49+ }
50+
51+ private static async Task < IResult > GetBasicHealthAsync (
52+ [ FromServices ] HealthCheckService healthCheckService ,
53+ [ FromServices ] ILoggerFactory loggerFactory ,
54+ CancellationToken cancellationToken = default )
55+ {
56+ var logger = loggerFactory . CreateLogger ( nameof ( HealthEndpoints ) ) ;
57+
58+ try
59+ {
60+ var healthReport = await healthCheckService . CheckHealthAsync ( cancellationToken ) ;
61+
62+ var response = new HealthCheckResponse
63+ {
64+ Status = healthReport . Status . ToString ( ) ,
65+ TotalDuration = healthReport . TotalDuration
66+ } ;
67+
68+ var statusCode = healthReport . Status == HealthStatus . Healthy
69+ ? StatusCodes . Status200OK
70+ : StatusCodes . Status503ServiceUnavailable ;
71+
72+ logger . LogInformation ( "Health check completed with status: {Status}" , healthReport . Status ) ;
73+
74+ return Results . Json ( response , statusCode : statusCode ) ;
75+ }
76+ catch ( Exception ex )
77+ {
78+ logger . LogError ( ex , "Health check failed with exception" ) ;
79+
80+ var response = new HealthCheckResponse
81+ {
82+ Status = "Unhealthy" ,
83+ TotalDuration = TimeSpan . Zero
84+ } ;
85+
86+ return Results . Json ( response , statusCode : StatusCodes . Status503ServiceUnavailable ) ;
87+ }
88+ }
89+
90+ private static async Task < IResult > GetDetailedHealthAsync (
91+ [ FromServices ] HealthCheckService healthCheckService ,
92+ [ FromServices ] ILoggerFactory loggerFactory ,
93+ CancellationToken cancellationToken = default )
94+ {
95+ var logger = loggerFactory . CreateLogger ( nameof ( HealthEndpoints ) ) ;
96+
97+ try
98+ {
99+ var healthReport = await healthCheckService . CheckHealthAsync ( cancellationToken ) ;
100+
101+ var response = new DetailedHealthCheckResponse
102+ {
103+ Status = healthReport . Status . ToString ( ) ,
104+ TotalDuration = healthReport . TotalDuration ,
105+ Entries = healthReport . Entries . ToDictionary (
106+ kvp => kvp . Key ,
107+ kvp => new HealthCheckEntry
108+ {
109+ Status = kvp . Value . Status . ToString ( ) ,
110+ Description = kvp . Value . Description ,
111+ Duration = kvp . Value . Duration ,
112+ Exception = kvp . Value . Exception ? . Message ,
113+ Data = kvp . Value . Data . Any ( ) ? kvp . Value . Data : null
114+ } )
115+ } ;
116+
117+ var statusCode = healthReport . Status == HealthStatus . Healthy
118+ ? StatusCodes . Status200OK
119+ : StatusCodes . Status503ServiceUnavailable ;
120+
121+ logger . LogInformation ( "Detailed health check completed with status: {Status}, Entries: {EntryCount}" ,
122+ healthReport . Status , healthReport . Entries . Count ) ;
123+
124+ return Results . Json ( response , statusCode : statusCode ) ;
125+ }
126+ catch ( Exception ex )
127+ {
128+ logger . LogError ( ex , "Detailed health check failed with exception" ) ;
129+
130+ var response = new DetailedHealthCheckResponse
131+ {
132+ Status = "Unhealthy" ,
133+ TotalDuration = TimeSpan . Zero ,
134+ Entries = new Dictionary < string , HealthCheckEntry >
135+ {
136+ [ "system" ] = new HealthCheckEntry
137+ {
138+ Status = "Unhealthy" ,
139+ Description = "Health check system failure" ,
140+ Duration = TimeSpan . Zero ,
141+ Exception = ex . Message
142+ }
143+ }
144+ } ;
145+
146+ return Results . Json ( response , statusCode : StatusCodes . Status503ServiceUnavailable ) ;
147+ }
148+ }
149+ }
150+
151+ /// <summary>
152+ /// Basic health check response
153+ /// </summary>
154+ public class HealthCheckResponse
155+ {
156+ public string Status { get ; set ; } = string . Empty ;
157+ public TimeSpan TotalDuration { get ; set ; }
158+ }
159+
160+ /// <summary>
161+ /// Detailed health check response with individual service status
162+ /// </summary>
163+ public class DetailedHealthCheckResponse
164+ {
165+ public string Status { get ; set ; } = string . Empty ;
166+ public TimeSpan TotalDuration { get ; set ; }
167+ public Dictionary < string , HealthCheckEntry > Entries { get ; set ; } = new ( ) ;
168+ }
169+
170+ /// <summary>
171+ /// Individual health check entry details
172+ /// </summary>
173+ public class HealthCheckEntry
174+ {
175+ public string Status { get ; set ; } = string . Empty ;
176+ public string ? Description { get ; set ; }
177+ public TimeSpan Duration { get ; set ; }
178+ public string ? Exception { get ; set ; }
179+ public IReadOnlyDictionary < string , object > ? Data { get ; set ; }
180+ }
0 commit comments