Microservice SDK for C#

This section describes how to develop and deploy microservices on top of Cumulocity using the Microservice SDK for C#.

Overview

This section describes how to develop and deploy microservices on top of Cumulocity using the Microservice SDK for C#, and it contains:

To develop a microservice using the SDK for C#, the starting point is our Hello world tutorial.

Info: You can develop microservices for Cumulocity with any IDE and build tool that you prefer, but this guide focuses on Cake (C# Make) and Visual Studio.

Development prerequisites

To use the C# client libraries for development, you need to install .NET Core SDK for your development platform such as Windows or Linux (version 2.2 of the .NET Core SDK). Note that .NET Core Runtime and .NET Core SDK are different things.

Use the following command to verify the version of your .NET Core SDK:

$ dotnet --info

The output must show a version number later than “2.2.0” to implement the basic examples.

You also need a local Docker installation. Review the information at Docker for Windows: What to know before you install and install Docker For Windows.

For .NET development, Microsoft provides a number of different images depending on what you are trying to achieve.

Depending on what you want to do, you need either the .NET Core SDK or the .NET Core Runtime.

Windows system requirements

Linux system requirements

Runtime prerequisites

The most important requirement is an installation of Docker 17.06 or later.

The recommended image for production is mcr.microsoft.com/dotnet/core/aspnet:<version> AS runtime as it contains the .NET Core (runtime and libraries) and it is optimized for running .NET Core applications.

Important: Cumulocity supports only Linux containers. Nevertheless, for development – should you wish to do so – it is possible to use Windows containers.

The SDK is based on the package Cumulocity.SDK.Microservices and it has a dependency on:

Hello, world!

Overview

This example provides a step-by-step guide to develop a simple microservice in Cumulocity. It uses Cake (C# Make), which is a cross-platform build automation system.

To start building .NET apps, you just need to download and install the .NET SDK. Follow the instructions on the download page for the last stable release or alternatively you can also try using 2.2.

If you use Linux, visit the MonoDevelop website for download packages and more details about our cross-platform IDE. Follow the instructions on the download page for the last stable release or alternatively you can also try using 6.0 or higher version of mono IDE. Note, that Mono-devel is required to compile code.

The initial script was used to create a demo, which makes it easier to create an example microservice project with dependency management and then deploy it on the server. The script attempts to download the package from the sources listed in the project file, and next a reference is added to the appropriate project file. In addition, the script creates the appropriate Dockerfile to take into account the naming of projects. Next it will create a Docker image based on a Dockerfile.

The application created in this way uses the ASP.NET Web API framework to create a web API. The API runs on an isolated web server called Kestrel and as a foreground job, which makes it work really well with Docker.

Building and deploying Hello World on Windows

Building and deploying the “Hello, World” microservice on Windows is similar to the way it is done for Linux. For Windows, Powershell is installed by default and that is why we use it. For Linux, Powershell Core can be considered as an alternative.

Download the script file to build a “Hello World” application. Manage the version of scripts and replace latest to the right version number.

Invoke-WebRequest  http://resources.cumulocity.com/cssdk/releases/microservicesdk-win-dev-latest.zip -OutFile microservicesdk-win-dev-latest.zip

The latest can be replaced by the version number, e.g. microservicesdk-lin-dev-{X.X.X}.zip.

Once you have downloaded the source, unzip the file.

Expand-Archive c:\microservicesdk-win-dev-latest.zip -DestinationPath c:\microservicesdk-win-dev-latest

Change the current folder and navigate to the _microservicesdk_ folder.

cd microservicesdk-win-dev-latest

Make sure to use the correct SDK version - 2.2.110 or define which .NET Core SDK version is used when you run .NET Core CLI commands.

dotnet new globaljson --sdk-version 2.2.110

Open the Dockerfile inside the microservicesdk-win-dev-latest folder. Make sure that it points to the correct Docker image of .Net Core.

The first line of the Dockerfile shall look like:

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS runtime

Run the script create.ps1 to create a sample project, provide the name of the project and the API application.

./create.ps1

Execute the bootstrapper script to build the application and an image from a Dockerfile.

./build.ps1

After a successful build you will be provided with a ZIP file in the target directory. The ZIP can be deployed to the Cumulocity platform as described in the Deployment section.

Running the microservice locally

In order to test the microservice calls to Cumulocity, you can run the Docker container locally.

The microservice must be deployed to verify calls from Cumulocity.

To run a microservice which uses Cumulocity API locally you need the following:

You may also install the cURL utility. There are several ways to install it on Windows:

Assuming that Chocolatey is installed:

choco install curl

Step 1 - Create application

If the application does not exist, create a new application on a platform:

POST <URL>/application/applications

HEADERS:
  "Authorization": "<AUTHORIZATION>"
  "Content-Type": "application/vnd.com.nsn.cumulocity.application+json"
  "Accept: application/vnd.com.nsn.cumulocity.application+json"

BODY:
  {
    "name": "<APPLICATION_NAME>",
    "type": "MICROSERVICE",
    "key": "<APPLICATION_NAME>-microservice-key"
  }

Example:

curl -X POST -s \
-d '{"name":"hello-microservice-1","type":"MICROSERVICE","key":"hello-microservice-1-key"}' \
-H "Authorization: <AUTHORIZATION>" \
-H "Content-Type: application/vnd.com.nsn.cumulocity.application+json" \
-H "Accept: application/vnd.com.nsn.cumulocity.application+json" \
"<URL>/application/applications"

Example response:

{
    "availability": "PRIVATE",
    "id": "<APPLICATION_ID>",
    "key": "<APPLICATION_NAME>-microservice-key",
    "manifest": {
        "imports": [],
        "noAppSwitcher": true
    },
    "name": "<APPLICATION_NAME>",
    "owner": {
        "self": "...",
        "tenant": {
            "id": "..."
        }
    },
    "requiredRoles": [],
    "roles": [],
    "self": "..",
    "type": "MICROSERVICE"
}

If the application has been created correctly, you can get the application ID from the response.

Step 2 - Acquire the microservice bootstrap user

GET <URL>/application/applications/<APPLICATION_ID>/bootstrapUser

HEADERS:
  "Authorization": <AUTHORIZATION>
  "Content-Type": application/vnd.com.nsn.cumulocity.user+json

Example response:

HTTP/1.1 200 Ok
Content-Type: application/vnd.com.nsn.cumulocity.user+json
{
  "tenant": "...",
  "name": "...",
  "password": "..."
}

Step 3 - Run microservice locally

The image is already added to the local Docker repository during the build. List all the Docker repository images available:

$ docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
api                 latest              a8298ed10cd9        16 hours ago        258MB

After you find the image in the list, run the Docker container for the microservice by providing the baseurl and the bootstrap user credentials:

$ docker run -e C8Y_BASEURL=<URL> -e C8Y_BOOTSTRAP_TENANT=<BOOTSTRAP_TENANT> -e C8Y_BOOTSTRAP_USER=<BOOTSTRAP_USERNAME> -e C8Y_BOOTSTRAP_PASSWORD=<BOOTSTRAP_USER_PASSWORD> -e C8Y_MICROSERVICE_ISOLATION=MULTI_TENANT -i -t <DOCKER_REPOSITORY_IMAGE>:<TAG>

Step 4 - Subscribe to microservice

POST <URL>/tenant/tenants/<TENANT_ID>/applications

HEADERS:
  "Authorization": "<AUTHORIZATION>"

BODY:
  { "application":
      { "id": "<APPLICATION_ID>" }
  }

Example:

curl -X POST -d '{"application":{"id": "<APPLICATION_ID>"}}'  \
-H "Authorization: <AUTHORIZATION>" \
-H "Content-type: application/json" \
 "<URL>/tenant/tenants/<TENANT_ID>/applications"

Step 5 - Verify if microservice is running

Now you can verify if your application is running by executing

curl -H "Authorization: <AUTHORIZATION>" \
  <URL>/service/hello/api/values

The expected result is:

["value1","value2"]

Running the application within the IDE

It is possible to check if the application communicates with the platform by defining relevant environmental variables in launchSettings.json. This file sets up the different launch environments that Visual Studio can launch automatically. Here is a snippet of the default launchSettings.json.

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:3288/",
      "sslPort": 0
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "Api": {
      "commandName": "Project",
      "environmentVariables": {
        "SERVER_PORT": "47000",
        "C8Y_MICROSERIVCE_ISOLATION": "PER_TENANT",
        "C8Y_BASEURL": "<URL>",
        "C8Y_BASEURL_MQTT": "",
        "C8Y_TENANT": "",
        "C8Y_PASSWORD": "",
        "C8Y_USERNAME": "",
        "C8Y_BOOTSTRAP_TENANT": "<tenant>",
        "C8Y_BOOTSTRAP_USERNAME": "<username>",
        "C8Y_BOOTSTRAP_PASSWORD": "<password>"
      }
    }
  }
}

Microservice package and deploy

Cumulocity provides you with an utility tool for easy microservice packaging, deployment and subscription. The script requires running Docker and can be found in the ZIP file microservicesdk-win-dev-latest.zip. Use the following command to download it.

Invoke-WebRequest  http://resources.cumulocity.com/cssdk/releases/microservicesdk-win-dev-latest.zip -OutFile microservicesdk-win-dev-latest.zip

To show all the functions are available, type

./microservice.ps1 --help

For further information refer to Microservice package and deploy in the Reference guide.

Deployment

In addition, there is a deploy.ps1 script that uses credentials stored locally. Run the script in order to deploy the application. You must provide the correct URL and credentials in the settings.ini file.

To deploy a microservice application on the platform, you need the following:

Configure the settings.ini file as follows:

[deploy]
username=<tenant>/<user>
password=<password>
url=<tenanturl>
appname=sample_application

deploy.ps1

./deploy.ps1

Call the script with the .ini name

./deploy.ps1 -f settings.ini
./deploy.ps1 -an hello-world -f settings_alternative.ini
./deploy.ps1 -s <siteurl> -u <username> -p <password>  -an hello-world -f settings.ini

Improving the microservice

The application starts executing from the entry point public static void Main() in Program class where the host for the application is created. The following shows an example of a program created by create.sh.

namespace api
{
	using System.Net;
	public class Program
  {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }
        public static IWebHost BuildWebHost(string[] args) =>

                WebHost.CreateDefaultBuilder(args)
                .ConfigureLogging((hostingContext, logging) =>
                {
                    logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                    logging.AddConsole();
                    logging.AddDebug();
                })
                .UseStartup<Startup>()
                .UseKestrel(options =>
                {
                    var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
                    var port = Environment.GetEnvironmentVariable("SERVER_PORT");
                    int portNumber = 8080;
                    if (Int32.TryParse(port, out portNumber))
                    {
                        options.Listen(IPAddress.Parse("0.0.0.0"), portNumber);
                    }
                    else
                    {
                        options.Listen(IPAddress.Parse("0.0.0.0"), 1);
                    }
                })
                .Build();
	}
}

The method BuildWebHost performs the following tasks:

An example application must include Startup class. As the name suggests, it is executed first when the application starts.

public class Startup
{
    public IConfiguration Configuration { get; }

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCumulocityAuthentication(Configuration);
        services.AddPlatform(Configuration);
        services.AddSingleton<IApplicationService, ApplicationService>();
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());

    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.UseAuthentication();
        app.UseBasicAuthentication();
        app.UseMvcWithDefaultRoute();
    }
}

Startup.cs responsibilities:

The Dockerfile created by create.ps1 contains:

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS runtime
WORKDIR /app
COPY ./publish/Web ./
ENV SERVER_PORT 4700
ENTRYPOINT ["dotnet", "api.dll"]

The Dockerfile defines what goes on in the environment inside a container:

Platform API

It is possible to use the C# REST SDK as an extension. A developer can use it to perform basic operations against the platform. For hosted deployment, most of the properties are provided by the platform.

The API provides the following services:

For further information, refer to the Device SDK guide.

C# MQTT SDK

It is possible to use the C# MQTT SDK as a nuget-package. A developer can use it to perform basic operations against the platform. For further information, refer to MQTT examples in the Device SDK guide.

Building and deploying on Linux

use the wget command to download the script file to build a “Hello World” application.

$ sudo wget  http://resources.cumulocity.com/cssdk/releases/microservicesdk-lin-dev-latest.zip

The “latest” can be replaced by the version number, e.g. microservicesdk-lin-dev-<X.X.X>.zip.

Once you have downloaded the source, unzip the file.

$ unzip microservicesdk-lin-dev-latest.zip -d  microservicesdk-latest

Change the current folder, to navigate to a microservicesdk folder.

$ cd microservicesdk-latest

Open the Dockerfile inside the microservicesdk-win-dev-latest folder. Make sure that it points to the correct Docker image of .Net Core.
The first line of the Dockerfile shall look like:

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS runtime

Run the script create.sh to create a sample project, provide the name of the project and the API application.

$ ./create.sh

Enter the solution name:
<demo>

Enter the name of a web API project:
<api>

For a working cake you need the build.sh or build.ps1 file to bootstrap cake and the build.cake file. build.sh and build.ps1 are bootstrapper scripts that ensure you have Cake and other required dependencies installed. The bootstrapper scripts are also responsible for invoking Cake. build.cake is the actual build script.

build.cake contains tasks representing a unit of work in Cake, and you may use them to perform specific work in a specific order:

Execute the bootstrapper script, to build the application and an image from a Dockerfile.

$ ./build.sh

Launch the Docker container with the command:

$ docker run -p 8999:4700 <imagename>:latest

Check the status of the application that is running inside the Docker container.

$ curl http://localhost:8999/api/values

In order to deploy the application, run the deploy script. You must provide the correct URL and credentials in this script.

Microservice package and deploy

Cumulocity provides you with an utility tool for easy microservice packaging, deployment and subscription. The script requires running Docker and can be found in cumulocity-examples:

Next, add execution permissions

$ chmod +x microservice

To show all options, type

$ ./microservice help

For further information, refer to Microservice package and deploy in the Reference guide.

Deployment

In addition, there is a deploy.ps1 script that uses credentials stored locally. In order to deploy the application run the deploy script. You must provide the correct URL and credentials in the settings.ini file.

To deploy a microservice application on an environment you need the following:

The settings.ini file contains:

[deploy]
username=<tenant>/<user>
password=<password>
url=<tenanturl>
appname=sample_application
$ ./deploy.sh
$ ./deploy.sh -f settings.ini
$ ./deploy.sh -s <URL> -u <username> -p <password> -an hello-world -f settings.ini

Developing microservices

The SDK is based on ASP.NET Core, a cross-platform, high-performance, open-source framework for building modern, cloud-based, Internet-connected applications. ASP.NET Core apps use a Startup class, which is named Startup by convention. The Startup class

This document describes microservice SDK features, services, configuration files, logging and Cake (C# Make).

There are two possible deployment types on the platform:

For development and testing purposes, you can deploy a microservice on a local Docker container. The process is described below.

Microservice security

The Configure method is used to specify how the application responds to HTTP requests. The request pipeline is configured by adding middleware components to an IApplicationBuilder instance.

The UseAuthentication method adds a single authentication middleware component which is responsible for automatic authentication and the handling of remote authentication requests. It replaces all of the individual middleware components with a single, common middleware component. Since ASP.NET Security does not include Basic Authentication middleware, we must add custom Basic Authentication middleware.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
	app.UseAuthentication();
	app.UseBasicAuthentication();
}

Next, each authentication scheme is registered in the ConfigureServices method of Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
	services.AddCumulocityAuthentication(Configuration);
}

Platform

The root interface for connecting to Cumulocity from C# is called “Platform”. It provides access to all other interfaces of the platform, such as the inventory. In its simplest form, it is instantiated as follows.

To enable service providers to run microservices together with the platform, it is required to execute the registration procedure. During this process each microservice receives a dedicated bootstrap user to ensure that the microservice can be identified by the platform and can only access allowed resources.

The platform is registered with the dependency injection container. Services that are registered with the dependency injection (DI) container are available to the controllers.

public void ConfigureServices(IServiceCollection services)
{
	// ...
	 services.AddPlatform(Configuration);
}

where Configuration represents a set of key/value application configuration properties.

public IConfiguration Configuration { get; }

public Startup(IConfiguration configuration)
{
	Configuration = configuration;
}

In this way microservices should receive very basic configuration. Besides the properties related to the isolation level, the microservices will receive the following variables:

Variable Description
C8Y_BASEURL URL which points to the core platform
C8Y_BASEURL_MQTT URL which points to the core platform with MQTT protocol
SERVER_PORT Port on which the microservice should run
C8Y_MICROSERVICE_ISOLATION Isolation level
C8Y_TENANT Application user tenant (available only for PER_TENANT isolation)
C8Y_USER Application user name (available only for PER_TENANT isolation)
C8Y_PASSWORD Application user password (available only for PER_TENANT isolation)
C8Y_BOOTSTRAP_TENANT Bootstrap user to get platform subscriptions
C8Y_BOOTSTRAP_USERNAME Bootstrap user to get platform subscriptions
C8Y_BOOTSTRAP_PASSWORD Bootstrap user to get platform subscriptions

Role-based authorization

Once a user has been authenticated, the next step is to check if the user is authorized to do what they are trying to do.

[Authorize]
public IActionResult Index()
{
  return View();
}

The authorize attribute is used to protect an action in a controller from being called. If no conditions have been specified, any user who is authenticated is able to perform the action.

To be more specific and allow only members of a certain role (in this case the ROLE_APPLICATION_MANAGEMENT_READ role) to perform actions in a controller, add the role as a requirement to the attribute like this:

[Authorize(Roles = "ROLE_APPLICATION_MANAGEMENT_READ")]
public class HomeController : Controller
{
    public HomeController(Platform platform)
    {

    }
}

Accessing HTTPContext in ASP.net Core

In earlier versions of .Net Core, IHttpContextAccessor was automatically registered. This was removed. You need to register it manually if you intend to use it inside services. IHttpContextAccessor is only intended for accessing the HttpContext in locations where it is not directly available.

public void ConfigureServices(IServiceCollection services)
{
    // ...
     services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}

Building a scheduled task

In order to add a new scheduled task, add it as shown in the example below. All scheduled tasks should look similar to:

public class SomeTask : IScheduledTask
{
    public string Schedule => "0 1/6 * * *";

    public async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        //...
    }
}

where the Schedule property is a cron expression and the ExecuteAsync() method is the work to execute asynchronously.

Then you can easily register scheduled tasks:

public void ConfigureServices(IServiceCollection services)
{
    // ...
    // Add scheduled tasks
    services.AddSingleton<IScheduledTask, SomeTask>();
}

Microservice subscription

The following section refers to the user management as described under General aspects of microservices in Cumulocity.

This SDK has a task CurrentApplicationSubscriptionsTask which only fetches a list of all subscriptions. The CurrentApplicationSubscriptionsTask is the IScheduledTask implementation which runs every hour:

services.AddSingleton<IScheduledTask, CurrentApplicationSubscriptionsTask>();

services.AddScheduler((sender, args) =>
{
    Debug.Write(args.Exception.Message);
    args.SetObserved();
});

It should get all subscriptions and make it available for any other part of my application to work with.

As you can see, the AddScheduler takes a delegate that handles unobserved exceptions. In our scheduler code, TaskFactory.StartNew() is used to run the task’s code. If there is an unhandled exception, you won’t see this exception. Therefore, you may want to do some logging. This is normally done by setting TaskScheduler.UnobservedTaskException, that is global for this case so added our own to specifically catch scheduled tasks unhandled exceptions.

The SDK allows you to subscribe to the event application subscriptions changed.

Start by getting the singleton instance of the hub:

var hub = MessageHub.Instance;

You can now use the hub to subscribe to any publication of a given type, in our case OnChangedSubscription.

public class HomeController : Controller
{
    private readonly MessageHub _hub;
    private readonly Guid _subscriptionToken;

    public HomeController(Platform platform,MessageHub hub)
    {
        _hub = hub;
        _subscriptionToken =   _hub.Subscribe<List<ChangedSubscription>>(OnChangedSubscription);
    }

    private void OnChangedSubscription(List<ChangedSubscription> obj)
    {

    }
}

Program class

In ASP.NET Core 2.0, the Program class is used to setup the IWebHost. This is the entry point to our application. The main method creates a host, builds and then runs it. The host then listens for HTTP requests.

There are multiple ways to configure the application.

Simplified configuration

By using the extension to IWebHost - UseMicroserviceApplication the configuration with Startup can be simplified.

UseMicroserviceApplication has an optional parameter by default true. This parameter indicates whether to create a health point.

public class Program
{
	public static void Main(string[] args)
	{
		BuildWebHost(args).Run();
	}

	public static IWebHost BuildWebHost(string[] args) =>
		WebHost.CreateDefaultBuilder(args)
			.UseKestrel(options =>
			{
				var port = Environment.GetEnvironmentVariable("SERVER_PORT");
				options.Listen(IPAddress.Parse("0.0.0.0"), Int32.TryParse(port, out var portNumber) ? portNumber : 8080);
			})
			.ConfigureLogging((hostingContext, logging) =>
			{
				logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
				logging.AddConsole().SetMinimumLevel(LogLevel.Information);
			})
			.UseMicroserviceApplication()
			.UseStartup<Startup>()
			.Build();
}

The minimum form of the Startup class may look like as follows:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
      app.UseMvcWithDefaultRoute();
		}
}

Advanced configuration

In this case, the entire configuration must be carried out manually:

public class Program
{
	public static void Main(string[] args)
	{
	    BuildWebHost(args).Run();
	}

	public static IWebHost BuildWebHost(string[] args) =>

	    WebHost.CreateDefaultBuilder(args)
	    .UseKestrel()
	    .ConfigureLogging((hostingContext, logging) =>
	    {
	        logging.SetMinimumLevel(LogLevel.Warning);
	        logging.AddConsole();
	        logging.AddDebug();
	    })
	    .UseStartup<Startup>()
	    .UseKestrel(options =>
	    {
	        var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
	        var port = Environment.GetEnvironmentVariable("SERVER_PORT");
	        int portNumber = 8080;

	        if (Int32.TryParse(port, out portNumber)){
	            options.Listen(IPAddress.Parse("0.0.0.0"), portNumber);
	        }
	        else {
	            options.Listen(IPAddress.Parse("0.0.0.0"), 1);
	        }
	    })
	    .Build();
}

The Startup class may look like the following code:

public class Startup
{
	ILogger _logger;

	public Startup(IConfiguration configuration, ILoggerFactory loggerFactory)
	{
		Configuration = configuration;
		_logger = loggerFactory.CreateLogger<Startup>();
	}

	public IConfiguration Configuration { get; }

	public void ConfigureServices(IServiceCollection services)
	{
		_logger.LogDebug($"Total Services Initially: {services.Count}");

		services.AddMemoryCache();
		services.AddPlatform(Configuration);
		ConfigureServicesLayer(services);
		services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

		// Add scheduled tasks & scheduler
		services.AddSingleton<IScheduledTask, TimerTask>();
		services.AddScheduler((sender, args) =>
		{
			Debug.Write(args.Exception.Message);
			args.SetObserved();
		});

		//MVC
		services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
		//services.Replace(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(TimedLogger<>)));
	}
	public virtual void ConfigureServicesLayer(IServiceCollection services)
	{
		services.AddCumulocityAuthentication(Configuration);
		services.AddSingleton<IApplicationService, ApplicationService>();
	}

	public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
	{
		app.UseAuthentication();
		app.UseBasicAuthentication();
		app.UseMvcWithDefaultRoute();
	}
}

Health check

Health monitoring can allow near-real-time information about the state of your containers and microservices. Health monitoring is critical to multiple aspects of operating microservices and is especially important when orchestrators perform partial application upgrades in phases.

For a service or web application to expose the health check endpoint, it has to enable the UseHealthChecks([url_for_health_checks]) extension method. This method goes at the WebHostBuilder level in the main method of the Program class of your ASP.NET Core service or web application, right after UseKestrel as shown in the code below.

public class Program
{
    public static void Main(string[] args)
    {
        BuildWebHost(args).Run();
    }

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .ConfigureLogging((hostingContext, logging) =>{})
            .UseStartup<Startup>()
            .UseKestrel(options =>{})
            .UseHealthChecks("/health")
            .Build();
}

The process works like this: each microservice exposes the endpoint e.g. /health. This endpoint is created by the library ASP.NET Core middleware. When this endpoint is invoked, it runs all the health checks that are configured in the AddHealthChecks method in the Startup class.

The UseHealthChecks method expects a port or a path. That port or path is the endpoint to use to check the health state of the service. For instance, the catalog microservice uses the path /health.

The basic flow is that you register your health checks in your IoC container. You register these health checks via a fluent HealthCheckBuilder API in your Startup‘s ConfigureServices method. This HealthCheckBuilder will build a HealthCheckService and register it as an IHealthCheckService in your IoC container.

Built-in platform health checks

The microservice is healthy if the platform is accessible via HTTP from the application. To check it, it is possible to use an action that is built-in.

.AddPlatformCheck();

After that, you add the health check actions that you want to perform in that microservice. These actions are basically dependencies on other microservices (HttpUrlCheck) or databases (currently SqlCheck* for SQL Server databases). You add the action within the Startup class of each ASP.NET microservice or ASP.NET web application.

Custom health check

It is also possible to make your own custom health check. However, to do that, derive from IHealthCheck and implement the interface. Below is an example of one that checks to make sure the C drive has at least 1 GB of free space.

public class CheckCDriveHasMoreThan1GbFreeHealthCheck : IHealthCheck
{
    public ValueTask<IHealthCheckResult> CheckAsync(CancellationToken cancellationToken = default(CancellationToken))
    {
        long freeSpaceInGb = GetTotalFreeSpaceInGb(@"C:\");
        CheckStatus status = freeSpaceInGb > 1 ? CheckStatus.Healthy : CheckStatus.Unhealthy;

        return new ValueTask<IHealthCheckResult>(HealthCheckResult.FromStatus(status, $"Free Space [GB]: {freeSpaceinGb}"));

    }

    private long GetTotalFreeSpaceInGb(string driveName)
    {
        foreach (DriveInfo drive in DriveInfo.GetDrives())
        {
            if (drive.IsReady && drive.Name == driveName)
            {
                return drive.TotalFreeSpace / 1024 / 1024 / 1024;
            }
        }
        throw new ArgumentException($"Invalid Drive Name {driveName}");
    }
}

Then in your ConfigureServices method, register the custom health check with adequate the lifetime of the service that makes sense for the health check and then add it to the AddHealthChecks registration that has been done before.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<CDriveHasMoreThan1GbFreeHealthCheck>();

    services.AddHealthChecks(checks =>
    {
        checks.AddCheck<CheckCDriveHasMoreThan1GbFreeHealthCheck>("C Drive has more than 1 GB Free");
    });

    services.AddMvc();
}

The following example combines built-in checking and custom checking:

public class Startup
{
    ILogger _logger;
    public Startup(IConfiguration configuration, ILoggerFactory loggerFactory)
    {
        Configuration = configuration;
        ...
    }

    public IConfiguration Configuration { get; }


    public void ConfigureServices(IServiceCollection services)
    {
        ...
        // Add framework services
        services.AddHealthChecks(checks =>
        {
            checks.AddPlatformCheck();
            checks.AddCheck("long-running", async cancellationToken =>
            {
                await Task.Delay(1000, cancellationToken);
                return HealthCheckResult.Healthy("I ran too long");
            });
        });

        ...
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        ...
        app.UseMvcWithDefaultRoute();
    }
}

Cake

Cake is a cross platform build automation system, and it is built on top of Roslyn and the Mono Compiler which uses C# as the scripting language to do things like compiling code, copy files/folders, running unit tests, compress files and build NuGet packages.

The cake script called build.cake has has the predefined tasks. These tasks represent a unit of work in Cake, and you use them to perform specific work in a specific order.