10

For a project, I need to implement SSE (Server Sent Events) in a C# Application. Although this may sound easy, I've got no clue how to solve this.

As I'm new to C# (though, not new to programming in general) I took an excursion to Google and tried to look for some sample code. From what I've seen so far, I could learn to build a HTTP Server with C# or consume server sent events. But I found nothing about sending SSEs.

What I'm trying to get my head around: how can I keep sending updated data over the incoming request? Normally, you get a request, do your thing and reply. Done, connection closed. But in this case I want to kind of "stick" to the response-stream and send new data through, each time an event in my application fires.

The problem, for me, lies in this event-based approach: it's not some intervall-based polling and updating. It's rather that the app goes like "Hey, something happend. I really should tell you about it!"

TL;DR: how can I hold on to that response-stream and send updates - not based on loops or timers, but each time certain events fire?

Also, before I forget: I know, there are libraries out there doing just that. But from what I've seen so far (and from what I've understood; correct me if I'm wrong) those solutions depend on ASP.NET / MVC / you name it. And as I'm just writing a "plain" C# application, I don't think I meet these requirements.

mh166
  • 361
  • 1
  • 4
  • 13
  • No, there's libraries that don't require ASP.NET. Just research for them. Even if they have ASP.NET in the name, that might just be because they're run by the ASP.NET team and might be self-hostable. I wouldn't limit myself to SSE either, don't forget about technologies like web sockets. – mason Jun 30 '17 at 17:27
  • I don't doubt that. I'm just saying, that from _what I've seen so far_ I couldn't find any. For me, the results so far have always been about either SSE _clients_ or, if not, ASP.NET. As you probably can imagine, searching for a "server side event server library" is rather useless. But if you can provide me with a link to a library, I'm more than happy to take it. :) – mh166 Jun 30 '17 at 17:34
  • Out of curiosity, why don't you just build an ASP.Net application instead? It just adds a few extra DLL dependencies...seems like an arbitrary limitation. – Tim Copenhaver Jun 30 '17 at 17:35
  • @TimCopenhaver Then you tie yourself to IIS. – mason Jun 30 '17 at 17:35
  • Asking for library recommendations is off-topic for Stack Overflow. You're googling for too specific a thing. Google ".NET server push" and I'm sure you can come up with something. Like I said, don't limit yourself to SSE. – mason Jun 30 '17 at 17:36
  • @mason: I compared SSEs, WebSockets and even WebRTC before choosing. A great insight in why I made that choice can be found here: [WebSockets vs. Server-Sent events/EventSource](https://stackoverflow.com/a/5326159/3380671). Also, I'm not explicitly asking for a library. As you might have seen, I was asking about how to shove events happening in my application into the (still available) response stream. – mh166 Jun 30 '17 at 17:42
  • 1
    @mason not true. You can use ASP.Net without IIS - they are completely separate things, especially with .Net Core. – Tim Copenhaver Jun 30 '17 at 17:42
  • @TimCopenhaver: I'm writing a VSTO plugin for PowerPoint. So I assumed ASP.NET was not within the realms of possibilities. – mh166 Jun 30 '17 at 17:43
  • You want the server to run as a VSTO plugin, or the client? As long as the server is an external application it should be fine. – Tim Copenhaver Jun 30 '17 at 17:46
  • Valid question, let me clarify: the application I'm writing is a VSTO plugin. It should provide SSEs based on what happens withing Powerpoint (opening a PPT, starting the presentation, advancing slides). These SSEs will then be consumed by a custom Web GUI. – mh166 Jun 30 '17 at 17:48
  • @TimCopenhaver That would be ASP.NET Core, which is not the same thing as ASP.NET. Okay, sure you can use ASP.NET with Mono and Apache or others, but the majority of the time if you're using ASP.NET you've tied yourself to IIS. – mason Jun 30 '17 at 17:53
  • @mason Microsoft (and many forums) almost always advertise ASP.NET together with IIS, but in fact they're different things. Any web server that can send its requests to the ASP.NET engine is enough. IIS, Apache and [even a simple WinForms program](https://www.west-wind.com/presentations/aspnetruntime/aspnetruntime.aspx) can do it. – Alejandro Jun 30 '17 at 20:21
  • @Alejandro System.Web is the engine of ASP.NET. They're so intertwined that the ASP.NET platform is inseperable from System.Web. That's why ASP.NET Core was created. IIS and System.Web are not the same thing, but they're so intertwined that you basically have to re-implement all the bindings for it if you want to run ASP.NET anywhere but IIS (which is what Mono does, and Mono doesn't have nearly the traction of the .NET Framework). – mason Jun 30 '17 at 20:30
  • As per [this answer](https://stackoverflow.com/a/44855299/197591), there are good instructions [here](https://makolyte.com/event-driven-dotnet-how-to-consume-an-sse-endpoint-with-httpclient/) on consuming an SSE stream. – Neo Nov 01 '20 at 16:41

2 Answers2

20

As for a light-weight server I would go with an OWIN selfhost WebAPI (https://learn.microsoft.com/en-us/aspnet/web-api/overview/hosting-aspnet-web-api/use-owin-to-self-host-web-api).

A simple server-sent event server action would basically go like:

public class EventController : ApiController
  {
    public HttpResponseMessage GetEvents(CancellationToken clientDisconnectToken)
    {
      var response = Request.CreateResponse();
      response.Content = new PushStreamContent(async (stream, httpContent, transportContext) =>
      {
        using (var writer = new StreamWriter(stream))
        {
          using (var consumer = new BlockingCollection<string>())
          {
            var eventGeneratorTask = EventGeneratorAsync(consumer, clientDisconnectToken);
            foreach (var @event in consumer.GetConsumingEnumerable(clientDisconnectToken))
            {
              await writer.WriteLineAsync("data: " + @event);
              await writer.WriteLineAsync();
              await writer.FlushAsync();
            }
            await eventGeneratorTask;
          }
        }
      }, "text/event-stream");
      return response;
    }

    private async Task EventGeneratorAsync(BlockingCollection<string> producer, CancellationToken cancellationToken)
    {
      try
      {
        while (!cancellationToken.IsCancellationRequested)
        {
          producer.Add(DateTime.Now.ToString(), cancellationToken);
          await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
        }
      }
      finally
      {
        producer.CompleteAdding();
      }
    }
  }

The important part here is the PushStreamContent, which basically just sends the HTTP headers and then leaves the connection open to write the data when it is available.

In my example the events are generated in an extra-task which is given a producer-consumer collection and adds the events (here the current time every second) if they are available to the collection. Whenever a new event arrives GetConsumingEnumerable is automatically notified. The new event is then written in the proper server-sent event format to the stream and flushed. In practice you would need to send some pseudo-ping events every minute or so, as streams which are left open for a long time without data being sent over them would be closed by the OS/framework.

The sample client code to test this would go like:

Write the following code in async method.

using (var client = new HttpClient())
{
  using (var stream = await client.GetStreamAsync("http://localhost:9000/api/event"))
  {
    using (var reader = new StreamReader(stream))
    {
      while (true)
      {
        Console.WriteLine(reader.ReadLine());
      }
    }
  }
}
Amey Vartak
  • 309
  • 2
  • 5
ckuri
  • 3,784
  • 2
  • 15
  • 17
  • Why do this instead of SignalR? – mason Jul 03 '17 at 13:49
  • @ckuri yours looks like a perfect answer, I want to consume server sent events in a desktop C# WPF app. can you please tell me how the code for the client side or listener should be implemented? thanks, – digitai Feb 03 '18 at 22:38
  • @datelligence I have already given the network part of the client listener. Instead of the Console.WriteLine you would of course need to interpret the SSE packets, which however is quite simple. You just call reader.ReadLine in a loop. An empty line is the packet end marker. Before that there are one or multiple lines of the format "key: value" which you need to collect and then dispatch/evaluate when you hit an empty line. For details see https://www.w3.org/TR/eventsource/#event-stream-interpretation (ignore the MessageEvent interface stuff which is just relevant for browser implementations). – ckuri Feb 03 '18 at 23:05
  • 3
    @mason I had to do something similar as the client implemented Server Sent Events EventSource (https://html.spec.whatwg.org/multipage/server-sent-events.html) and I had no control over the client implementation. Otherwise, SignalR is a great tool to have for streaming scenarios. – Sudhanshu Mishra Jun 07 '18 at 03:59
5

This sounds like a good fit for SignalR. Note SignalR is part of the ASP.NET family, however this does NOT require the ASP.NET framework (System.Web), or IIS, as mentioned in comments.

To clarify, SignalR is part of ASP.NET. According to their site:

ASP.NET is an open source web framework for building modern web apps and services with .NET. ASP.NET creates websites based on HTML5, CSS, and JavaScript that are simple, fast, and can scale to millions of users.

SignalR has no hard dependency on System.Web or on IIS.

You can self-host your ASP.Net application (see https://learn.microsoft.com/en-us/aspnet/signalr/overview/deployment/tutorial-signalr-self-host). If you use .net core, it is actually self-hosted by default and runs as a normal console application.

mason
  • 31,774
  • 10
  • 77
  • 121
Tim Copenhaver
  • 3,282
  • 13
  • 18
  • I already stumbled across this library, but the ASP.NET part held me back. Judging from the docs you linked to, I'm wondering: there seems to be no console application-specific part, right? So I could just include this in my VSTO plugin as well? Or would this lead to problems in terms of threading (i.e. the server blocking the further codepath)? – mh166 Jun 30 '17 at 17:54
  • um...technically SignalR is PART of asp.net. Seems a little misleading to say it doesn't require it - that's similar to saying Web Forms doesn't require Asp.Net. Maybe we just define "Asp.net" a little differently? – Tim Copenhaver Jun 30 '17 at 18:09
  • 1
    SignalR might be under the ASP.NET brand name but when someone says "use ASP.NET" or "require ASP.NET" that generally means `System.Web` which again, is tied to IIS. Web Forms absolutely requires ASP.NET, `System.Web` and IIS. If we were talking about Web API, that does not have a hard dependency on ASP.NET, System.Web, or IIS. Web API and SignalR are on the same boat when it comes to dependency on ASP.NET. I'd just like you to fix your first paragraph so that we're not giving out incorrect or misleading information. – mason Jun 30 '17 at 18:14
  • I never said it depends on "System.Web". ASP.Net Core actually has no dependency on System.Web at all. I think you're reading in an interpretation that's not there. I will update with a clarifying note, but I don't believe the information is wrong. – Tim Copenhaver Jun 30 '17 at 18:16
  • 1
    I see you've edited your post, and I made it a little clearer too. Feel free to reverse mine if you see fit, but I think it improves the understanding. – mason Jun 30 '17 at 19:29