3

So I have the following architecture: an Angular SPA (single page application) performs a call to a .NET Web API controller, which publishers a message to a Publisher EasyNetQ window service, which sends an asynchronous request to a second EasyNetQ window service called Subscriber, which calls a backend class to generate a SSRS report, and finally sends an asynchronous response back to Publisher. Here is a diagram of the architecture in question:

Report Generation Architecture

So far so good, Subscriber receives the response, generates the report(s), and sends a message(s) back to Publisher. Here is how the Web API controller sends the report data message to the Publisher:

private IHttpActionResult generateReports(int[] incidentIds)
{
    try
    {
        var incident = this.incidentRepository.GetIncident(incidentIds[0]);
        var client = this.clientService.GetClient(incident.ClientId_Fk);

        using (var messageBus = RabbitHutch.CreateBus("host=localhost"))
        {
            // Loop through all incidents
            foreach (var incidentId in incidentIds)
            {

                foreach (var format in this.formats)
                {
                    Dictionary<Dictionary<int, Client>, SSRSReportFormat> reportData = new Dictionary
                        <Dictionary<int, Client>, SSRSReportFormat>()
                        {
                            {new Dictionary<int, Client>() {{incidentId, client}}, format}
                        };

                    messageBus.Publish(new ReportData
                    {
                        clientId = client.Id,
                        incidentId = incidentId,
                        clientName = client.Name,
                        clientNetworkPath = client.NetworkPath,
                        formatDescription = EnumUtils.GetDescription(format),
                        reportFormat = format.ToString()
                    });                            
                }
            }
        }

        return this.Ok();
    }
    catch (Exception ex)
    {
        return this.InternalServerError(ex);
    }
}

This is how I send a request from Publisher:

public partial class CreateRequestService : ServiceBase
{
    private IBus bus = null;

    public CreateRequestService()
    {
        this.InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        this.bus = RabbitHutch.CreateBus("host=localhost");

        this.bus.Subscribe<ReportData>("reportHandling", this.HandleReportData);
    }

    protected override void OnStop()
    {
        this.bus.Dispose();
    }

    private void HandleReportData(ReportData reportData)
    {
        int clientId = reportData.clientId;
        int incidentId = reportData.incidentId;
        string clientName = reportData.clientName;
        string clientNetworkPath = reportData.clientNetworkPath;
        string formatDescription = reportData.formatDescription;
        string reportFormat = reportData.reportFormat;

        var task = this.bus.RequestAsync<ReportData, TestResponse>(reportData);
        task.ContinueWith(response => Library.WriteErrorLog("Got response: '{0}'" + response.Result.Response, "PublisherLogFile"));

    }
}

And finally, the code for generating reports and sending responses back from Subscriber:

public partial class RequestResponderService : ServiceBase
{
    private IBus bus = null;

    public RequestResponderService()
    {
        this.InitializeComponent();
    }

    /// <summary>
    /// Initialize the Bus to receive and respond to messages through
    /// </summary>
    /// <param name="args"></param>
    protected override void OnStart(string[] args)
    {
        // Create a group of worker objects
        var workers = new BlockingCollection<MyWorker>();
        for (int i = 0; i < 10; i++)
        {
            workers.Add(new MyWorker());
        }

        workers.CompleteAdding();

        // Initialize the bus
        this.bus = RabbitHutch.CreateBus("host=localhost");

        // Respond to the request asynchronously
        this.bus.RespondAsync<ReportData, TestResponse>(request =>
            (Task<TestResponse>) Task.Factory.StartNew(() =>
            {
                var worker = workers.Take();

                try
                {
                    return worker.Execute(request);
                }
                catch (Exception)
                {

                    throw;
                }
                finally
                {
                }
            }));
    }

    protected override void OnStop()
    {
        this.bus.Dispose();
    }        
}

class MyWorker
{
    public TestResponse Execute(ReportData request)
    {
        int clientId = request.clientId;
        int incidentId = request.incidentId;
        string clientName = request.clientName;
        string clientNetworkPath = request.clientNetworkPath;
        string formatDescription = request.formatDescription;
        string reportFormat = request.reportFormat;

        ReportQuery reportQuery = new ReportQuery();
        reportQuery.Get(incidentId, reportFormat, formatDescription, clientName, clientNetworkPath, clientId);

        return new TestResponse { Response = " ***** Report generated for client: " + clientName + ", incident Id: " + incidentId + ", and format: " + reportFormat + " ***** " };
    }
}

While this all works, I also need some way to notify the Angular SPA that a report has been generated so I can give the user an appropriate feedback. This is where I am a bit lost though. Can EasyNetQ interact with Angular code? Also, once I receive a response in Publisher, i can probably call some method in my Web API controller, but still the problem of alerting the Angular code remains. Any ideas?

AGB
  • 2,230
  • 1
  • 14
  • 21
lukegf
  • 2,147
  • 3
  • 26
  • 39
  • 2
    There are just two options. First one: client (anjular app) should poll from time to time about report status, while your service either stores report generation progress in persistent storage (database) or publishes it via rabbit message back to your web api which stores in memory. Then when client polls - web api reports status. Second: you implement persistent channel (web sockets) from angular app to web api - then you notify client without polling (again first sending message to web api app, which in turn sends this message via websocket to anjular). – Evk May 23 '16 at 19:54
  • 3
    Please take a look at [SignalR](http://signalr.net/): as Evk said your only option for a *near real-time* notification system is to use Web Sockets or long polling. SignalR does exactly this. – Federico Dipuma May 23 '16 at 21:50
  • Great! Thanks to both of you guys. Looking into SignalR now. One (or both) of you should post an answer so I can mark it as the accepted one. – lukegf May 23 '16 at 22:03

1 Answers1

3

First note that you have to store information about report status somewhere. You can store it in two places:

  • Persistent storage (database, redis cache, whatever).
  • In memory of web api service (because it's this service which client is communicating to).

When you decided where to store - there are again two options of how to pass this information to a client:

  • Client (Angular) can poll from time to time (note that it is not what is called "long polling"). If you store your status in database - you can just look it up there in this case.

  • There is a persistent connection between Angular and your api (web sockets, long polling also falls here). In this case you better store your status in memory of web api (by passing rabbit message with report status from your service to web api, which then stores that in memory and\or directly forwards that to Angular via persistent connection).

If you don't expect clients on different platforms (iOS, pure linux etc) - SignlarR can work fine. It will fallback from websockets to long polling to regular polling depending on user browser's capabilities.

Evk
  • 98,527
  • 8
  • 141
  • 191