Microservice SDK for C#

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

Overview

Using Microservice SDK for C#, you can develop microservices in C#. This section explains:

The most simple starting point is the C# Hello World example.

Info: You can develop Cumulocity with any IDE and any build tool that you prefer, but the examples focus 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 the your development platform such as Windows, Linux or Mac (at least Version 2 of the .NET Core SDK). Note that .NET Core Runtime and .NET Core SDK are different things.

To verify the version of your .NET Core SDK, type

$ dotnet --info

The output must show a version number later than "2.0.0" to perform the basic examples.

For 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.

Whether you need the .NET Core SDK or the .NET Core runtime depends on what you want to do:

  • .NET Core SDK - includes tools and libraries to build .NET Core applications.

  • .NET Core Runtime - is required to run .NET Core applications.

Windows system requirements

  • Powershell (at least Version 6 or Core)
  • .NET Core SDK (at least Version 2.0)
  • Docker for Windows (at least Version 17.06)

Unix system requirements

  • .NET Core SDK (at least Version 2 .0)
  • Docker (at least Version 17.06)
  • Mono (at least Version 5.4.0)

Runtime prerequisites

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

Recommended as production image is microsoft/dotnet:-runtime: This image contains the .NET Core (runtime and libraries) and is optimized for running .NET Core apps.

Assumed, only Linux containers are supported. However, for the development itself it is possible to use Windows containers.

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

  • Cumulocity.AspNetCore.Authentication.Basic - a package wrapper around Bazinga.AspNetCore.Authentication.Basic Ensures adding basic authentication to Asp.Net Core in the right way because ASP.NET Core 2.0 introduced breaking changes to Authentication and Identity. From this version of the web framework Microsoft doesn't ship a Basic Authentication package with ASP.NET Core Security.

Hello, world!

Overview

This section provides an example of a C# 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.0.

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 5.4 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 Docker file to take into account the naming of projects. Next it will create a Docker image based on a Docker file.

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 "Hello World" on Windows is similar to the way it is done for Linux. For Windows, powershell is installed by default and that's why we use it.

Download a script file to build a "Hello World" application. Manage the version of scripts and replace X.X.X to the right version number.

Invoke-WebRequest  http://resources.cumulocity.com/cssdk/releases/microservicesdk-win-dev-latest.zip -OutFile microservicesdk-win-dev-X.X.X.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-X.X.X.zip -DestinationPath c:\microservicesdk-win-dev-X.X.X

Change the current folder and navigate to a microservicesdk folder.

cd microservicesdk-win-dev-X.X.X

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 Docker file.

./build.ps1

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

How to call the script

  • Call "deploy.ps1"
    • The script looks for a settings.ini 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.sh -s {siteurl} -u {username} -p {password}  -an hello-world -f settings.ini

The ini sample:

[deploy]
username=tenant/user
password=pass
url=someurl
appname=sample_application

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();
    }

}

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 Startup class with the UseStartup<TStartup>

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:

  • Setup configuration in the Startup constructor
  • Setup dependency injection in ConfigureServices
  • Setup the middleware pipeline in Configure

Dockerfile created by "create.ps1":

FROM microsoft/dotnet:2.0-runtime
WORKDIR /app
COPY ./publish/Web ./
ENV SERVER_PORT 4700
ENTRYPOINT ["dotnet", "api.dll"]

Dockerfile 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

Building and deploying Hello World on Linux

Download a script file to build a "Hello World" app. Wget utility is the best option to download a file.

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

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 Docker file.

./build.sh

Launch the Docker container with the command

docker run -p 8999:4700 imagename:latest

Check the status of an 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.

How to call the script

  • 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. Parameters from the file are overwritten by explicitly defined parameters.
    • deploy.sh -an hello-world -f settings_alternative.ini
    ./deploy.sh -s {siteurl} -u {username} -p {password}  -an hello-world -f settings.ini

The ini sample:

[deploy]
username=tenant/user
password=pass
url=someurl
appname=sample_application

Developing Microservices

Overview

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.
  • can optionally include a ConfigureServices method to configure the app's services.

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

There are two possible deployment types on the platform:

  • Hosted deployment - the default for microservices. For typical use cases the hosted deployment is the suggested one.
  • 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. The process is described in this document.

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;
}

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

  • 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're 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's 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 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 concept 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 so 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 healthpoint.

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 the following code:

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. That endpoint is created by the library ASP.NET Core middleware. When that 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, 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. 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 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 cumulocity.json. The zip archive contains image.tar and cumulocity.json.
  • Docker-Run - Creates a new container using default settings.