skip to Main Content

I have an API that allows a user to run a back end process and get the response as a series of text lines. The controller is calling this:

public async IEnumerable<string> Execute(Info[] infoArray)
{
    foreach(var info in infoArray)
    {
        try
        {
            var result = await ProcessInfo(info);
            yield return result;
        }
        catch(ProcessException e)
        {
            yield return e.Message;
        }
}

So ProcessInfo can take some time to execute, but I want the UI to have a running update of the results.

So, basically I want the controller to return a stream, and send one string at a time as it evaluates the IEnumerable, and then on the Typescript side (I am currently using Axios.get requests) get it one string at a time so that I can update a GUI with a running log.

I spent several hours digging around on Google and Stackoverflow and couldn’t find a good example to follow. What is the best approach?

2

Answers


  1. One possible approach is to use the IAsyncEnumerable feature that is supported in ASP.NET Core 6. This feature allows you to stream JSON responses from the controller action to the response formatter, without buffering the data in memory. You can also use EF Core to query the data asynchronously and return an IAsyncEnumerable from your ProcessInfo method.

    To use this feature, you need to change your controller action to return an IAsyncEnumerable instead of an IEnumerable. For example:

    public async IAsyncEnumerable<string> Execute(Info[] infoArray)
    {
        foreach(var info in infoArray)
        {
            try
            {
                var result = await ProcessInfo(info);
                yield return result;
            }
            catch(ProcessException e)
            {
                yield return e.Message;
            }
    }
    

    On the Typescript side, you need to use a library that can handle streaming JSON responses, such as Oboe.js.
    This library allows you to parse the JSON response as a stream and handle each item as it arrives.

    Login or Signup to reply.
  2. Your controller should return an IAsyncEnumerable<string> or a ContentResult with a specific media type (text/event-stream).

    You may need to disable request buffering and enable streaming to allow for a continuous flow of data like that

    [HttpGet]
    [Route("stream")]
      public async IAsyncEnumerable<string> StreamData()
     {
       foreach (var info in infoArray)
      {
        try
        {
            var result = await ProcessInfo(info);
            yield return $"data: {result}nn";
            await Task.Delay(1000); // Simulate processing time
        }
        catch (ProcessException e)
        {
            yield return $"data: {e.Message}nn";
        }
      }
    }
    

    In Angular service or component, create an EventSource instance that connects to the streaming endpoint of API.

    import { Injectable } from '@angular/core';
    
    @Injectable({
     providedIn: 'root'
    })
    export class StreamService {
     private eventSource: EventSource;
    
     startStream(url: string): Observable<string> {
      return new Observable(observer => {
       this.eventSource = new EventSource(url);
    
       this.eventSource.onmessage = event => {
         observer.next(event.data);
       };
    
       this.eventSource.onerror = error => {
         observer.error(error);
         this.eventSource.close();
       };
    
        return () => {
          this.eventSource.close();
        };
      });
     }
    }
    

    Inject the service into component and subscribe to the startStream method.
    Update your component’s view as new data arrives

    // In your component
     this.streamService.startStream('api/stream').subscribe(
       data => {
       // Update UI here with the incoming data
       },
     error => {
      // Handle errors
      }
    );
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search