Nodejs with React and TypeScript

If you want to run some Facebook React component on the client side and still have all the benefits of TypeScript, it’s possible. This article will show you how to setup React with TypeScript. I have already cover previously how to setup Visual Studio Code with Nodejs. You should go take a look because this text consider that everything is setup.

First, you need to get the TypeScript definition file for React. This is required to have all functions signatures and let TypeScript validate that you are using legal calls. To do so, use npm to download react and react-dom TypeScript definition file. The first command is to get tsd, the two next ones are to install the libraries needed.

   npm install tsd -g
   tsd install react --save
   tsd install react-dom --save

You should already have TypeScript tsconfig.json file configured for you from the previous article. Make sure you have the compilation property “jsx” set to “react”.

{
    "compilerOptions": 
    {
    "target": "ES6",
    "module": "amd",
    "sourceMap": true,
    "jsx": "react",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "declaration": false,
    "noImplicitAny": false,
    "removeComments": true,
    "noLib": false,
    "preserveConstEnums": true,
    "suppressImplicitAnyIndexErrors": true
    }
}

The next step is to have a web server running. This one will provide the Html file, JavaScript (being compiled from TypeScript), Css. The simplest way is to use the server called Express.

    npm install express

This will install a small server that you will be access by your localhost with port 3000. This can be changed anytime in the bin\www file (generated by Express).

For the purpose of this demo, we will create a new page that will say “hello world”, on the client side, using React. Inside the file app.js, generated by Express, we will add a new route entry. The mapping between hello as a string from the url is set to a JavaScript route file, in that case named also “hello”.

    app.use('/hello', hello);

The route details are specified in routes/hello.js file. The route tells that if the url is localhost:3000/hello to map to the hello view. Here is how it should look:

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('hello', { title: 'Hello Word Title' });
});

module.exports = router;

The view by default use the Jade engine. This can be modified in the app.js. For this tutorial, Jade engine is not really used and will reference a basic Html file. To do, create a file named “hello.jade” in the “views” folder.

    include hello.html

Create also an Html file hello.html:

<html>
    <head>
        <link rel="stylesheet" src="stylesheets/style.css"/>
        <script src="javascripts/require.js"></script>
        <script>
            require.config({
                baseUrl: "/",
                paths: {
                    "react": "react/dist/react",
                    "react-dom": "react-dom/dist/react-dom",
                }
            });
        </script>
    </head>
    <body>
        <h1>Title1</h1>
        <div id="hello"></div>
    </body>
    
    <script>
        require(["javascripts/hello", 'react', 'react-dom'],function(Hello, React, ReactDom) {
                var helloWorldComponent = ReactDom.render(Hello({name:"Patrick"}), document.getElementById('hello'));
            }
        );
    </script>
</html>

This has several important point. First, you need to have require.js to use your JavaScript in module. to do so, download requires.js and make a copy in public/javascripts folder. This will let you ask for specific JavaScript to be fully loaded before using them. In our case, we need our custom hello control, react and react-dom to be fully available before generating the component.

[npm]
npm install requiresjs
[/npm]

Be careful, you need to copy the require.js file from the node_modules/requirejs folder next to your JavaScript. However, copying react and react-dom is not required because we can setup requirejs to have paths. Finally, in the Html, we add a script that will wait to have your hello JavaScript file, react and react-dom before using ReactDom.render to populate the component into the div with “hello” id.

Since we are using TypeScript, we need to create in the javascript folder a file named hello.tsx. When we compile TypeScript, this will generate a JavaScript file that will be used by the require statement in the Html. A simple TypeScript for hello world is to have a React component that will render “Hello” + property.

/// <reference path="../../typings/react/react.d.ts" />
/// <reference path="../../typings/react/react-dom.d.ts" />

import * as React from 'react';

interface HelloProps { name: string;}
interface HelloState { }

class Hello extends React.Component<HelloProps,HelloState> {
  constructor(props: HelloProps) {
    super(props);
  }
  public render() {
    return <div>Hello {this.props.name}</div>
  }
}
function HelloFactory(props: HelloProps) {
  return React.createElement(Hello, props);
}

export = HelloFactory;

The first two lines are there to specify that we are using react. It’s a link to the definition file. Without them, the compiler doesn’t know where to get all existing React classes.

The code defines two interface. One for properties, one for state of the React component we are building. The class take the property in parameter and reader a Html that says hello. Finally, we export the class to be accessible outside this file.

To see everything, open your console at the project root and run : node .\bin\www and go to : http://localhost:3000/hello

HelloReactPatrick

This example is very straight forward but is not simple if you just start with Nodejs, React and TypeScript. It’s a lot of libraries that need to be stitches together. It’s also a very moving field with a lot of changes. A caveat of this tutorial is that we only show a read only example of React. In a next article, I’ll show you how to have a full loop that allow you to input value and have the control to be re-render. You can find the whole source code in this GitHub Repository.

VsCode and TypeScript

Visual Studio Code has a good article about how to configure VsCode and TypeScript but I wanted to give more detail about how to setup everything in a more straight forward way. This article talks about how to use VsCode in a way that every time you save a TypeScript file that this one generate the JavaScript and mapping automatically. It also have the advantage to do quick change while running NodeJs because NodeJs doesn’t need to be closed and re-started.

.vscode folder

The VsCode folder is a folder located at the root folder of your project. This folder lets you add configuration files in Json format. One file that can be used is named “tasks.json” and is used for running task. This is where we will create the task runner for TypeScript.

vscodefolder

To do so, hit “F1” key and type “Configure Task Runner“. This will create for you the tasks.json file under .vscode folder.

taskrunner

You can put he following code:

{
	// See http://go.microsoft.com/fwlink/?LinkId=733558
	// for the documentation about the tasks.json format
	"version": "0.1.0",
	"command": "tsc",
	"isShellCommand": true,
	"args": ["-p", "."],
	"showOutput": "silent",
	"problemMatcher": "$tsc"
}

VSCode Shortcut

From there, you can use the default hotkey to run the task : Ctrl+Shift+B. However, you can also bind that key on save. This way, everytime you save your file, TypeScript will be compiled into JavaScript. To do so, you can change you VsCode keybinding located in your user profile.

    C:\Users\patrick\AppData\Roaming\Code\User\keybindings.json

You can all on “ctrl+s” the command “tasks build”.

[
  {
    "key": "ctrl+s",          
    "command": "workbench.action.tasks.build" 
  }
]

TypeScript Configuration

Next thing is to add TypeScript configuration into a tsconfig.json file. This file is located at the root of your project, at the same level where reside your package.json (npm). My TypeScript configuration contains a “jsx” mention only because I am using React and TypeScript.

{
    "compilerOptions": 
    {
    "target": "ES6",
    "module": "amd",
    "sourceMap": true,
    "jsx": "react",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "declaration": false,
    "noImplicitAny": false,
    "removeComments": true,
    "noLib": false,
    "preserveConstEnums": true,
    "suppressImplicitAnyIndexErrors": true
    }
}

You can have some problem to compile your TypeScript if you have this one already installed in your Program Files. VsCode already have TypeScript installation, so you do not need to have any other installation on your machine.

Localized URL with Asp.Net MVC with Traversal Feature

I already wrote how to have URL in multiple languages in Asp.Net MVC without having to specify the language in the URL. I then, shown how to configure the routing with a Fluent Api to help creating routing. In this article, I have an improved version of the previous code.

  • Routing Traversal with the Visitor Pattern
  • Mirror Url Support
  • Add Routing for Default Domain Url
  • Associate Controller to a Specific NameSpace

Routing Traversal with the Visitor Pattern

The biggest improvement from the last article is the possibility to traversal the whole routing tree to search a specific route. While the solution works with Asp.Net MVC by hooking easily with the AreaRegistrationContext and RouteCollection, you may want to traverse to get the translated Url in project outside the web project. In any case, the new solution let you traverse easily with different logic without the need to change anything. The secret resides in the Visitor Pattern. Route classes accept a visitor where logic can be executed. Here is an example of a concrete visitor that get the localized url from an area, controller and action.

// Arrange
var visitor = new RouteLocalizedVisitor(LocalizedSection.EN, Constants.Areas.MODERATOR, "Symbol", "SymbolChangeList", null, null);

// Act
RoutesArea.AcceptRouteVisitor(visitor);

// Assert
var result = visitor.Result().FinalUrl();
Assert.AreEqual("Moderation/Symbol-en/Symbol-Change-List",result);

How is this possible? By having 2 interfaces. One is for every element (Area, controller, action, list of area, list of controller, list of action) and one interface for the visitor.

/// <summary>
/// A route element is an Area, a Controller, an Action or a list of all these threes.
/// </summary>
public interface IRouteElement
{
    /// <summary>
    /// Entry point for the visitor into the element
    /// </summary>
    /// <param name="visitor"></param>
    void AcceptRouteVisitor(IRouteVisitor visitor);
}

/// <summary>
/// A visitor is the code that will traverse the configuration (tree) of routes.
/// </summary>
public interface IRouteVisitor
{
    /// <summary>
    /// Logic to be done by the visitor when this one visit an Area
    /// </summary>
    /// <param name="element"></param>
    /// <returns>True if has found a route that match the area criteria</returns>
    bool Visit(AreaSectionLocalized element);

    /// <summary>
    /// Logic to be done by the visitor when this one visit a Controller
    /// </summary>
    /// <param name="element"></param>
    /// <returns>True if has found a route that match the controller criteria</returns>
    bool Visit(ControllerSectionLocalized element);

    /// <summary>
    /// Logic to be done by the visitor when this one visit an Action
    /// </summary>
    /// <param name="element"></param>
    /// <returns>True if has found a route that match the actopm criteria</returns>
    bool Visit(ActionSectionLocalized element);

    /// <summary>
    /// Flag to indicate that a route has been found and that subsequent visits call be cancelled.
    /// This is to improve performance.
    /// </summary>
    bool HasFoundRoute { get; }
}

The IRouteElement interface is the entry point to start traversing the route’s tree. It’s also this interface used to move from one node to another one. This interface is implemented by every nodes (Area, controller, action, list of area, list of controller, list of action). The consumer of the fluent Api shouldn’t care else than knowing that this is where he will pass its visitor. For the curious, here is the implementation on the Controller.

public void AcceptRouteVisitor(IRouteVisitor visitor)
{
    if (visitor.Visit(this))
    {
        foreach (var action in this.ActionTranslations)
        {
            action.AcceptRouteVisitor(visitor);
            if (visitor.HasFoundRoute)
            {
                break;
            }
        }
    }
}

The implementation is very similar for Area. What is does is that it allow the visitor to visit the controller node, if this one is matching (controller name), then it visits every children (actions) of the controller. To improve the performance, the loop is stopped if an action is found has a good one. The most interesting part is how to create a visitor. The visitor is the one that get called by the tree on every AcceptRouteVisitor. The visitor is the one having the logic of what you want to do. Here is the full code to get a localized route.

/// <summary>
/// Visitor to find from generic information a localized route from an three of routes
/// </summary>
public class RouteLocalizedVisitor: IRouteVisitor
{
    private readonly CultureInfo culture;
    private readonly string area;
    private readonly string controller;
    private readonly string action;
    private readonly string[] urlInput;
    private readonly string[] tokens;
    private readonly RouteReturn result;


    /// <summary>
    /// 
    /// </summary>
    /// <param name="culture">Culture used for the route to Url convertion</param>
    /// <param name="area">Area requested. Can be null.</param>
    /// <param name="controller">Controller requested. This cannot be null.</param>
    /// <param name="action">Action requested. This cannot be null</param>
    /// <param name="urlInput">Specific input. Can be null.</param>
    /// <param name="tokens">Custom localized token. Can be null.</param>
    public RouteLocalizedVisitor(CultureInfo culture, string area, string controller, string action, string[] urlInput, string[] tokens)
    {
        if (controller == null)
        {
            throw new ArgumentNullException(nameof(controller));
        }
        if (action == null)
        {
            throw new ArgumentNullException(nameof(action));
        }
        this.culture = culture;
        this.area = area;
        this.controller = controller;
        this.action = action;
        this.urlInput = urlInput;
        this.tokens = tokens;
        this.result = new RouteReturn();
    }

    /// <summary>
    /// Visitor action for area. If the area match, the result is updated with the localized area name
    /// </summary>
    /// <param name="element">Area visited</param>
    /// <returns>True if found; False if not found</returns>
    public bool Visit(AreaSectionLocalized element)
    {
        if (element.AreaName == this.area)
        {
            this.result.UrlParts[Constants.AREA] = element.Translation.First(d => d.CultureInfo.Name == this.Culture.Name).TranslatedValue;
            return true;
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    /// Visitor action for controller. If the controller match, the result is updated with the localized controller name
    /// </summary>
    /// <param name="element">Controller visited</param>
    /// <returns>True if found; False if not found</returns>
    public bool Visit(ControllerSectionLocalized element)
    {
        if (element.ControllerName == this.controller)
        {
            this.result.UrlParts[Constants.CONTROLLER] =  element.Translation.First(d => d.CultureInfo.Name == this.Culture.Name).TranslatedValue;
            return true;
        }
        else
        {
            return false;
        }
    }

    /// <summary>
    /// Visitor action for action. If the action match, the result is updated with the localized action name
    /// </summary>
    /// <param name="element">Action visited</param>
    /// <returns>True if found; False if not found</returns>
    public bool Visit(ActionSectionLocalized element)
    {
        var urlPartToAddIfGoodPart = new Dictionary<string, string>();
        if (element.ActionName == this.action)
        {
            if (!this.ExtractTokens(element, urlPartToAddIfGoodPart))
            {
                return false;
            }

            if (!this.ExtractUrlPartValues(element, urlPartToAddIfGoodPart))
            {
                return false;
            }

            this.result.UrlParts[Constants.ACTION] = element.Translation.First(d => d.CultureInfo.Name == this.Culture.Name).TranslatedValue;
        }
        else
        {
            return false;
        }
            
            
        this.RemoveOptionalWithDefaultEmpty(element, urlPartToAddIfGoodPart);
        urlPartToAddIfGoodPart.ToList().ForEach(x => this.result.UrlParts.Add(x.Key, x.Value)); //Merge the result
        this.result.UrlTemplate = element.Url;
        this.result.HasFoundRoute = true;
        return true;

    }

    /// <summary>
    /// Remove optional value by adding this one in the UrlPart with Empty string which make the GetFinalUrl to replace the {xxx} with nothing
    /// </summary>
    /// <param name="element"></param>
    /// <param name="urlPartToAddIfGoodPart"></param>
    private void RemoveOptionalWithDefaultEmpty(ActionSectionLocalized element, Dictionary<string, string> urlPartToAddIfGoodPart)
    {
        if (element.Values != null)
        {
            var dict = (RouteValueDictionary) element.Values;
            foreach (var keyValues in dict)
            {
                var remove = this.urlInput == null || (this.urlInput != null && this.urlInput.All(f => f != keyValues.Key));
                if (remove)
                {
                    urlPartToAddIfGoodPart[keyValues.Key] = string.Empty;
                }
            }
        }
    }

    /// <summary>
    /// If the user request a url than we let it through (to let the user replace with his value). If not defined in UrlPart, then use default value.
    /// </summary>
    /// <param name="element"></param>
    /// <param name="urlPartToAddIfGoodPart"></param>
    /// <returns></returns>
    private bool ExtractUrlPartValues(ActionSectionLocalized element, Dictionary<string, string> urlPartToAddIfGoodPart)
    {
        //Default Values : check if there, nothing to replace
        if (this.urlInput != null)
        {
            foreach (string input in this.urlInput)
            {
                if (element.Url.IndexOf(input, StringComparison.CurrentCultureIgnoreCase) >= 0)
                {
                    var routeValues = (RouteValueDictionary) element.Values;
                    var isDefinedValue = (routeValues != null) && routeValues.Keys.Contains(input);
                    if (isDefinedValue)
                    {
                        var defaultValue = routeValues[input].ToString();
                        if (defaultValue == string.Empty)
                        {
                            urlPartToAddIfGoodPart[input] = "{" + input + "}";
                        }
                        else
                        {
                            urlPartToAddIfGoodPart[input] = defaultValue;
                        }
                    }
                    else
                    {
                        //Default if not empty
                        urlPartToAddIfGoodPart[input] = "{" + input + "}";
                    }
                }
                else
                {
                    return false;
                }
            }
        }
        return true;
    }

    /// <summary>
    /// Get localized value for every tokens
    /// </summary>
    /// <param name="element"></param>
    /// <param name="urlPartToAddIfGoodPart"></param>
    /// <returns></returns>
    private bool ExtractTokens(ActionSectionLocalized element, Dictionary<string, string> urlPartToAddIfGoodPart)
    {
        if (this.tokens != null)
        {
            if (element.Tokens == null)
            {
                return false;
            }
            for (int i = 0; i < this.tokens.Length; i++)
            {
                if (element.Tokens.ContainsKey(this.tokens[i]))
                {
                    var tokenFound = element.Tokens[this.tokens[i]];
                    var tokenTranslation = tokenFound.First(d => d.CultureInfo.Name == this.Culture.Name);
                    urlPartToAddIfGoodPart[this.tokens[i]] = tokenTranslation.TranslatedValue;
                }
                else
                {
                    return false;
                }
            }
        }
        return true;
    }

    /// <summary>
    /// Indicate if a route has been found. This mean that every condition was met
    /// </summary>
    public bool HasFoundRoute
    {
        get { return this.result.HasFoundRoute; }
    }


    public CultureInfo Culture
    {
        get { return this.culture; }
    }



    public RouteReturn Result()
    {
        return this.result;
    }
}

This code let you specify an area (or not), a controller, an action, expected values to be passed and tokens. If the values has a default value, this one will be used. If the default value is empty, this one is avoided in the url. The token is simply translated. Here is two examples:

public static ControllerSectionLocalizedList RoutesController = FluentLocalizedRoute
											.BuildRoute()
										    .ForBilingualController("Account", "Account-en", "Compte")
												.WithBilingualAction("Profile", "Profile-en", "Afficher-Profile")
												   .WithDefaultValues(new { username = UrlParameter.Optional })
												   .WithUrl("{action}/{username}")
											.ToList();
[TestMethod]
public void GivenARouteToVisit_WhenNoAreaWithDefaultValue_ThenReturnRouteWithoutAreaWithDefaultValue()
{
    // Arrange
    var visitor = new RouteLocalizedVisitor(LocalizedSection.EN, null, "Account", "Profile", null, null);

    // Act
    RoutesController.AcceptRouteVisitor(visitor);

    // Assert
    var result = visitor.Result().FinalUrl();
    Assert.AreEqual("Profile-en",result);
}

[TestMethod]
public void GivenARouteToVisit_WhenNoAreaWithDefaultValueSet_ThenReturnRouteWithoutAreaWithDefaultValue()
{
    // Arrange
    var visitor = new RouteLocalizedVisitor(LocalizedSection.EN, null, "Account", "Profile", new [] {"username"}, null);

    // Act
    RoutesController.AcceptRouteVisitor(visitor);

    // Assert
    var result = visitor.Result().FinalUrl();
    Assert.AreEqual("Profile-en/{username}", result);
}

Mirror Url Support

Mirror Url is the capability to have more than one Url for a specific route. This is good when you want to have more than a single URL to be associated to a specific action. It’s a mirror Url because the real Url won’t get affected. It also mean that trying to generate this Url from the route values won’t get into that mirror Url but the main one. The change is inside the Fluent Url and it adds in the list of action the mirror Url.

public IRouteBuilderAction_ToListOnlyWithAnd WithMirrorUrl(string url)
{
	this.AddInActionList();
	var mirrorAction = new ActionSectionLocalized(this.currentAction.ActionName
		, this.currentAction.Translation
		, this.currentAction.Values
		, this.currentAction.Constraints
		, url);
	var s = new RouteBuilderAction(this.currentControllerSection, mirrorAction, this.routeBuilder,this.routeBuilderController);
	this.currentControllerSection.ActionTranslations.Add(mirrorAction);
	this.currentAction = mirrorAction;
	return s;
}

Add Routing for Default Domain Url

This new feature lets having an action related to the root url, the domain one. In short, it set the controller and action as a default value, so, it’s not required to be in the Url.

public IRouteBuilderAction_ToListWithoutUrl AddDomainDefaultRoute(string controller, string action)
{
	var controller1 = ForBilingualController("{controller}", "{controller}", "{controller}");
	var action1 = controller1.WithBilingualAction("{action}", "{action}", "{action}");
	var action2 = action1.WithDefaultValues(Constants.CONTROLLER, controller);
	var action3 = action2.WithDefaultValues(Constants.ACTION, action);
	var action4 = action3.WithUrl("{controller}/{action}");
	return action4;
}

Associate Controller to a Specific NameSpace

The last modification is to have the possibility to associate a namespace for the controller. This is required if your controller name is used in different namespace to avoid collision. This is also inside the Fluent Api. In short, it add to the controller section a namespace if this one doesn’t have one. However, if this one already have a namespace, this one is added.

public IRouteBuilderControllerAndControllerConfiguration AssociateToNamespace(string @namespace)
{
	if (currentControllerSection.NameSpaces == null)
	{
		currentControllerSection.NameSpaces = new[] { @namespace };
	}
	else
	{
		var currentNamespaces = currentControllerSection.NameSpaces;
		var len = currentControllerSection.NameSpaces.Length;
		Array.Resize(ref currentNamespaces, len + 1);
		currentNamespaces[len-1] = @namespace;
		currentControllerSection.NameSpaces = currentNamespaces;
	}
   
	return this;
}

A change is also required inside the LocalizedRoute class.

private void AdjustForNamespaces()
{
	var namespaces = this.ControllerTranslation.NameSpaces;
	bool useNamespaceFallback = (namespaces == null || namespaces.Length == 0);
	base.DataTokens["UseNamespaceFallback"] = useNamespaceFallback;
	if ((namespaces != null) && (namespaces.Length > 0))
	{
		base.DataTokens["Namespaces"] = namespaces;
	}
}

These changes are nice addition to the previous post. You can find the the whole source code in GitHub.

AutoMapper.Mapper.CreateMap : Dynamically creating maps will be removed in version 5.0

AutoMapper from version 4.2 the static method CreateMap is obsolete and will be removed at version 5. It’s been years that people are configuring their mapping with this static method. Most people have divided their mapping into multiple classes across their model (domain) classes. While this can be a big task for huge solution, in most case the migration is simple. This article shows how to migrate from the static CreateMap method into a custom static variable that will handle all configurations. While the new patterns is great to be injected, it doesn’t mean that you should change your whole solution now to go in that direction.

First of all, if you had a custom interface or base class for the classes that define your mapping you should use instead AutoMapper.Profile. Having your class using this interface lets you override a method called Configure. You can from that base class call base.CreateMap. Since you access the CreateMap method, not statically, and with the same signature, the migration is easy. Here is an example.

public class OverallMapping: Profile
{
	protected override void Configure()
	{
		base.CreateMap<HealthOverall, HealthOverallViewModel>();
	}
}

The last step is to have all profiles loaded into your static variable. The easiest way is to use reflection to loops through all classes and to get all classes that inherit from Profile. The method that use the reflection is called once in your Global.asax.cs during the application start. Since it’s called once, the reflection call is not problematic on performance of your web application.

public static class MappingConfiguration
{
	public static void CreateMapping()
	{
		var profiles = (from type in typeof(MappingConfiguration).Assembly.GetTypes()
						where typeof(Profile).IsAssignableFrom(type) && !type.IsAbstract && type.GetConstructor(Type.EmptyTypes) != null
						select type).Select(d => (Profile)Activator.CreateInstance(d))
									 .ToArray();

		var config = new MapperConfiguration(cfg =>
		{
			foreach (var profile in profiles)
			{
				cfg.AddProfile(profile);
			}
		});
		MapperFacade.MapperConfiguration = config.CreateMapper();
	}
}

public static class Mapper{
	public static IMapper MapperConfiguration;
}

The static class and static property that hold all mapping is what you need to use in your application to map anything.

var viewModel = MapperFacade.MapperConfiguration.Map<HealthOverall, HealthOverallViewModel>(model);

That’s it! Pretty straight forward. What is time consuming is to change mapping configuration but this is still limited if your application already had a good division about how to define the mapping.

Application Insight Analytics Feature

If you are using Microsoft Azure Application Insight to send telemetry you may had yourself a little bit in a corner when it was the time to consume the data. Microsoft Azure portal has a metric explorer, but it was limited on what you can group on and custom properties were hard to queries. In fact, I should say that it wasn’t possible to really query other than using pre-defined option. Recently, the Application Insight team released Analytics service.

ApplicationInsightsAnalytics

The Analytics service is available directly on Microsoft Azure portal. The Analytics is on its on page — you won’t be on Azure portal anymore. The website is very well done. You will see an easy access to the documentation, on the left side of the page is some commands you can use and in the main part you have a huge textbox where you will write you queries. The Analytics service is a query tool to get information from Application Insights. You can create queries, save them, generate tables of data or graphics all in this webpage.

For example, I have some Azure WebJobs that collect the duration of the jobs. The output desired is to see the time that a job take and if the performance are stable across every execution but also in every day. This is possible with Analytics. The syntax is not really easy to catch on and I won’t give all information in this article.

customEvents
| where name == "JobPerformance"
| where timestamp >= ago(14d)
| extend d=parsejson(customDimensions)
| extend jobName = d.JobName
| where jobName == "CleanExpiredOrders"
| extend m=parsejson(customMeasurements)
| extend tt = todouble(m.JobElapsedTimeInMs)
| summarize  percentiles(tt, 25, 50, 75, 95) by bin(timestamp,1d)

The first line is about on “what” we will query. I have create a custom query with the name “JobPerformance” that I use to run on each type of WebJob I have. The second line filter down to that telemetry — only WebJob. The third line filter even more to get just the last 14 days. The two next lines open the customDimensions which are properties that you create during the write of the telemetry. In my case, this is an object with some properties, the one I need it the JobName. I need it because I want a graph in time for a specific job named “CleanExpiredOrders”. Finally, I need to get from that custom event some custom measurements. I need the property I created named “JobElapsedTimeInMs”. Once all information is collected, the idea is to bucket the data in percentile to see if we have some stable performances across queries. This is done by summarizing with the percentiles keyword. Since we want to get the information per day, we select a bin per day.

This gives in few seconds a table with all the data.

AnalyticGridResults

Even more useful we can graph the output:
GraphAnalytics

Microsoft Application Insight has just got a new boost with the Analytics page. It’s now possible to query the way you want your data but also your custom data in whatever the structure you decided to save them.