An ASP.NET Core 1.0 AngularJS application with Elasticsearch, NEST and Watcher

This article shows how to create an Angular application using ASP.NET Core 1.0 which can create alarm documents in Elasticsearch using NEST. The repository layer uses the new configuration from the beta 6 version and also the built in DI from the ASP.NET Core 1.0 framework. The post is part 1 of a three part series. In the following posts, events will be defined in Elasticsearch which will then be displayed in the UI using Angular.

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

Updating to the latest dnvm, dnu, dnx

Before creating the ASP.NET 5 application, we want to update to the latest dnx version, at present beta 7. Usually, you would update to the latest stable version, leaving out the -u option. Use the -u for the latest version.

To do this, run the following commands in the console with administrator rights.

dnvm upgrade

dnvm upgrade –runtime CoreCLR

Or for the latest stable version:

dnvm upgrade 

dnvm upgrade –runtime CoreCLR

Now check which version is your default:

dnvm list

Here’s a good blog explaining dnvm, dnu and dnx

Project Settings

The default Web API vNext template is used to create the project. Then the project.json file can be edited.

The latest packages are added in the dependencies in the project.json file. If these do not appear, you need to define the MyGet feed in the NuGet package settings. Only dnx451 is targeted in this application. This is because NEST and NEST Watcher are .NET 4.5 assemblies which cannot be used in core. I expect this to change once ASP.NET 5 is released. This is understandable, as it is very frustrating and complicated trying to update an existing .NET package to the new dnx core runtime. Many existing NuGet packages and libraries will struggle with this, unless Microsoft provide better support. I see a lot of headaches and long nights here.

{
    "webroot": "wwwroot",
    "version": "1.0.0-*",

    "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"
    },

    "commands": {
        "web": "Microsoft.AspNet.Server.Kestrel --server.urls http://localhost:17202"
    },

    "frameworks": {
        "dnx451": { }
    },

    "exclude": [
        "wwwroot",
        "node_modules",
        "bower_components"
    ],
    "publishExclude": [
        "node_modules",
        "bower_components",
        "**.xproj",
        "**.user",
        "**.vspscc"
    ]
}

ASP.NET 5 DI

Now that the basic project, is setup, the default DI can be used. This is configured in the Startup.cs file. In the ConfigureServices method, you can add scoped, transient, singleton or instance definitions for your dependencies. These can then be added via construction injection in the controllers or child classes.

Here’s an example of a scoped and an instance configuration.

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

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

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

A good post explaining ASP.NET 5 DI can be found here.

ASP.NET 5 Configuration

ASP.NET 5 also has a new configuration model. The configuration can be defined directly or from an ini, xml or json file. A json file is used for configuration in this project. The config.json file is defined as follows:

{
    "ApplicationConfiguration": {
        "ElasticsearchConnectionString": "http://localhost:9200"
    }
}

The the config class:

namespace AspNet5Watcher.Configurations
{
    public class ApplicationConfiguration
    {
        public string ElasticsearchConnectionString { get; set; }
    }
}

This is then used in the Startup.cs file and added as a instance type to the services. This has changed in beta6 version compare to beta 4.

public IConfigurationRoot Configuration { get; set; }

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

The configuration reference is added as an instance in the services.

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

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

The configuration can be used via constructor injection in any class then.

private IOptions<ApplicationConfiguration> _optionsApplicationConfiguration;

        public SearchRepository(IOptions<ApplicationConfiguration> o)
        {
            _optionsApplicationConfiguration = o;
            var uri = new Uri(_optionsApplicationConfiguration.Value.ElasticsearchConnectionString);

Adding an Elasticsearch document using NEST

A SearchRepository class is used to define the DAL for Elasticsearch. This uses NEST from Elasticsearch to use the Elasticsearch HTTP API. Later NEST Watcher will be also added here. The class uses the AlarmMessage Dto for the alarms index and the alarm type. The last ten alarms and also last ten critical alarms can be queried using this repository. This will be improved in a later post.

using System;
using System.Collections.Generic;
using System.Linq;
using Nest;
using Microsoft.Extensions.Configuration;

namespace AspNet5Watcher.SearchEngine
{
    public class SearchRepository
    {
        private ElasticClient client;
        private const string INDEX_ALARMMESSAGE = "alarms";
        private const string TYPE_ALARMMESSAGE = "alarm";

        private IOptions<ApplicationConfiguration> _optionsApplicationConfiguration;

        public SearchRepository(IOptions<ApplicationConfiguration> o)
        {
            _optionsApplicationConfiguration = o;
            var uri = new Uri(_optionsApplicationConfiguration.Value.ElasticsearchConnectionString);
            var settings = new ConnectionSettings( uri,  defaultIndex: "coolsearchengine");
            settings.MapDefaultTypeIndices(d => d.Add(typeof(AlarmMessage), INDEX_ALARMMESSAGE));
            settings.MapDefaultTypeNames(d => d.Add(typeof(AlarmMessage), TYPE_ALARMMESSAGE));

            client = new ElasticClient(settings);
        }

        public void AddDocument(AlarmMessage alarm)
        {
            client.Index(alarm, i => i
                .Index(INDEX_ALARMMESSAGE)
                .Type(TYPE_ALARMMESSAGE)
                .Refresh()
            );
        }

        public List<AlarmMessage> SearchForLastTenCriticalAlarms()
        {
            var results = client.Search<AlarmMessage>(i => i.Query(q => q.Term(p => p.AlarmType, "critical")).SortDescending("created"));
            return results.Documents.ToList();
        }

        public IEnumerable<AlarmMessage> SearchForLastTenAlarms()
        {
            var results = client.Search<AlarmMessage>(i => i.Query(q => q.MatchAll()).SortDescending("created"));
            return results.Documents.ToList();
        }
    }
}

Now that the repository is programmed, this can be used in the AlarmsController. The SearchRepository is added to the AlarmsController using the built in DI.

using System;
using System.Collections.Generic;
using AspNet5Watcher.SearchEngine;
using Microsoft.AspNet.Mvc;

namespace AspNet5Watcher.Controllers
{
    [Route("api/[controller]")]
    public class AlarmsController : Controller
    {
        private SearchRepository _searchRepository;

        public AlarmsController(SearchRepository searchRepository)
        {
            _searchRepository = searchRepository;
        }

        // GET: api/values
        [HttpGet]
        [Route("LastTenAlarms")]
        public IEnumerable<AlarmMessage> GetLast10Alarms()
        {
            return _searchRepository.SearchForLastTenAlarms();
        }

        [HttpGet]
        [Route("LastTenCritcalAlarms")]
        public IEnumerable<AlarmMessage> GetLastTenCritcalAlarms()
        {
            return _searchRepository.SearchForLastTenCriticalAlarms();
        }

        [HttpPost]
        [Route("AddAlarm")]
        public IActionResult Post([FromBody]AlarmMessage alarm)
        {
            if(alarm == null)
            {
                return new HttpStatusCodeResult(400);
            }

            alarm.Id = Guid.NewGuid();
            alarm.Created = DateTime.UtcNow;
            _searchRepository.AddDocument(alarm);
            return new HttpStatusCodeResult(200);

        }
    }
}

Angular Input Form

The Angular application for the client UI is setup using grunt and bower. You can check the gruntfile.json and bower.json to see how this is configured. Then a template is created for the Create Alarm form. This form can be found in the wwwroot/templates folder.

<div class="col-xs-5">
	<div class="panel panel-primary">
		<div class="panel-heading">
			<h3 class="panel-title">{{message}}</h3>
		</div>
		<div class="panel-body">
            <div class="col-xs-12">
                <form class="form-horizontal">

                    <div class="form-group">
                        <label class="control-label col-xs-3"></label>
                        <div class="col-xs-5">
                        </div>
                    </div>

                    <div class="form-group">
                        <label class="col-sm-3 control-label" for="username">Alarm Type</label>
                        <div class="col-sm-9">
                            <input id="alarmType" class="form-control" type="text" placeholder="info" ng-model="Vm.AlarmType" />
                        </div>
                    </div>

                    <!-- Password Group -->
                    <div class="form-group">
                        <label class="col-sm-3  control-label" for="password">Message</label>
                        <div class="col-sm-9">
                            <textarea id="text" class="form-control" type="text" placeholder="message" ng-model="Vm.Message"></textarea>
                        </div>
                    </div>

                    <div class="form-group">
                        <label class="control-label col-xs-3"></label>
                        <div class="col-xs-5">
                        </div>
                    </div>

                    <div class="form-group">
                        <label class="col-sm-3 control-label"></label>
                        <div class="col-xs-5">
                            <button class="btn btn-primary" type="submit" ng-click="Vm.CreateNewAlarm()">Create Alarm</button>
                        </div>
                    </div>

                </form>
            </div>
	</div>
</div>

The form uses the AlarmsController.js to handle the submit event.

(function () {
	'use strict';

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

	var AlarmsController = (function () {
	    function AlarmsController(scope, log, alarmsService) {
	        scope.Vm = this;

	        this.alarmsService = alarmsService;
	        this.log = log;

	        this.log.info("alarmsController called");
	        this.message = "Add an alarm to elasticsearch";

	        this.AlarmType = "info";
	        this.Message = "";
	    }

	    AlarmsController.prototype.CreateNewAlarm = function () {
	        console.log("CreateNewAlarm");
	        var data = { AlarmType: this.AlarmType, Message: this.Message, Id:"" };
	        this.alarmsService.AddAlarm(data);
	    };

	    return AlarmsController;
	})();

    // this code can be used with uglify
	module.controller("alarmsController",
		[
			"$scope",
			"$log",
			"alarmsService",
			AlarmsController
		]
	);
})();

The Angular controller uses the alarms service javascript file to send the create alarm HTTP POST request to the Web API backend.

(function () {
    'use strict';

	function AlarmsService($http, $log, $q) {
	    $log.info("alarmsService called");

	    var AddAlarm = function (alarm) {
	        var deferred = $q.defer();

	        console.log("addAlarm started");
	        console.log(alarm);

	        $http({
	            url: 'api/alarms/AddAlarm',
	            method: "POST",
	            data: alarm
	        }).success(function (data) {
	            deferred.resolve(data);
	        }).error(function (error) {
	            deferred.reject(error);
	        });
	        return deferred.promise;
	    };

		return {
		    AddAlarm: AddAlarm
		}
	}

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

	// this code can be used with uglify
	module.factory("alarmsService",
		[
			"$http",
			"$log",
            "$q",
			AlarmsService
		]
	);

})();

Now documents will be added every time you submit a request.

The application can be started from the command line using

dnu restore

dnx . web

The web is the command configured in the project.json file. Or of course, you can start it from Visual Studio.

NOTE: You must also have Elaticsearch installed and running before the application will run.

aspnet5Elasticsearch_02

This can be viewed using

http://localhost:9200/alarms/alarm/_search

aspnet5Elasticsearch_01

Now the basic application is up and running. The next step is to added Elastic Watcher and create some data events.

Links:

http://chris.59north.com/post/Getting-the-ASPNET-5-samples-to-work-on-Windows-with-the-new-dnvm

http://typecastexception.com/post/2015/05/17/DNVM-DNX-and-DNU-Understanding-the-ASPNET-5-Runtime-Options.aspx

http://blogs.msdn.com/b/webdev/archive/2014/06/17/dependency-injection-in-asp-net-vnext.aspx

https://github.com/aspnet/Announcements/issues/13

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

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

old(beta4 configuration)
http://weblog.west-wind.com/posts/2015/Jun/03/Strongly-typed-AppSettings-Configuration-in-ASPNET-5

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

3 comments

  1. […] An ASP.NET 5 Angular application with Elasticsearch, NEST and Watcher – damienbod […]

  2. Angulat + elasticsearch, wov, two of my favorite technologies. Keep up the good work and thanks for this cloudy Monday:)

Leave a comment

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