using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; using FastEndpoints; using Microsoft.AspNetCore.Http; using Newtonsoft.Json; namespace GB5Shared.FastEndPoint { public abstract class StreamingEndPoint : Endpoint where TRequest : notnull { protected abstract IAsyncEnumerable GetStreamDataAsync(TRequest req, CancellationToken ct); public override async Task HandleAsync(TRequest req, CancellationToken ct) { HttpContext.Response.ContentType = "application/x-ndjson"; int count = 0; try { await foreach (var item in GetStreamDataAsync(req, ct)) { ct.ThrowIfCancellationRequested(); await WriteJsonAsync(new StandardStreamDto("data", item), ct); count++; } await WriteJsonAsync(new StandardStreamDto("footer", null, "Stream completed", count), ct); } catch (OperationCanceledException) { await WriteJsonAsync(new StandardStreamDto("error", null, "Stream cancelled"), ct); } catch (Exception ex) { await WriteJsonAsync(new StandardStreamDto("error", null, ex.Message), ct); } } protected virtual async Task WriteJsonAsync(T obj, CancellationToken ct) { try { // Serialize to NDJSON (newline-delimited JSON) string json = JsonConvert.SerializeObject(obj); byte[] jsonBytes = Encoding.UTF8.GetBytes(json + "\n"); await HttpContext.Response.Body.WriteAsync(jsonBytes, 0, jsonBytes.Length, ct); await HttpContext.Response.Body.FlushAsync(ct); } catch (Exception ex) { // Handle write failure gracefully var error = new StandardStreamDto("error", null, $"WriteJsonAsync failed: {ex.Message}"); string errorJson = JsonConvert.SerializeObject(error); byte[] errorBytes = Encoding.UTF8.GetBytes(errorJson + "\n"); await HttpContext.Response.Body.WriteAsync(errorBytes, 0, errorBytes.Length, ct); await HttpContext.Response.Body.FlushAsync(ct); } } public class StandardStreamDto { public string Type { get; set; } // "data", "footer", "error" public T Data { get; set; } public string Message { get; set; } public int? Count { get; set; } public StandardStreamDto(string type, T data, string message = null, int? count = null) { Type = type; Data = data; Message = message; Count = count; } } } }