[KOSD] Learning from Issues: Troubleshooting Containerisation for .NET Worker Service

Recently, we are working on a project which needs a long-running service for processing CPU-intensive data. We choose to build a .NET worker service because with .NET, we are now able to make our service cross-platform and run it on Amazon ECS, for example.

Setup

To simplify, in this article, we will be running the following code as a worker service.

using Microsoft.Extensions.Hosting;

using NLog;
using NLog.Extensions.Logging;

Console.WriteLine("Hello, World!");

var builder = Host.CreateApplicationBuilder(args);

var logger = LogManager.Setup()
.GetCurrentClassLogger();

try
{
builder.Logging.AddNLog();

logger.Info("Starting");

using var host = builder.Build();
await host.RunAsync();
}
catch (Exception e)
{
logger.Error(e, "Fatal error to start");
throw;
}
finally
{
// Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
LogManager.Shutdown();
}

So, if we run the code above locally, we should be seeing the following output.

The output of our simplified .NET worker service.

In this project, we are using the NuGet library NLog.Extensions.Logging, thus the NLog configuration is by default read from appsettings.json, which is provided below.

{

"NLog":{
"internalLogLevel":"Info",
"internalLogFile":"Logs\\internal-nlog.txt",
"extensions": [
{ "assembly": "NLog.Extensions.Logging" }
],
"targets":{
"allfile":{
"type":"File",
"fileName":"C:\\\\Users\\gclin\\source\\repos\\Lunar.AspNetContainerIssue\\Logs\\nlog-all-${shortdate}.log",
"layout":"${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}"
}
},
"rules":[
{
"logger":"*",
"minLevel":"Trace",
"writeTo":"allfile"
},
{
"logger":"Microsoft.*",
"maxLevel":"Info",
"final":"true"
}
]
}
}

So, we should be having two log files generated with one showing something similar to the output on the console earlier.

The log file generated by NLog.

Containerisation and the Issue

Since we will be running this worker service on Amazon ECS, we need to containerise it first. The Dockerfile we use is simplified as follows.

Simplified version of the Dockerfile we use.

However, when we run the Docker image locally, we receive an error, as shown in the screenshot below, saying “You must install or update .NET to run this application.” However, aren’t we already using .NET runtime as stated in our Dockerfile?

No framework is found.

In fact, if we read the error message clearly, it is the ASP .NET Core that it could not find. This confused us for a moment because it is a worker service project, not a ASP .NET project. So why does it complain about ASP .NET Core?

Solution

This problem happens because one of the NuGet packages in our project relies on ASP.NET Core runtime being present, as discussed in one of the StackOverflow threads.

We accidentally include the NLog.Web.AspNetCore NuGet package which supports only ASP .NET Core platform. This library is not used in our worker service at all.

NLog.Web.AspNetCore supports only ASP .NET platform.

So, after we remove the reference, we can now run the Docker image successfully.

WRAP-UP

That’s all for how we solve the issue we encounter when developing our .NET worker service.


KOSD, or Kopi-O Siew Dai, is a type of Singapore coffee that I enjoy. It is basically a cup of coffee with a little bit of sugar. This series is meant to blog about technical knowledge that I gained while having a small cup of Kopi-O Siew Dai.

[KOSD Series] When Surface Dial meets UWP

Starting from end of last year, I have been working on several UWP projects at work. Few days ago, I’m glad to have the opportunity to borrow a Surface Dial from Riza.

Surface Dial is a revolutionary input device. It was introduced by Microsoft in the 2016 together with Surface Studio. Most of the time, it works awesome on Surface devices. However, that doesn’t mean we can’t use it with our Windows desktop or laptop. As a secondary input device, it can be used together with our mouse or touch. Developers are also welcomed to customize the abilities of the Surface Dial on their UWP apps.

In this article, I will be sharing on how we can use Surface Dial to support a quick menu of commands in an UWP app.

Installing Surface Dial

First of all, we need to make sure our Windows device is on (Yes, UWP currently can only work on Windows machines). Then we need to turn on the Bluetooth on the machine. If our PC doesn’t come with the Bluetooth hardware installed, we can easily add it by purchasing a Bluetooth USB dongle.

After that, we need to turn our Surface Dial on by removing the cover of its bottom and then pressing the button next to the batteries, as shown in the photo below.

🎨 Added Surface Dial to my PC. 🎨

Now we can find the Surface Dial on the “Bluetooth & other devices” window. We can proceed to add it to our PC.

Adding Menu on Surface Dial

For those who attended my sharing in Microsoft Insider Dev Tour 2019 in Kuala Lumpur and Johor Bahru last year, the following UWP app should be familiar to you.

🎨 The UWP demo app done by Justin Liu for Microsoft Insider Dev Tour. 🎨

Now we will proceed to add Surface Dial menu to this page.

In the code-behind of the page, we will have the following global objects.

private RadialController radialController;
private RadialControllerConfiguration radialControllerConfig;

Then, we can initialize the RadialController.

// Create a reference to the RadialController.
radialController = RadialController.CreateForCurrentView();
// Set rotation resolution to 1 degree of sensitivity.
radialController.RotationResolutionInDegrees = 1;

What does setting RotationResolutionInDegrees mean here? The value actually is the minimum rotation value required for the RotationChanged event to be fired. So, by setting it to 1, every one degree of rotate on the Surface Dial, the RotationChanged event will be triggered. Also, by default, when the RotationChanged happens, the Surface Dial will vibrate. So it is like massaging your hand when you’re rotating the Surface Dial that has its RotationResolutionInDegrees set to 1.

Then we can proceed to add our menu items to the Surface Dial. Here, we use a font glyph for the custom tool.

var imageGallery = RadialControllerMenuItem.CreateFromFontGlyph("Image Gallery", "\xE15A", "Segoe MDL2 Assets");
...
radialController.Menu.Items.Add(imageGallery);

However, please take note that, by default, there are built-in menu items for the Surface Dial. So we need to remove them to prevent squeezing in too much menu items to the Surface Dial UI and thus making it harder to control.

To remove the built-in menu items, we just need to reset in the configuration of the Surface Dial. Another thing to take note is that the Surface Dial menu must have at least one menu item, else the default menu items will be restored.

radialControllerConfig = RadialControllerConfiguration.GetForCurrentView();

radialControllerConfig.SetDefaultMenuItems(new RadialControllerSystemMenuItemKind[] { });

Now there is a funny thing is that if we remove all the built-in menu items before we add our customized menu items, i.e swapping the position of the two blocks of codes above, then we will realize that the default menu items will be restored and our customized menu items will be appended to the default ones, as shown in the screenshot below.

🎨 Oh my tian, the buttons on the Surface Dial menu are so crowded! 🎨

Finally, if we want to handle the events fired from the Surface Dial, for example when users click on it or rotate it, we can use the following handlers.

radialController.ButtonClicked += RadialController_ButtonClicked;
radialController.RotationChanged += RadialController_RotationChanged;

Please take note that the ButtonClicked event is not triggered when a menu item is selected. Instead we need to do as follows to handle the menu item chosen event.

imageGallery.Invoked += ImageGallery_Invoked;

Result

So, now, with all these few lines of codes, we can have a beautiful Surface Dial control on our UWP app, as shown in the following photo. Yay!

🎨 Control the universe with our hand. 🎨

References

  1. Support the Surface Dial (and other wheel devices) in your UWP app;
  2. Creating Custom Dial Menu.

KOSD, or Kopi-O Siew Dai, is a type of Singapore coffee that I enjoy. It is basically a cup of coffee with a little bit of sugar. This series is meant to blog about technical knowledge that I gained while having a small cup of Kopi-O Siew Dai.

[KOSD Series] First Attempt of Deploying ASP .NET Core to Azure Container Service

KOSD, or Kopi-O Siew Dai, is a type of Singapore coffee that I enjoy. It is basically a cup of coffee with a little bit of sugar. This series is meant to blog about technical knowledge that I gained while having a small cup of Kopi-O Siew Dai.

kosd-docker-azure_container_registry-vsts

Last month, after sharing the concepts and use cases of Domain Driven Development, Riza moved on to talk about Containers in the sharing session of Singapore .NET Developers Community.

microservices-not-equal-to-containers.png
Riza’s talking about Containers. Yes, microservices are not containers!

Learning Motivation

In the beginning of Riza’s talk, he mentioned GO-JEK, an Indonesia ride-hailing phone service. Due to their rapid growth, the traditional monolithic architecture can no longer support their business. Hence, they switched to use a modern approach which includes moving apps to containers.

go-jek-containers.png
Go-Jek team is working on moving apps to container.

Hence, after the meetup, I was very excited to find out more about micro-services and Docker containers. With the ability of .NET Core to be cross-platform, as a Azure lover, I am interested to find out more how I can deploy ASP .NET Core web app to a container in Azure. So, I decided to write this short article to share with my teammates about this that they can learn while drinking a cup of coffee.

Creating New Project with Docker Support

Since I am trying it out as personal project, I choose to start it with a new ASP .NET Core project. Then in the Visual Studio, I can easily turn it to be a Docker supporting app easily by checking the “Enable Docker Support” option.

enable-docker-support.png
Enable Docker Support

For existing web application projects, we will not have the screen above. Luckily, it is still easy to add Docker Support to an existing ASP .NET Core project on Visual Studio.

add-docker-support-to-existing-project
Enabling Docker Support in existing projects.

Then by clicking on the “F5” button to run the project, I manage to get the following screen (The background is customized by me). The message is displayed using the following line.

System.Runtime.InteropServices.RuntimeInformation.OSDescription;

launched-at-localhost.png
Yay, we managed to run the web app inside a Linux container locally.

Publishing to Microsoft Azure with Continuous Delivery

Without Continuous Delivery, we also can easily right-click the web application to publish it to the Container Registry on Azure.

publishing-to-container-registry
Creating a new Azure Container Registry which will have the Docker image published to.

Then, on Azure Portal, we will see three new resources added. Firstly, we will have the Container Registry.

Then, we will also have an app service site which is running the image downloaded from the Container Registry. Finally, we have an App Service Plan which needs to be at least B1 because free and shared SKUs are not available for apps running on Linux (The official Microsoft documentation says we should have the VM size of the App Service Plan to be S1 or larger though).

container-registry-on-azure.png
Container Registry for my new web app, Changshi.

To enable Continuous Delivery, I choose to use Github + Visual Studio Team Services (VSTS). By doing so, build and release will be automatically started whenever I check in code to Github.

build-on-vsts
Build history and details on VSTS.

Yup, this is so far what I have tried out in my first step of playing with containers. If you are interested, please check out the references listed below.

References