55using Microsoft . AspNetCore . Mvc ;
66using Microsoft . EntityFrameworkCore ;
77using Microsoft . Extensions . AI ;
8+ using Scalar . AspNetCore ;
89using ChatRole = Microsoft . Extensions . AI . ChatRole ;
910
1011var builder = WebApplication . CreateBuilder ( args ) ;
1920
2021builder . Services . AddKernel ( ) ;
2122
22- // Learn more about configuring Swagger/ OpenAPI at https://aka.ms/aspnetcore/swashbuckle
23+ // Learn more about configuring OpenAPI at https://aka.ms/aspnetcore/openapi
2324builder . Services . AddEndpointsApiExplorer ( ) ;
24- builder . Services . AddSwaggerGen ( ) ;
25+ builder . Services . AddOpenApi ( options =>
26+ {
27+ options . AddDocumentTransformer ( ( document , context , cancellationToken ) =>
28+ {
29+ document . Info . Title = "BuildWithAspire API" ;
30+ document . Info . Description = "API for BuildWithAspire application with AI chat capabilities" ;
31+ document . Info . Version = "v1" ;
32+ return Task . CompletedTask ;
33+ } ) ;
34+ } ) ;
2535
2636builder . Services . AddTransient < ChatService > ( ) ;
2737
3242// Configure the HTTP request pipeline.
3343if ( app . Environment . IsDevelopment ( ) )
3444{
35- app . UseSwagger ( ) ;
36- app . UseSwaggerUI ( ) ;
45+ app . MapOpenApi ( ) ;
46+ app . MapScalarApiReference ( options =>
47+ {
48+ options . WithTitle ( "BuildWithAspire API" ) ;
49+ options . WithDefaultHttpClient ( ScalarTarget . CSharp , ScalarClient . HttpClient ) ;
50+ } ) ;
3751}
3852
3953app . UseHttpsRedirection ( ) ;
4862 var startTime = DateTime . UtcNow ;
4963
5064 // Ensure database schema exists
51- var wasCreated = await dbContext . Database . EnsureCreatedAsync ( ) ;
65+ var wasCreated = await dbContext . Database . EnsureCreatedAsync ( ) . ConfigureAwait ( false ) ;
5266 var duration = DateTime . UtcNow - startTime ;
5367
5468 if ( wasCreated )
6680 app . Logger . LogError ( ex , "Database initialization failed. Service will continue without database." ) ;
6781}
6882
69-
7083app . MapGet ( "/weatherforecast" , ( IChatClient client ) =>
7184{
7285 async IAsyncEnumerable < WeatherForecast > GetForecasts ( )
7386 {
7487 for ( int index = 1 ; index <= 5 ; index ++ )
7588 {
7689 var temperature = Random . Shared . Next ( - 20 , 55 ) ;
77- var summary = await GetWeatherSummary ( client , temperature ) ;
90+ var summary = await GetWeatherSummary ( client , temperature ) . ConfigureAwait ( false ) ;
7891 yield return new WeatherForecast
7992 (
8093 DateOnly . FromDateTime ( DateTime . Now . AddDays ( index ) ) ,
@@ -95,7 +108,7 @@ static async Task<string> GetWeatherSummary(IChatClient client, int temp)
95108 // User messages represent user input, whether historical or the most recent input
96109 new ( ChatRole . User , $ "How would you describe the weather at temp { temp } in celcius? Provide the response in 1 word with no punctuation.")
97110 } ;
98- var completion = await client . GetResponseAsync ( conversation ) ;
111+ var completion = await client . GetResponseAsync ( conversation ) . ConfigureAwait ( false ) ;
99112
100113 return $ "{ completion . Text } ";
101114 }
@@ -118,7 +131,7 @@ static async Task<string> GetWeatherSummary(IChatClient client, int temp)
118131 c . UpdatedAt ,
119132 MessageCount = c . Messages . Count ( )
120133 } )
121- . ToListAsync ( ) ;
134+ . ToListAsync ( ) . ConfigureAwait ( false ) ;
122135
123136 return Results . Ok ( conversations ) ;
124137 }
@@ -137,7 +150,7 @@ static async Task<string> GetWeatherSummary(IChatClient client, int temp)
137150 {
138151 var conversation = await db . Conversations
139152 . Include ( c => c . Messages . OrderBy ( m => m . CreatedAt ) )
140- . FirstOrDefaultAsync ( c => c . Id == id ) ;
153+ . FirstOrDefaultAsync ( c => c . Id == id ) . ConfigureAwait ( false ) ;
141154
142155 if ( conversation == null )
143156 {
@@ -160,7 +173,9 @@ static async Task<string> GetWeatherSummary(IChatClient client, int temp)
160173 try
161174 {
162175 if ( string . IsNullOrWhiteSpace ( request . Name ) )
176+ {
163177 return Results . BadRequest ( "Conversation name cannot be empty" ) ;
178+ }
164179
165180 var conversation = new Conversation
166181 {
@@ -171,7 +186,7 @@ static async Task<string> GetWeatherSummary(IChatClient client, int temp)
171186 } ;
172187
173188 db . Conversations . Add ( conversation ) ;
174- await db . SaveChangesAsync ( ) ;
189+ await db . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
175190
176191 return Results . Created ( $ "/conversations/{ conversation . Id } ", conversation ) ;
177192 }
@@ -190,14 +205,18 @@ static async Task<string> GetWeatherSummary(IChatClient client, int temp)
190205 {
191206 // Validate input
192207 if ( string . IsNullOrWhiteSpace ( request . Message ) )
208+ {
193209 return Results . BadRequest ( "Message cannot be empty" ) ;
210+ }
194211
195212 var conversation = await db . Conversations
196213 . Include ( c => c . Messages )
197- . FirstOrDefaultAsync ( c => c . Id == id ) ;
214+ . FirstOrDefaultAsync ( c => c . Id == id ) . ConfigureAwait ( false ) ;
198215
199216 if ( conversation == null )
217+ {
200218 return Results . NotFound ( "Conversation not found" ) ;
219+ }
201220
202221 // Add user message
203222 var userMessage = new Message
@@ -214,7 +233,7 @@ static async Task<string> GetWeatherSummary(IChatClient client, int temp)
214233 conversation . UpdatedAt = DateTime . UtcNow ;
215234
216235 // Save user message first
217- await db . SaveChangesAsync ( ) ;
236+ await db . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
218237
219238 // Prepare history for AI - get all messages for this conversation including the new one
220239 var messages = await db . Messages
@@ -225,13 +244,13 @@ static async Task<string> GetWeatherSummary(IChatClient client, int temp)
225244 Role = m . Role ,
226245 Content = m . Content
227246 } )
228- . ToListAsync ( ) ;
247+ . ToListAsync ( ) . ConfigureAwait ( false ) ;
229248
230249 // Get AI response with timeout handling
231250 string aiResponse ;
232251 try
233252 {
234- aiResponse = await chatService . ProcessMessagesWithHistory ( messages ) ;
253+ aiResponse = await chatService . ProcessMessagesWithHistory ( messages ) . ConfigureAwait ( false ) ;
235254 }
236255 catch ( Exception ex )
237256 {
@@ -251,7 +270,7 @@ static async Task<string> GetWeatherSummary(IChatClient client, int temp)
251270
252271 // Add to DbContext directly instead of through navigation property
253272 db . Messages . Add ( assistantMessage ) ;
254- await db . SaveChangesAsync ( ) ;
273+ await db . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
255274
256275 return Results . Ok ( new { response = aiResponse } ) ;
257276 }
@@ -266,30 +285,35 @@ static async Task<string> GetWeatherSummary(IChatClient client, int temp)
266285
267286app . MapDelete ( "/conversations/{id}" , async ( Guid id , ChatDbContext db ) =>
268287{
269- var conversation = await db . Conversations . FindAsync ( id ) ;
288+ var conversation = await db . Conversations . FindAsync ( id ) . ConfigureAwait ( false ) ;
270289 if ( conversation == null )
290+ {
271291 return Results . NotFound ( ) ;
292+ }
272293
273294 db . Conversations . Remove ( conversation ) ;
274- await db . SaveChangesAsync ( ) ;
295+ await db . SaveChangesAsync ( ) . ConfigureAwait ( false ) ;
275296
276297 return Results . NoContent ( ) ;
277298} )
278299. WithName ( "DeleteConversation" )
279300. WithOpenApi ( ) ;
280301
281302// Keep the original chat endpoint for backward compatibility
282- app . MapGet ( "/chat" , async ( ChatService chatService , string message ) => await chatService . ProcessMessage ( message ) )
303+ app . MapGet ( "/chat" , async ( ChatService chatService , string message ) => await chatService . ProcessMessage ( message ) . ConfigureAwait ( false ) )
283304 . WithName ( "GetChat" )
284305 . WithOpenApi ( ) ;
285306
286307app . Run ( ) ;
287308
288- record WeatherForecast ( DateOnly Date , int TemperatureC , string ? Summary )
309+ public record WeatherForecast ( DateOnly Date , int TemperatureC , string ? Summary )
289310{
290311 public int TemperatureF => 32 + ( int ) ( TemperatureC / 0.5556 ) ;
291312}
292313
293314// Request DTOs
294315public record CreateConversationRequest ( string Name ) ;
295316public record SendMessageRequest ( string Message ) ;
317+
318+ // Make Program class accessible for testing
319+ public partial class Program { }
0 commit comments