Overview
This section describes how to develop and deploy microservices on top of Cumulocity IoT using the Microservice SDK for C#, and it contains:
- Prerequisites – Development and runtime requirements you must develop and run C# microservices.
- Hello world tutorial – Step-by-step instructions to develop and deploy your first C# microservice.
- Developing microservices - Information about this SDK and detailed C# API reference.
- Troubleshooting – Some identified common problems and their solutions.
To develop a microservice using the SDK for C#, the starting point is our Hello world tutorial.
If you face any issue or need technical support, please refer to the Software AG Tech Community. You will find there many useful questions and answers.
Upgrading the SDK
The latest supported SDK is based on .NET Core 3.1 and Visual Studio 2019 is required for supporting it. To migrate your current version (for example 9.20.0 or 1004.12.0) to a new one of the SDK, update all dependencies to the latest version, for example, 1006.6.0, and update the project to .Net SDK 3.1. The developer who is upgrading an existing project to the latest version of SDK (1006.6.0) must follow the offical Microsoft guidelines about the code changes to be done for upgrading.
For new projects, you shall use a new bundle of building scripts, and it is recommended to use Cumulocity.SDK.Microservices v1006.6.0 based on .Net Core 3.1.
Development prerequisites
To use the C# client libraries for development, you must install .NET Core SDK for your development platform such as Windows or Linux (version 3.1 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 “3.1.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 Linux systems, follow the general installation on the Get Started with Docker webpage.
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.
- .NET Core SDK - Includes tools and libraries to build .NET Core applications.
- .NET Core Runtime - Required to run .NET Core applications.
Windows system requirements
- Powershell (at least Version 6 or Core)
- .NET Core SDK (at least Version 3.1)
- Docker for Windows (at least Version 17.06)
Linux system requirements
- .NET Core SDK (at least Version 3.1)
- Docker (at least Version 17.06)
- Mono (at least Version 6.8.0)
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.
The SDK is based on the package Cumulocity.SDK.Microservices
and it has a dependency on:
- Cumulocity.AspNetCore.Authentication.Basic - a package wrapper around the Basic Authentication for Microsoft ASP.NET Core Security which ensures adding basic authentication to Asp.Net Core.
Hello world tutorial
This example provides a step-by-step guide to develop a simple microservice in Cumulocity IoT. 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 3.1.
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.8 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 on Windows
In Windows systems, Powershell is installed by default. 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 https://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, for example, 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-win-dev-latest folder.
cd microservicesdk-win-dev-latest
Verify the version of your .Net tools.
dotnet --version
Make sure to use the correct SDK version - 3.1.200 or define which .NET Core SDK version is used when you run .NET Core CLI commands as seen in the terminal output above.
dotnet new globaljson --sdk-version 3.1.200
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 IoT platform as described in the Deployment section.
Running the microservice locally
In order to test the microservice calls to Cumulocity IoT, you can run the Docker container locally.
The microservice must be deployed to verify calls from Cumulocity IoT.
To run a microservice which uses Cumulocity IoT API locally you need the following:
- URL address of the Cumulocity IoT host of your tenant
- Authorization header = “Basic <Base64(<username>:<password>)>”
- Tenant - tenant ID
You may also install the cURL utility. There are several ways to install it on Windows:
- Install it using Chocolatey.
- Install it with a one-click installer.
- Using official cURL binaries.
Assuming that Chocolatey is installed:
choco install curl
Step 1 - Create the 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 the 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 the 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 the microservice is running
Now you can verify if your application is running by executing
curl -H "Authorization: <AUTHORIZATION>" \
<URL>/service/hello/weatherforecast
The expected result is like:
[
{
"date": "2020-07-14T10:39:06.3395082+00:00",
"temperatureC": 27,
"temperatureF": 80,
"summary": "Hot"
},
{
"date": "2020-07-15T10:39:06.3408548+00:00",
"temperatureC": 40,
"temperatureF": 103,
"summary": "Cool"
},
{
"date": "2020-07-16T10:39:06.3408577+00:00",
"temperatureC": 18,
"temperatureF": 64,
"summary": "Balmy"
}
]
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": "https://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 IoT 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 https://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 General aspects > Microservice utility tool in this 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:
- URL address of the Cumulocity IoT host of your tenant
- Username and password to log in with
- An application created on the platform
- ZIP build from previous steps for deployment
Configure the settings.ini file as follows:
[deploy]
username=<tenant>/<user>
password=<password>
url=<tenanturl>
appname=sample_application
deploy.ps1
- The script looks for a settings.ini file in the same directory. If found, it uses the credentials and tenant URL from that file.
- If settings.ini is not found, an error is shown.
./deploy.ps1
Call the script with the .ini name
- Loads the credentials and tenant URL from settings_alternativ.ini.
- If settings_alternative.ini is not found, an error is shown.
./deploy.ps1 -f settings.ini
- Merge the given arguments and ini configuration. Parameters from the file are overwritten by explicitly defined parameters.
./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;
using Microsoft.AspNetCore;
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:
- initializes a new instance of the WebHostBuilder class with pre-configured defaults
- specifies Kestrel as the server to be used by the web host
- configures the LoggerFactory
- specifies the class with the
UseStartup<Startup>
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.AddControllers(options => options.EnableEndpointRouting = false);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
app.UseAuthentication();
app.UseBasicAuthentication();
app.UseMvcWithDefaultRoute();
}
}
Startup.cs responsibilities:
- Setup configuration in the Startup constructor
- Setup dependency injection in ConfigureServices
- Setup the middleware pipeline in Configure
The Dockerfile file created by create.ps1 contains:
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS runtime
WORKDIR /app
COPY ./publish/Web ./
ENV SERVER_PORT 4700
ENTRYPOINT ["dotnet", "api.dll"]
It also defines what goes on in the environment inside a container:
- Sets the working directory
- Copies all from an application directory to the working directory
- Sets the environment variable, in this case SERVER_PORT
- Specifies what executable to run when the container starts
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:
- Alarm - AlarmApi
- AuditRecord - AuditRecordApi
- CepModule - CepApi
- Operation - DeviceControlApi
- Event - EventApi
- ExternalID - IdentityApi
- Binary - BinariesApi
- ManagedObject - InventoryApi
- Measurement - MeasurementApi
For further information, refer to Device integration.
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 Device integration.
Building and deploying on Linux
use the wget command to download the script file to build a “Hello World” application.
$ sudo wget https://resources.cumulocity.com/cssdk/releases/microservicesdk-lin-dev-latest.zip
The “latest” can be replaced by the version number, for example, 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
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:
- Clean
- Build
- DotnetPublish
- SingleDockerImage
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 https://localhost:8999/weatherforecast
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 IoT 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 General aspects > Packing in this 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:
- URL address of the Cumulocity IoT host of your tenant
- username and password to log in with
- application name created on the platform
- ZIP build from previous steps for deployment
The settings.ini file contains:
[deploy]
username=<tenant>/<user>
password=<password>
url=<tenanturl>
appname=sample_application
- Call deploy.sh
- The script looks for a settings.ini in the same directory. If found, uses the credentials and tenant URL from that file.
- If settings.ini is not found, an error is shown.
$ ./deploy.sh
- Call the script with the .ini name
- Loads the credentials and tenant URL from settings_alternativ.ini.
- If settings_alternative.ini is not found, an error is shown.
$ ./deploy.sh -f settings.ini
- Merge the given arguments and ini configuration. The parameters from the file are overwritten by explicitly defined parameters.
deploy.sh -an hello-world -f settings_alternative.ini
$ ./deploy.sh -s <URL> -u <username> -p <password> -an hello-world -f settings.ini
Improving the microservice
Now that you have done your first steps, check out the section Developing microservices to find out what else can be implemented. Review also the extended example in the GitHub repository to learn more features of the microservice SDK and REST API.
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 must include a Configure
method to create the app’s request processing pipeline, and can optionally include a ConfigureServices
method to configure the app’s services.
There are two possible deployment types on the platform:
- Hosted deployment - The default for microservices and it is the suggested one for typical use cases.
- External/legacy deployment - Requires custom installation of the platform and agent.
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, IWebHostEnvironment 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 IoT 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);
}
The Configuration
object represents a set of key/value application configuration properties.
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
In this way microservices 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 runs |
C8Y_MICROSERVICE_ISOLATION | Isolation level |
C8Y_TENANT | Application user tenant (available only for PER_TENANT isolation) |
C8Y_USER | Application username (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 must 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 IoT.
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 your 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 3.1, 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, IWebHostEnvironment 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.AddControllers(options => options.EnableEndpointRouting = false);
//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, IWebHostEnvironment 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 must 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();
}
Each microservice exposes the endpoint /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, IWebHostEnvironment 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.
- Clean - Cleans the specified directory, deletes files.
- Build – Restores the dependencies and tools of projects and the task builds all projects, but before that it does the cleaning task.
- Publish – The task compiles the application, reads through its dependencies specified in the project file, and publishes the resulting set of files to a directory. The result will be placed in the output folder
- Docker-Build - Will save an image and an application manifest to images/multi/image.zip. Inside the root folder of your application, the so-called “application manifest” is stored in a file named cumulocity.json. The ZIP archive contains image.tar and cumulocity.json.
- Single-DockerImage - Will save an image and an application manifest to images/single/image.zip. Inside the root folder of your application, the so-called “application manifest” is stored in a file named cumulocity.json. The ZIP archive contains image.tar and cumulocity.json.
- Docker-Run - Creates a new container using default settings.
Authentication
The C# SDK also supports OAuth tokens. Authentication with OAuth is based on cookies technology, so the token must be read from the request cookie header. Refer to General aspects > Security for more details.
You can find a microservice example in our GitHub repository to learn how to use OAuth tokens. The microservice configures REST endpoints (GET, PUT, POST, DELETE) using basic and OAuth authentication schemes, but it does not add any business logic as it is just for demonstration. The configuration is done by runtime and adds the authentication. Finally, a web port is created and starts listening on the specified port in the Properties/launchSettings.json file.
Note that when a request authenticated with OAuth arrives to the microservice, it must be verified using a token saved in the authorization cookie and with the X-XSRF-TOKEN header. A request in user scope with OAuth must pass the cookie and header to the platform.
The UseAuthentication
method adds a single authentication middleware component which is responsible for automatic authentication and the handling of remote authentication requests.
To employ OAuth you must add the following code to the ConfigureServices
method in the Startup.cs file:
services.AddCumulocityAuthenticationAll(Configuration);
This adds support to OAuth and Basic authentication – by default OAuth is being used.
public static IServiceCollection AddCumulocityAuthenticationAll(this IServiceCollection services, IConfiguration configuration)
{
services.AddAuthentication(OAuthAuthenticationDefaults.AuthenticationScheme)
.AddBasicAuthentication<BasicCredentialVerifier>()
.AddOAuthAuthentication<OAuthVerifier>();
return services;
}
Moreover, you must register the authentication middleware. To do so, call the UseAuthentication
method within the Configure
method in the Startup.cs file as shown below:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseAuthentication(); // to employ OAuth
app.UseMvcWithDefaultRoute();
}
AddBasicAuthentication
method if you want OAuth-based authentication for your microservice.To use multiple authentication schemes, the Controllers class must be decorated as follows:
[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = AllSchemes)]
public class ValuesController : Controller
{
public const string AllSchemes =
BasicAuthenticationDefaults.AuthenticationScheme + ", " +
OAuthAuthenticationDefaults.AuthenticationScheme;
// REST endpoints methods ...
}
Refer to Authorize with a specific scheme in ASP.NET Core for more details.
Testing the microservice with OAuth tokens
Third-party tools such as Postman can be used to test your REST API. You need the cookie and X-XSRF-TOKEN headers which must be captured from the platform (for example by watching network requests in the browser). Note that the token expires within few minutes as configured by the administrator. Make sure you test this feature before the token expires. The cURL equivalent to test the microservice example is as follows:
curl 'http://<URL>/api/values' \
-H 'X-XSRF-TOKEN: CHMkPIteHmTiihocpPwZ' \
-H 'Cookie: authorization=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOm51bGwsImlzcyI6Im9hdXRoLWp3a3MubGF0ZXN0LnN0YWdlLmM4eS5pbyIsImF1ZCI6Im9hdXRoLWp3a3MubGF0ZXN0LnN0YWdlLmM4eS5pbyIsInN1YiI6ImRvbWluaWthIiwidGNpIjoiMjY0NjBjNGItZWJmNy00OGRlLWE1ZmMtYzkxZGJhZWM3MWFlIiwiaWF0IjoxNTk3ODk4OTcyLCJuYmYiOjAsImV4cCI6MTU5NzkwMjU3MiwidGZhIjpmYWxzZSwidGVuIjoidDU5MDA4IiwieHNyZlRva2VuIjoiQ0hNa1BJdGVIbVRpaWhvY3BQd1oifQ.laooVzd3jS2Vj9Pj86To1M1ONl7_m7bPX0cGH8dYnUltDu5jxwNjpaCy7L8Hei59VYB7euGO7qn0LeqNZGt9Nw; XSRF-TOKEN=CHMkPIteHmTiihocpPwZ' \
--compressed
You can get the headers from the Network tab and replace them in the example above to hit the endpoint which returns 200 OK response code.
Troubleshooting
Some common problems and their solutions have been identified and documented below.
After deploying my microservice, requests to any endpoint return an error message “Microservice not available Connection refused”
After uploading the microservice, the internal deployment and container run may take a couple of minutes. Once completed, the error message will disappear.
I get a message error saying “The current .NET SDK does not support targeting .NET Core 3.1.”
This error will appear if you try running the SDK on Visual Studio 2017 or lower. The current version of the Cumulocity IoT Microservices SDK (1006.6.0) is compatible with .NET Core 3.1 and Visual Studio 2019, and it is a prerequisite for developing applications.