Visualizing Elasticsearch Watcher events using ASP.NET Core 1.0, SignalR and Angular

This article shows how to display Elasticsearch Watcher events in an Angular UI using SignalR. The Elasticsearch Watcher events are sent to a MVC 6 controller in an ASP.NET Core 1.0 application. The action method in the controller handles the watcher events and sends the data to the Angular UI using SignalR and the SignalR client library angular-signalr-hub.

Code: https://github.com/damienbod/AspNet5Watcher

2015.09.20: Updated to ASP.NET Core 1.0 beta 7
2015.10.20: Updated to ASP.NET Core 1.0 beta 8
2015.11.18: Updated to ASP.NET Core 1.0 rc1

Setting up SignalR in a ASP.NET 5 MVC 6 application

To use SignalR in an ASP.NET 5 project, it must be added to the project in the project.json file in the dependencies. The following configuration is required:

"Microsoft.AspNet.SignalR.Server": "3.0.0-*"

Here’s the configuration for the ASP.NET 5 project using the beta5 version.

    "dependencies": {
        "Microsoft.AspNet.Diagnostics": "1.0.0-rc1-final",
        "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final",
        "Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
        "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-rc1-final",
        "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
        "Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final",
        "Microsoft.Extensions.Logging": "1.0.0-rc1-final",
        "Microsoft.Extensions.Logging.Console": "1.0.0-rc1-final",
        "Microsoft.Extensions.Logging.Debug": "1.0.0-rc1-final",
        "NEST": "1.7.0",
        "Microsoft.Net.Http": "2.2.29",
        "NEST.Watcher": "1.0.0-beta2",
        "Microsoft.AspNet.SignalR.Server": "3.0.0-rc1-15919"
    },

Now SignalR can be added to the Startup.cs class. This just requires the AddSignalR and the UseSignalR methods in the ConfigureServices and Configure methods respectively.

public IConfigurationRoot Configuration { get; set; }

public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
             var builder = new ConfigurationBuilder()
                .SetBasePath(appEnv.ApplicationBasePath)
                .AddJsonFile("config.json");
            Configuration = builder.Build();
}

public void ConfigureServices(IServiceCollection services)
{
            services.Configure<ApplicationConfiguration>(Configuration.GetSection("ApplicationConfiguration"));

            services.AddMvc();
            services.AddSignalR(options =>
            {
                options.Hubs.EnableDetailedErrors = true;
            });

            services.AddScoped<SearchRepository, SearchRepository>();
            services.AddInstance(_configuration);
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
	loggerFactory.MinimumLevel = LogLevel.Information;
	loggerFactory.AddConsole();
	loggerFactory.AddDebug();

	app.UseIISPlatformHandler();

	app.UseStaticFiles();
	app.UseMvc(routes =>
	{
		routes.MapRoute(
			name: "default",
			template: "{controller=Home}/{action=Index}/{id?}");
	});
	app.UseSignalR();

Now that SignalR is up and running on the server, a Hub can be created and used. The Hub in this demo is a simple class which inherits from the Hub class.

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;

namespace AspNet5Watcher.Hubs
{
    [HubName("alarms")]
    public class AlarmsHub  : Hub
    {
    }

}

Using the SignalR Hub in a MVC 6 Controller

In the MVC 6 controller, WatcherEventsController which is used to handle the Elasticsearch Watcher events, the AlarmsHub is used to send the events to all the SignalR clients. The Hub is added to the controller using the FromServices attribute.

private IConnectionManager _connectionManager;
private IHubContext _alarmsHub;

[FromServices]
public IConnectionManager ConnectionManager
{
	get
	{
		return _connectionManager;
	}
	set
	{
		_connectionManager = value;
		_alarmsHub = _connectionManager.GetHubContext<AlarmsHub>();
	}
}

Then the client can be used in the WatcherEvents/CriticalAlarm method. When Elasticsearch sends new data, the _alarmsHub is used to send the total count of critical data to the SignalR clients and also the last ten critical alarms.

//POST http://localhost:5000/api/WatcherEvents/CriticalAlarm HTTP/1.1
[HttpPost]
[Route("CriticalAlarm")]
public IActionResult Post([FromBody]int countNewCriticalAlarms)
{  
	if (countNewCriticalAlarms != _criticalAlarmsCount )
	{
		var newCriticalAlarmsCount = countNewCriticalAlarms - _criticalAlarmsCount;
		_criticalAlarmsCount = countNewCriticalAlarms;

		_alarmsHub.Clients.All.sendtotalalarmscount(countNewCriticalAlarms);

		_alarmsHub.Clients.All.sendlasttencriticalalarms(
                       _searchRepository.SearchForLastTenCriticalAlarms());
	}

	return new HttpStatusCodeResult(200);
}

Using SignalR with a Angular JS client

The SignalR events are implemented in Angular using angular-signalr-hub. This is not necessary but I like its simplicity when using it.

The SignalR packages are added using bower. These are added to the bower.json file in the dependencies property. This will download and install the default bower settings. If you required only the min files, then you need to customize the exportsOverride for the packages.

"angular-signalr-hub": "1.5.0",
"signalr": "2.2.0"

Then the javascript files are added to the index.html file before the app.js file.

<script src="lib/jquery/js/jquery.js"></script>
<script src="lib/signalr/jquery.signalr.js"></script>
<script src="lib/angular-signalr-hub/signalr-hub.js"></script>

<script type="text/javascript" src="app.js"></script>

Now the module can be added to your main angular module and also the new alarmsDisplay route can be configured in the app.js file.

(function () {
    var mainApp = angular.module("mainApp", ["ui.router", "SignalR"]);

	mainApp.config(["$stateProvider", "$urlRouterProvider",
		function ($stateProvider, $urlRouterProvider) {
		    $urlRouterProvider.otherwise("/home/createAlarm");

            	$stateProvider
                    .state("home", { abstract: true, url: "/home", templateUrl: "/templates/home.html" })
                        .state("createAlarm", {
                            parent: "home", url: "/createAlarm", templateUrl: "/templates/createAlarm.html", controller: "alarmsController",
                        	
                        })
                    .state("alarms", {
                        url: "/alarms", templateUrl: "/templates/alarms.html", controller: "alarmsDisplayController",

                    })
        }
	]
    );
})();

The application uses an angular controller to add the SignalR client to the UI. This is implemented in the AlarmsDisplayController controller. The SignalR client implements two listeners for the SignalR methods. When the listeners receive an event, the data is added to the root scope of the application.

(function () {
	'use strict';

	var module = angular.module("mainApp");

	var AlarmsDisplayController = (function () {
	    function AlarmsDisplayController($scope, log, alarmsService, $rootScope, Hub, $timeout) {
	        $scope.Vm = this;

	        this.$scope = $scope;
	        this.alarmsService = alarmsService;
	        this.log = log;
	        this.$rootScope = $rootScope;
	        this.Hub = Hub;
	        this.$timeout = $timeout;

	        this.log.info("AlarmsDisplayController called");
	        this.Name = "Displaying Watcher alarms";

	        var hub = new Hub('alarms', {

	            //client side methods
	            listeners: {
	                'sendtotalalarmscount': function (count) {
	                    $rootScope.AlarmsCount = count;
	                    $rootScope.$apply();
	                    console.log("Recieved SendTotalAlarmsCount:" + count);
	                },
	                'sendlasttencriticalalarms': function (lasttenalarms) {
	                    $rootScope.LastTenCriticalAlarms = lasttenalarms;
	                    $rootScope.$apply();
	                    console.log(lasttenalarms);
	                },
	            },

	            //handle connection error
	            errorHandler: function (error) {
	                console.error(error);
	            },

	            stateChanged: function (state) {
	                switch (state.newState) {
	                    case $.signalR.connectionState.connecting:
	                        console.log("signalR.connectionState.connecting" + state.newState)
	                        break;
	                    case $.signalR.connectionState.connected:
	                        console.log("signalR.connectionState.connected" + state.newState)
	                        break;
	                    case $.signalR.connectionState.reconnecting:
	                        console.log("signalR.connectionState.reconnecting" + state.newState)
	                        break;
	                    case $.signalR.connectionState.disconnected:
	                        console.log("signalR.connectionState.disconnected" + state.newState)
	                        break;
	                }
	            }
	        });
	    }

	    return AlarmsDisplayController;
	})();

    // this code can be used with uglify
	module.controller("alarmsDisplayController",
		[
			"$scope",
			"$log",
			"alarmsService",
            '$rootScope',
            'Hub',
            '$timeout',
			AlarmsDisplayController
		]
	);
})();

Once the controller is completed, the data can be used in the alarms display HTML file in the templates folder in the wwwroot. This HTML will be updated every time a SignalR event is sent to all the listening clients.

<div class="container">
    <div class="row">
        <div class="col-sm-9">
            <h4>{{Vm.Name}}, Total critical alarms:</h4>
        </div>
        <div class="col-sm-3">
            <div class="alarmsDisplay round-corners-5px">
                <p>{{$root.AlarmsCount}}</p>
            </div>
        </div>
    </div>
</div>

<div class="container">
    <h4>Last ten critical alarms</h4>
    <table class="table">
        <thead>
            <tr>
                <th>Message</th>
                <th>Created</th>
                <th>Id</th>
            </tr>
        </thead>
        <tbody>
            <tr data-ng-repeat="alarm in $root.LastTenCriticalAlarms">
                <td>{{alarm.Message}}</td>
                <td>{{alarm.Created}}</td>
                <td>{{alarm.Id}}</td>
            </tr>
        </tbody>
    </table>
</div>


Displaying the Elasticsearch Watcher Data

Every time a new critical alarm is added or created, the alarm is saved to Elasticsearch and within 10s, Elasticsearch Watcher notices this and calls the MVC 6 action method. The action method then uses SignalR and the UI is updated in real time.

SignalRWatcher_01

The HTML page is updated is real time without a refresh using SignalR.

SignalRWatcher_02

Conclusion

Using Elasticsearch Watcher together with SignalR and ASP.NET 5, some really cool features can be implemented which could be very useful when solving a business problem for a particular use case.

Note

At present the application uses HttpClient directly with Elasticsearch to create and delete the Elasticsearch Watcher, with a not so nice magic string. This will be updated as soon as the next NEST Elasticsearch Watcher is released and supports the watcher features used in this application.

Links:

https://github.com/JustMaier/angular-signalr-hub

https://github.com/aspnet/musicstore

https://github.com/SignalR/SignalR

https://www.elastic.co/guide/en/watcher/current/index.html

http://amsterdam.luminis.eu/2015/06/23/first-steps-with-elastic-watcher/

https://github.com/elastic/elasticsearch-watcher-net

https://www.elastic.co/guide/en/watcher/current/actions.html#actions-index

7 comments

  1. […] Visualizing Elasticsearch Watcher events using ASP.NET 5, SignalR and Angular – damienbod […]

  2. […] Damien Bod shared a post about Elastic Search watcher events using ASP.NET 5, SIgnalR and Angular […]

  3. I can’t seem to find the package “Microsoft.AspNet.SignalR.Server”: “3.0.0-beta7” , the only one returned to me is the “3.0.0-beta5” which doesn’t seem to work in my code.
    Any idea where to find beta7?? Thanks

    1. Hi Marcel

      I use 3.0.0-beta8-15595, sorry I’ll update the packages, thanks for the feedback, beta8 should be released today, I’ll update the whole project then.

      Greetings Damien

  4. Christophe · · Reply

    Hi Damien, it looks like signalR is on hold and is no longer working on ASP 5: https://github.com/aspnet/SignalR-Server/issues/121
    Can you confirm that SignalR 3.0.0-beta8-15595 properly works on ASP 5 RC1 waiting for the latest and greatest SignalR 3 ? Else would it still be possible using SignalR 2 with ASP 5 RC1 ?
    Thanks

    1. Hi Christophe

      It is possible but the code is not tested or validated by Microsoft. You need to test and validate it yourself, or edit the code… Here’s an example with ASP.NET 5 RC1 from Fabian

      https://github.com/FabianGosebrink/ASPNET-Core-Angular2-SignalR-Typescript

      I would not use any ASP.NET Core until RC2 is released.

      Greetings Damien

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.