Server-Sent Events en C#: Web reactivo sin WebSocket

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

¿Por qué su sitio web todavía no "habla" con el usuario en tiempo real?

\n\n

Imagine: abre un terminal de bolsa y el tipo de cambio del dólar se actualiza solo. O ingresa al panel de administración y ve cómo, ante sus ojos, aumenta la cantidad de usuarios registrados. O un chat donde los mensajes llegan sin constantes "actualizaciones" de la página.

\n\n

Antes, para esto había que instalar WebSocket (complejo, costoso, no siempre necesario) o usar long polling (un parche que agota la batería del teléfono). Pero hay un tercer camino: Server-Sent Events en C#.

\n\n

Hoy analizaremos la tecnología que permite al servidor "llamar" al cliente de forma autónoma y transmitir datos. Sin código innecesario, sin toneladas de JavaScript, sin dolores de cabeza con servidores proxy. Solo C#, solo pura esencia.

\n\n

¿Qué son los Server-Sent Events (SSE) y por qué no son un "protocolo para pobres"?

\n\n

Server-Sent Events (o SSE) es un estándar HTML5 que permite al servidor enviar datos en streaming al cliente a través de una conexión HTTP normal. El cliente se suscribe a los eventos y el servidor simplemente escribe en Response.Body. Eso es todo.

\n\n

La principal diferencia con WebSocket: SSE es un canal unidireccional (servidor → cliente). ¿No necesita enviar datos de vuelta? Perfecto, SSE es su elección. Además:

\n\n
    \n
  • Funciona sobre HTTP/1.1 y HTTP/2 estándar
  • \n
  • Se reconecta automáticamente en caso de interrupción (integrado en el EventSource del navegador)
  • \n
  • No requiere bibliotecas adicionales en el cliente (excepto JavaScript estándar)
  • \n
  • Atraviesa cualquier proxy y balanceador de carga (a diferencia de WebSocket)
  • \n
\n\n

En la práctica, SSE C# se utiliza para:

\n
    \n
  • Canales de notificaciones (notificaciones push directamente al navegador)
  • \n
  • Monitoreo de servidores (paneles con métricas en vivo)
  • \n
  • Transmisión de registros (por ejemplo, tail -f en el navegador)
  • \n
  • Streaming de generaciones de IA (entrega gradual de respuestas de modelos tipo ChatGPT)
  • \n
\n\n

Cómo funciona SSE: el protocolo en una línea

\n\n

Todo lo genial es simple. El servidor envía al cliente un flujo de texto con un formato especial:

\n\n
data: ¡Hola, mundo!\\n\\n\ndata: {"message": "JSON también funciona"}\\n\\n\nevent: customEvent\\n\ndata: Evento con nombre\\n\\n
\n\n

Cada mensaje comienza con data:, termina con dos saltos de línea. Se puede especificar event: para un tipo de evento personalizado, id: para un identificador y retry: para el tiempo de espera de reconexión.

\n\n

El cliente en JavaScript se suscribe así:

\n\n
const source = new EventSource("/api/stream");\nsource.onmessage = (event) => {\n    console.log("Recibido:", event.data);\n};\nsource.addEventListener("customEvent", (event) => {\n    console.log("Evento personalizado:", event.data);\n});
\n\n

Y esto funciona en todos los navegadores modernos. Sin paquetes npm, sin polyfills de WebSocket.

\n\n

Server-Sent Events en C#: Implementación en ASP.NET Core

\n\n

Finalmente, escribamos el código. La forma más sencilla de organizar SSE en C# es un endpoint en ASP.NET Core que no finaliza la respuesta, sino que escribe en ella por partes.

\n\n

Endpoint SSE básico

\n\n
using Microsoft.AspNetCore.Mvc;\n\n[ApiController]\n[Route("api")]\npublic class StreamController : ControllerBase\n{\n    [HttpGet("stream")]\n    public async Task Get(CancellationToken cancellationToken)\n    {\n        Response.Headers.Append("Content-Type", "text/event-stream");\n        Response.Headers.Append("Cache-Control", "no-cache");\n        Response.Headers.Append("Connection", "keep-alive");\n\n        for (int i = 0; i < 10; i++)\n        {\n            if (cancellationToken.IsCancellationRequested)\n                break;\n\n            // Formato SSE: data: ... \\n\\n\n            await Response.WriteAsync($"data: Mensaje #{i}\\n\\n",                

Blogs

Book Recommendations