This JSON is basically a serialization of our entity, with some built-in conventions such as;
CamelCase instead of PascalCase: We got levelName instead of LevelName and son, meaning that all our PascalCase .NET class names and properties will be automatically converted into camelCase when they are serialized to JSO,
No indentation and no line feed / carriage return (LF/CR): Everything is stacked within a single line of text.
These conventions are the default options set by .NET core when dealing with JSON outputs.
To change the default behavior for readability and no PascalCase to CamelCase switching, add these to Program.cs file;
builder.Services.AddControllersWithViews()
.AddJsonOptions(options =>
{
// set this option to TRUE to indent hte JSON output
options.JsonSerializerOptions.WriteIndented = true;
// set this option to NULL to use PascalCase instead of camelCase (default)
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
The story of AngularJS started in 2009 when Miško Hevery (now senior computer scientist and Agile coach at Google) and Adam Abrons (now director of engineering at Grand Rounds) were working on their side project, an end-to-end (E2E) web development tool that would have offered an online JSON storage service and also a client-side library to build web applications depending on it. To publish their project, they took the GetAngular.com hostname.
During that time, Hevery, who was already working at Google, was assigned to the Google Feedback project with two other developers. Together, they wrote more than 17,000 lines of code in 6 months, slowly sinking into a frustrating scenario of code bloat and testing issues. Given the situation, Hevery asked his manager to rewrite the application using GetAngular (the side project mentioned previously), betting that he could do that alone within 2 weeks. His manager accepted and Hevery lost the bet shortly thereafter, as the whole thing took him 3 weeks instead of two; however, the new application had only 1,500 lines of code instead of 17,000. This was more than enough to get Google’s interest for the new framework, which was given the name of AngularJS shortly thereafter.
To listen to the full story, take a look at the following Miško Hevery keynote speech at ng-conf 2014:
This is a continuation of earlier article where we implemented monitoring output as JSON. We are going to build Angular Component that is capable of fetching and displying the JSON data.
Angular component is commonly made of three separate files;
The Component(.ts) file, written in TypeScript and containing the Component class, together with all the module references, functions, variables, and so on.
The Template (.html) file, written in HTML extended with the Angular Template Syntax, which defines the UI layout architecture.
The Style (.css) file, written in CSS and containing the cascading style sheets rules and definitions for drawing the UI.
These three-files approach is the most practical one, the only required file is the Component one, as both the Template and the Style Files could also be embedded as inline elements within the Component file.
If we compare this with Model-View-Controller (MVC) pattern, we can say that, in Angular, The Component is the Controller/ViewModel and the template represents the view.
Navigate through the /ClientApp/src/app folder and create a new health-check folder.
Inside health-check folder, create following files;
Once done, fill-in with these contents;
health-check.component.ts
This one is in /ClientApp/src/app/health-check/health-check.component.ts folder.
At file start, we import all the Angular directives, pipes, services and Components (single word modules) that we need throughout the whole class.
We instantiated a HttpClient service and a baseUrl variable using DI in the Component constructor. The baseUrl value is being set by making use of BASE_URL provider, defined in the /ClientApp/src/main.ts file. This will resolve to our application’s root URL. This value is required by the HttpClient service to build the URL that will be used to fetch the data from the server. (NOTE: I am using .NET CORE Server port to access /hc endpoint becuase Angular runs on proxy side and has no way to see server side except via API. We will use API later).
Last, we defined two interfaces to deal with the JSON request we’re expecting to receive from the HealthChecks middleware; Result and Check.
We need to make following changes to our Program.cs file so that we don’t get CORS issue;
Let’s spend some time on some very important topics in Angular;
Imports and modules
The static import statement is used to import bindings that are exported by other JavaScript modules
A module is basically a collection of variables, functions, classes and so on, grouped within a class: each module is executed within its own scope, not in global scope, meaning all the elements declared within are not visible from the outside unless they are explicitly exported using the export statement. Conversely, to consume a variable, function, class, interface, and so on contained (and exported) within a module, the module has to be imported using the import statement.
This quite similar to what we do with namespaces in most programming languages (C# has using statement, for example).
IMPORTANT
Javascipt modules are different and Angular’s modularity system is different. Angular module are based upon the @NgModule decorator. Each Angular app has at least one NgModule class, called the root module, which is conventionally named AppModule and resides in the app.module.ts file in the application root.
DI
.NET core and Angular make extensive use of this design pattern. Think about dependencies in a class; these can be defined as service or objects that a class needs to instantiate into variables or properties, in order to perform one or more tasks.
In a classic coding pattern, those dependencies are instantiated on the fly within the class itself, for example;
public MyClass() {
var myElement = new Element();
myElement.doStuff();
}
In the preceding example, the myElement variable is an object instance of the Element type, and also a (local) dependency of MyClass: as we can see, it gets instantiated in the constructor because we most likely need to use it there. From there, we can either use it as a local variable (and let it die at the end of the constructor’s lifecycle) or assign it to a class property to further extend its life span and scope.
DI is an alternative software design pattern, in which a class asks for dependencies from external sources rather than creating them itself. To better understand such a concept, let’s try to rewrite the same code as before with a DI approach, like this:
public MyClass(Element myElement) {
myElement.doStuff();
}
As we can see, there’s no need to instantiate the myElement variable because such a task is already handled by the Dependency Injector—an external code that is responsible for creating the injectable objects and injecting them into the classes.
The whole DI coding pattern is based upon the concept of Inversion of Control (IoC), to resolve dependencies. Such a concept revolves around the basic idea that, formally, if ObjectA depends on ObjectB, then ObjectA must not create or import ObjectB directly, but provide a way to inject ObjectB instead. In the preceding code block example, ObjectA is obviously MyClass, while ObjectB is the myElement instance.
ngOnInit (and other lifecycle hooks)
Each Angular Component has a lifecycle, which is managed by Angular. Each time a user visits a view within our app, the Angular framework creates and renders the required Components (and directives) along with their children, reacts to their changes whenever the user interacts with them, and eventually destroys and removes them from the Document Object Model (DOM) when the user navigates elsewhere. All these “key moments” trigger some lifecycle hook methods that Angular exposes to the developers so that they can perform something when each one of them actually occurs: as a matter of fact, they are very similar to the C# event handlers.
ngOnChanges(): Responds when Angular (re)sets data-bound input properties. The method receives a SimpleChanges object of current and previous property values. Called before ngOnInit(), and whenever one or more data-bound input properties changes.
ngOnInit(): Initializes the directive/Component after Angular first displays the data-bound properties and sets the directive/Component’s input properties. Called once, after the first ngOnChanges() method.
ngDoCheck(): Detects and acts upon changes that Angular can’t, or won’t, detect on its own. Called during every change detection run, immediately after ngOnChanges() and ngOnInit().
ngAfterContentInit(): Responds after Angular projects external content into the Component’s view/the view that a directive is in. Called once after the first ngDoCheck() method.
ngAfterContentChecked(): Responds after Angular checks the content projected into the directive/Component. Called after the ngAfterContentInit() method and every subsequent ngDoCheck() method.
ngAfterViewInit(): Responds after Angular initializes the Component’s views and child views/the view that a directive is in. Called once after the first ngAfterContentChecked() method.
ngAfterViewChecked(): Responds after Angular checks the Component’s views and child views/the view that a directive is in. Called after the ngAfterViewInit() method and every subsequent ngAfterContentChecked() method.
ngOnDestroy(): Cleans up just before Angular destroys the directive/Component. Unsubscribes Observables and detaches the event.
A good practice is to use Http-Client source code in the ngOnInit lifecycle method instead of using the Component’s constructor.
Constructor
All TypeScript classes have a constructor() method that will be called whenever we create an instance of that class: since TypeScript is, by all means, a superset of JavaScript, any TypeScript constructor() method will be transpiled into a JavaScript constructor() function. Let’s go with an example;
class MyClass() {
constructor() {
console.log("MyClass has been instantiated");
}
}
This will be transpiled into the following Javascript function;
function MyClass() {
console.log("MyClass has been instantiated");
}
If we omit the constructor in TypeScript, the JavaScript transpiled function will be empty; however, whenever the framework needs to instantiate it, it will still call it in the following way, regardless of whether it has the constructor or not:
var myClassInstance = new MyClass();
Understanding this is very important because it greatly helps us to understand the difference between the Component’s constructor() method and its ngOnInit() lifecycle hook, and it’s a huge difference, at least from the perspective of the Component initialization phase.
The whole Angular Bootstrap process can be split into two major (and subsequent) stages:
Instantiating the Components
Performing change detection
As we can easily guess, the constructor() method is called during the former phase, while all the lifecycle hooks—including the ngOnInit() method—are called throughout the latter.
If we look at these methods from this perspective, it’s pretty easy to understand the following key concepts:
If we need to create or inject some dependencies into an Angular Component, we should use the constructor() method; as a matter of fact, this is also the only way we can do that since the constructor is the only method that gets called in the context of the Angular injector.
Conversely, whenever we need to perform any Component initialization and/or update task—such as performing an HTTP request, updating the DOM, and so on— we should definitely do that by using one of the lifecycle hooks.
The ngOnInit() method, as its name implies, is often a great choice for the Component’s initialization tasks, since it happens right after the directive’s and/or Component’s input properties are set. That’s why we have used this to implement our HTTP request, using the Angular built-in HttpClient service.
HTTPClient
Being able to efficiently send and receive JSON data from our .NET Core Controllers is probably the most important requirement for our single page application (SPA). We chose to do that using the Angular HttpClient service.
HttpClient provides testability support and better error handling via APIs entirely based on Observables.
It’s worth noting that putting the HttpClient service within the Component itself is not good practice because it will often lead to unnecessary code repetitions among the various Components that need to perform HTTP calls and handle their results. This is a known issue that greatly affects production-level apps, which will likely require post-processing of the received data, handling errors, adding retry logic to deal with intermittent connectivity, and so on.
To better deal with those scenarios, it’s strongly advisable to separate the data access logic and the data presentation role by encapsulating the former in a separate service, which can be then injected into all the Components that require it, in a standardized and centralized way.
Observables
Observables are a powerful feature for managing async data; they are the backbone of the ReactiveX JavaScript (RxJS) library, which is one of the Angular required dependencies.
An Observable can be configured to send literal values, structured values, messages, and events, either synchronously or asynchronously: the values can be received by subscribing to the Observable itself using the subscribe method hook, meaning that the whole data flow is handled within it—until we programmatically choose to unsubscribe. The great thing about this approach is that, regardless of the chosen approach (sync or async), streaming frequency, and data type, the programming interface for listening to values and stopping listening is the same.
The great advantages of Observables are the reason why Angular makes extensive use of them when dealing with data. If we take a good look at our HealthCheckComponent source code, we can see how we use them as well when our HttpClient service fetches the data from the server and stores the result in the this.result local variable. Such a task is performed by calling two consecutive methods: get() and subscribe().
Let’s try to summarize what they do, as follows:
get(): As the name suggests, this method issues a standard HTTP request to our .NET Core HealthChecks middleware to fetch the Result JSON response object. This method needs a URL parameter, which we create on the fly by adding the hc literal string (the same string that we set early on, within the Configure method of the Startup.cs file) to the Angular app’s BASE_URL.
subscribe(): This method instantiates an Observable object that will execute two very different actions right after a result and/or in case of an error. Needless to say, all this will be done asynchronously, meaning that it will run in a separate thread (or scheduled for later execution) while the rest of the code continues to execute.
Now that we know how the Angular HttpClient service works, we have every right to ask ourselves a couple of questions: why are we even using those interfaces? Can’t we just use the raw JSON data sent by the .NET Core HealthChecks middleware that we defined early on, consuming them as anonymous JavaScript objects?
Theoretically speaking, we can, just as much as we can output raw JSON from the Controllers, instead of creating all the ViewModel classes like we did instead. In a well-written app, though, we should always resist the temptation to handle raw JSON data and/or to use anonymous objects for a number of good reasons:
We have chosen TypeScript over JavaScript because we want to work with type definitions: Anonymous objects and properties are the exact opposite; they lead to the JavaScript way of doing things, which is something we wanted to avoid in the first place.
Anonymous objects (and their properties) are not easy to validate: We don’t want our data items to be error-prone or forced to deal with missing properties.
Anonymous objects are hardly reusable, and won’t benefit from many Angular handy features —such as object mapping—that require our objects to be actual instances of an interface and/or a type.
The first two arguments are very important, especially if we’re aiming for a production-ready application; no matter how easy our development task might seem at first, we should never think that we can afford to lose that level of control over our application’s source code.
The third reason is also crucial, as long as we want to use Angular to its full extent. If that’s the case, using an undefined array of properties—such as raw JSON data—is basically out of the question; conversely, using a structured TypeScript interface is arguably the most lightweight way to work with structured JSON data in a strongly typed fashion.
It’s worth noting that we’ve not added the export statement to our interface: we did that on purpose since we’re only going to use this within the HealthCheckComponent class. Should we need to change such behavior in the future—for example, to create an external Data Service—we’ll have to add such a statement (and, arguably, move each one of them into a separate file) to enable us to import them into other classes.
health-check.component.html
Located in /ClientApp/src/app/health-check/health-check.component.html folder;
As we can see, the template part of our Angular Component is basically an HTML page, containing a table with some Angular directive. Before moving on, let’s have a closer look, as follows:
ngIf: This is a structural directive that conditionally includes the container HTML element, based on the Boolean expression value specified after the equals (=) sign: when such an expression evaluates to true, Angular renders the element; otherwise, it doesn’t. It can be chained with an else block that—if present—will be shown when the expression evaluates to false or null. In the preceding code block, we use it within the element so that it only appears when the result internal variable (which we defined in the Component class earlier on) stops being null, which will happen after the data has been fetched from the server.
ngFor: Another structural directive that renders a template for each item contained in a given collection. The directive is placed on an element, which becomes the parent of the cloned templates. In the preceding code block, we use it inside the main element to create and show a element (a row) for each check item within the result.checks array.
{{ check.name }}, {{ check.responseTime }}, and so on: These are called interpolations and can be used to incorporate calculated strings into the text between HTML element tags and/or within attribute assignments. In other words, we can use them as placeholders for our class variables’ property values. As we can see, the interpolation default delimiters are the double curly braces, {{ and }}.
health-check.component.css
This file location is /ClientApp/src/app/health-check/health-check.component.css;
There’s not much to say here; just some vanilla CSS to style out the Component template.
Adding the Component to the Angular app
Our Component is ready, we need to add it to our Angular app. In order to do that, we need to make some minimal changes to the following files;
app.module.ts
nav-menu.component.ts
nav-menu.component.html
AppModule
Each new component must be referenced in the AppModule so that it can be registered within our app. On top of that, we need to create the relevant entry within our RoutingModule configuration so that our users wil be able to navigate to that page.
Open the /Client/App/src/app/app.module.ts file and add following highlighted lines;
We are done with the required step of add new component navigation path to the RoutingModule; however we also need to add a link for our users to click;
Open the ClientApp/src/app/nav-menu/nav-menu.component.ts file and add highlighted text;
Let’s get our hands in some code. This web app will act as a monitoring and reporting service that will check the health status of a target server.
We are going to use Micorosft.AspNetCore.Diagnositcs.HealthChecks package, a built-in feature of the .NET Core framework. This package is meant to be used to allow a monitoring service to check the status of another running service.
Add app.UseHalthChecks middleware in Program.cs file;
This middleware will create a server-side route for the health checks. Note that we have added this middleware before Endpoints so that this new route won’t be overriden by the general-purpose Controller route pattern.
Keep in mind, when we run application it uses two ports; one for server side and second one for angular side (in our case it’s 7031). For angular it creates a proxy and re-direct users there (in this case 44488).
If we run our application, we can see our system is healthy.
The system is healthy because we haven’t defined any checks yet.
Let’s add an Internet Control Message Protocol (ICMP) AKA Ping. PING request is a basic way to check the presence and therefore availability, of a server that we know we should be able to reach within a LAN / WAN connection.
In a nutshell, it works in following way; the machine that performs the PING sends one or more ICMP echo request packets to the target host and waits for a reply; if it receives it, it reports the round-trip time of the whole task; otherwise, it time outs and reports a “host not reachable” error.
The “host not reachable” error can be due to a number of possible reasons;
The target host is not available
The target host is available, but actively refuses TCP/IP connections of any kind
The target host is available and accepts TCP/IP incoming conections, but it has been configured to explicitly refuse ICMP requests and/or not send ICMP echo replies back.
The target host is available and properly configured to accept ICMP requests and send echo replies back, but the connection is very slow or hindered by unknown reasons (performance, heavy load etc), so the round-trip time takes too long, or even times out.
So what could be the possible outcomes of our implementation;
Healthy: We can consider the host Healthy whenever the PING succeeded with no errors and timeouts.
Degraded: We can consider the host Degraded whenever the PING succeeded, but the round-trip takes too long.
Unhealthy: We can consider the host Unhealthy whenever the PING failed, that is, the check times out before any reply.
public class PingHealthCheck : IHealthCheck
{
private string Host = "www.does-not-exist.com";
private int Timeout = 300;
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
using (var ping = new Ping())
{
var reply = await ping.SendPingAsync(Host);
switch (reply.Status)
{
case IPStatus.Success:
return (reply.RoundtripTime > Timeout) ? HealthCheckResult.Degraded() : HealthCheckResult.Healthy();
default:
return HealthCheckResult.Unhealthy();
}
}
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy();
}
}
}
Add PingHealthCheck to the pipeline by modifying Program.cs file;
So we are getting a response. that’s great, it works. Three are 3 major flaws;
Hardcoded values: The Host and the Timeout variables should be passed as parameters
Uninformative response: Healthy and Unhealthy are not that great, we should create a better output message
Untyped output: The current response is being sent in plain text, if we want to fetch it with Angular, a JSON content-type be better.
public class PingHealthCheck : IHealthCheck
{
private string Host { get; set; }
private int Timeout { get; set; }
public PingHealthCheck(string host, int timeout)
{
Host = host;
Timeout = timeout;
}
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
using (var ping = new Ping())
{
var reply = await ping.SendPingAsync(Host);
switch (reply.Status)
{
case IPStatus.Success:
var msg = $"ICMP to {Host} took {reply.RoundtripTime} ms.";
return (reply.RoundtripTime > Timeout) ? HealthCheckResult.Degraded(msg) : HealthCheckResult.Healthy(msg);
default:
var err = $"ICMP to {Host} failed: {reply.Status}";
return HealthCheckResult.Unhealthy(err);
}
}
}
catch (Exception ex)
{
var err = $"ICMP to {Host} failed: {ex.Message}";
return HealthCheckResult.Unhealthy(err);
}
}
}
Update middleware in Program.cs file so that we can call it programmatically;
builder.Services.AddHealthChecks()
.AddCheck("PING_01", new PingHealthCheck("www.msn.com", 100))
.AddCheck("PING_02", new PingHealthCheck("www.google.com", 100))
.AddCheck("PING_03", new PingHealthCheck("www.does-not-exist.com", 100));
The reason is obvious, if one of them failed the whole stack will be failed. The last one in our example is failure. We can avoid this sum behavior by implementing the resolution of third flaw (as mentioned before): JSON-structured output message.
Add a new class in project root, CustomHealthCheckOptions;
public class CustomHealthCheckOptions : HealthCheckOptions
{
public CustomHealthCheckOptions() : base()
{
var jsonSerializerOptions = new JsonSerializerOptions()
{
WriteIndented = true
};
ResponseWriter = async (c, r) =>
{
c.Response.ContentType = MediaTypeNames.Application.Json;
c.Response.StatusCode = StatusCodes.Status200OK;
var result = JsonSerializer.Serialize(new
{
checks = r.Entries.Select(e => new
{
name = e.Key,
responseTime = e.Value.Duration.TotalMilliseconds,
status = e.Value.Status.ToString(),
description = e.Value.Description
}),
totalStatus = r.Status,
totalResponseTime = r.TotalDuration.TotalMilliseconds,
}, jsonSerializerOptions);
await c.Response.WriteAsync(result);
};
}
}
Modify Program.cs file;
app.UseHealthChecks("/hc", new CustomHealthCheckOptions());
Run the application and here is expected output;
{
"checks": [
{
"name": "PING_01",
"responseTime": 78.6889,
"status": "Healthy",
"description": "ICMP to www.msn.com took 12 ms."
},
{
"name": "PING_02",
"responseTime": 62.3402,
"status": "Healthy",
"description": "ICMP to www.google.com took 14 ms."
},
{
"name": "PING_03",
"responseTime": 144.9184,
"status": "Unhealthy",
"description": "ICMP to www.does-not-exist.com failed: An exception occurred during a Ping request."
}
],
"totalStatus": 0,
"totalResponseTime": 181.1726
}
Each and every check is properly documented, as well as the total outcome data, in a structured JSON object. This is just what we need to feed some Angular Components.