Let’s assume now we want to dockerise a new ASP .NET Core 6.0 web app project that we have locally.
Now, when we build and run the project, we should be able to view it on localhost as shown below.
The default homepage of a new ASP .NET Core web app.
Before adding the .NET app to the Docker image, first it must be published with the following command.
dotnet publish --configuration Release
Build, run, and publish our ASP .NET Core web app.
Create the Dockerfile
Now, we will create a file named Dockerfile in directory containing the .csproj and open it in VS Code. The content of the Dockerfile is as follows.
FROM mcr.microsoft.com/dotnet/aspnet:6.0
COPY bin/Release/net6.0/publish/ App/
WORKDIR /App
ENTRYPOINT ["dotnet", "Lunar.Dashboard.dll"]
A Dockerfile must begin with a FROM instruction. It specifies the parent image from which we are building. Here, we are using mcr.microsoft.com/dotnet/aspnet:6.0, an image contains the ASP.NET Core 6.0 and .NET 6.0 runtimes for running ASP.NET Core web apps.
The COPY command tells Docker to copy the publish folder from our computer to the App folder in the container. Then the current directory inside of the container is changed to App with the WORKDIR command.
Finally, the ENTRYPOINT command tells Docker to configure the container to run as an executable.
Docker Build
Now that we have the Dockerfile, we can build an image from it.
In order to perform docker build, we first need to navigate the our project root folder and issue the docker build command, as shown below.
docker build -t lunar-dashboard -f Dockerfile .
We assign a tag lunar-dashboard to the image name using -t. We then specify the name of the Dockerfile using -f. The . in the command tells Docker to use the current folder, i.e. our project root folder, as the context.
Once the build is successful, we can locate the newly created image with the docker images command, as highlighted in the screenshot below.
The default docker images command will show all top level images.
Create a Container
Now that we have an image lunar-dashboard that contains our ASP .NET Core web app, we can create a container with the docker run command.
docker run -d -p 8080:80 --name lunar-dashboard-app lunar-dashboard
When we start a container, we must decide if it should be run in a detached mode, i.e. background mode, or in a foreground mode. By default the container will be running in foreground.
In the foreground mode, the console that we are using to execute docker run will be attached to standard input, output and error. This is not what we want. What we want is after we start up the container, we can still use the console for other commands. Hence, the container needs to be in detached mode. To do so, we use the -d option which will start the container in detached mode.
We then publish a port of the container to the host with -p 8080:80, where 8080 is the host port and 80 is the container port.
Finally, we name our container lunar-dashboard-app with the --name option. If we do not assign a container name with the --name option, then the daemon will generate a random string name for us. Most of the time, the auto-generated name is quite hard to remember, so it’s better for us to give a meaningful name to the container so we can easily refer the container later.
After we run the docker run command, we should be able to find our newly created container lunar-dashboard with the docker ps command, as shown in the following screenshot. The option -a is to show all containers because by default docker ps will show only containers which are running.
Our container lunar-dashboard is now running.
Now, if we visit the localhost at port 8080, we shall be able to see our web app running smoothly.
Hence, I have no choice but to use WSL, which runs a Linux kernel inside of a lightweight utility VM. WSL provides a mechanism for running Docker (with Linux containers) on the Windows machine.
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.
Unlike WinUI 2.x, which is a control library that sits on top of UWP, WinUI 3 works with any app supported by the Windows App SDK. We can use WinUI 3 as the entire UI layer for building our desktop app. Another more subtle difference is that WinUI 3 targets .NET 5, whilst UWP still uses .NET Native.
WinUI 3 controls and styles are demonstrated in a free app known as WinUI 3 Controls Gallery on Microsoft Store.
Why WinUI 3?
I’d like to share with you some of the benefits of developing with WinUI 3 that I found online.
Modern GUI Design: Our apps will be up-to-date with latest visual elements and controls with the latest Fluent Design without requiring an updated Windows SDK;
Backward Compatible: WinUI 3 provides backward compatibility with a wide range of Windows 10 versions;
UX Stack Decoupling: In WinUI 3, UX stack and control library completely decoupled from the OS and the Windows 10 SDK. Hence, unlike UWP, WinUI 3 allows us to use the latest UI controls without updating Windows;
The Windows App SDK is a set of unified developer components and tools that can be used in a consistent way by any desktop app on Windows 11 and backwards compatible all the way until Windows 10, version 1809.
Since {x:Bind} is new for Windows 10, {x:Bind} is currently only supported in the both UWP and WinUI but not in WPF even though all of them using XAML for frontend.
Microsoft.Toolkit.Mvvm
In order to decoupling front-end and back-end codes, there is a UI architectural design pattern, the MVVM (Model-View-ViewModel), introduced by Microsoft in 2005 in order to support the development of XAML apps in WPF, UWP, and now WinUI.
The view models in our WinUI app require the property change notification support. Hence, we will make our view models inherit from the ObservableObject base class.
ObservableObject provides a base implementation for INotifyPropertyChanged andexposes the PropertyChanged event. In addition, it provides a series of SetProperty methods that can be used to easily set property values and to automatically raise the appropriate events. The following code snippet shows how we implement that in the view model for a page called TwitterPage in the app.
public class TwitterPageViewModel : ObservableObject
{
private string _searchingQuery;
...
public string SearchingQuery
{
get => _searchingQuery;
private set => SetProperty(ref _searchingQuery, value);
}
...
}
For example, if we would like to have a button that can refresh the tweets in the TwitterPage when the button is clicked, we will do the following in the view model of the page.
public class TwitterPageViewModel : ObservableObject
{
...
public TwitterPageViewModel()
{
...
RefreshTweetsCommand = new AsyncRelayCommand(RefreshTweetsAsync);
}
public ICommand RefreshTweetsCommand { get; }
private async Task RefreshTweetsAsync() => await FetchTweetsAsync(_searchingQuery);
}
Then we can bind the command to a button on the TwitterPage as shown below.
Since we need to make API calls to Twitter, we have to provide relevant API keys and secrets. Instead of hardcoding the keys in the project, it’s better to keep them in another file appsettings.json which is also ignored in Git.
The NavigationView doesn’t perform any navigation tasks automatically. When we tap on a navigation item, an ItemInvoked event will be raised. Hence, we can make use of the ItemInvoked event and set a convention such that the Tag of the navigation will tell the app which page it should be navigated to once it’s tapped, as demonstrated in the code below.
NavigationView has a built-in back button; but there is also no backwards navigation implemented automatically. Hence, we need to handle that manually.
We also bind the IsBackEnabled property to the CanGoBack property of our navigation frame, i.e. ContentFrame so that the back button is only enabled when we are allowed to navigate backwards.
Dark Mode and Light Mode
We can control the application UI theme to be either light or dark theme via a settings page.
By default, the NavigationView will have a Settings button because its IsSettingsVisible is true by default. Since the default tag of the Settings button is “Settings”, we can simply link it up to our settings page by creating a SettingsPage. We can use the SettingsPagePage.xaml from the Windows Template Studio as the reference of our SettingsPage.
Settings page copied from the Windows Template Studio.
We first need to create a static class ThemeSelectorService. In this service, we will save and load the app theme in a setting called “AppBackgroundRequestedTheme” in the ApplicationData.LocalSettings.
Then we will also set the app theme in the service as follows.
public static void SetRequestedTheme()
{
((FrameworkElement)((MainWindow)(Application.Current as App).MainWindow).Content).RequestedTheme = Theme;
}
Finally, we will initialise the service when the app is launched normally, as demonstrated below.
Drawing circles and rectangles on the Win2D CanvasControl.
In this app, instead of directly drawing to the CanvasControl, we draw to the Command List Drawing Session (CLDS) where command list is an intermediate object that stores the results of rendering for later use.
After we have successfully reserved an app name on Microsoft Store, we can then associate our app with it in Visual Studio, as shown below. The following popup is shown after we right-click on WinUI 3 project in the Solution Explorer and choose “Package and Publish” then “Associate App with the Store…”.
Selecting an app name reserved on Microsoft Store.
We will then be alerted when we try to build our WinUI 3 project again because our Package.appxmanifest file has an error, as shown below.
The error says the required attribute “PhonePublisherId” is missing.
If we’re to open the file in XML editor, we will notice that PhoneIdentity is being added without us knowing, as highlighted in the following screenshot. This line should not be needed because our WinUI 3 app is not going to be made available on Windows Phone (in fact, there is no more Windows Phone).
We need to delete the PhoneIdentity to proceed.
Hence, the error will be gone after we delete the PhoneIdentity line. Now, we can continue to package and publish our app to the Microsoft Store, as shown below.
Uploading the msix of our WinUI 3 app to the Microsoft Store.
It’s always a good idea to not only make our libraries open-source, but also publish them as a package for public to use if our libraries can make the life of other developers better.
In this article, I’d like to share how we can use the pipeline in Azure DevOps to auto build and publish a C# library that has its source code on GitHub to the NuGet Gallery, the .NET package repository.
As you can see in the csproj file of the library project, we have the following properties are required to create a package.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
...
<PackageId>WordpressRssFeed</PackageId>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Description>A reusable codes library for reading WordPress RSS feeds.</Description>
<Authors>Goh Chun Lin</Authors>
<Copyright>Copyright 2022, Goh Chun Lin</Copyright>
<PackageTags>Wordpress</PackageTags>
<Company>Goh Chun Lin</Company>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/goh-chunlin/WordpressRssFeed</RepositoryUrl>
<PackageReleaseNotes>Please refer to README.</PackageReleaseNotes>
</PropertyGroup>
...
</Project>
Azure DevOps Project
On Azure DevOps, we can create a pipeline which has source code sitting in a GitHub repository. We simply select “GitHub” as the source and then choose the correct repository and branch which contains the code that the pipeline should build.
Selecting a repository and branch on the GitHub as the pipeline source.
Before we begin to look at the pipeline tasks, we need to setup the build number properly because we will later use it to version our package.
Firstly, we will setup BuildConfiguration, MajorVersion, MinorVersion, and BuildRevision as shown in the following screenshot.
We will start our first stable build from 1.0.0.
Next, we need to format the build number with MajorVersion, MinorVersion, and BuildRevision as shown below.
We will use the standard format $(MajorVersion).$(MinorVersion).$(BuildRevision) for build number.
NuGet 6 and .NET 6
Currently (Jan 2022), the latest version of NuGet.exe is 6.0.0 and the latest .NET SDK is 6.0.1. The main reason why we choose to use the latest versions is because we’d like to use the latest feature where we can pack a README.md file in our NuGet package and have it fully rendered on NuGet.org! This feature was newly introduced in May 2021 and was still in preview back then with NuGet 5.10 Preview 2 and .NET SDK 5.0.300 Preview.
Using .NET SDK 6.0.101.
Restore, Build, Test, Pack
We then need to point the restore and build tasks to our library csproj, as shown below.
The library project is built in release mode, which is specified in BuildConfiguration variable.
After the build succeeds, we can proceed to run our test cases in the test project, as shown in the following screenshot.
It’s important to test the library first before publishing it to the public.
We can pack our library once the test cases are all green. The package will be created in the $(Build.ArtifactStagingDirectory) directory, as shown below. Here, we need to make sure that we setup the package versioning properly. In order to make things easier, we will simply use the build number as our package version.
Using the build number as the package version.
If you are interested about the output of the pack task, you can also publish it to the drop as shown in the “Publish Artifact” as shown in the screenshot below. Otherwise, you can skip this task.
This task is optional. It is only for you to download the output of the “dotnet pack” task.
Publish Package to NuGet.org
Since our package is created in $(Build.ArtifactStagingDirectory), so we can specify the path to publish as shown in the screenshot below.
This pipeline has been linked with NuGet.org through an API key. So package will be uploaded directly to NuGet.org.
Add Readme File
Firstly, we will have a README.md file in the root of our library project, as demonstrated below.
Then we need to reference the README.md in the project file as follows.
Now, we shall use the <PackageIcon> property to specify the icon file path, relative to the root of the library project. In addition, we also need to make sure that the file is included in the package.
If you pay attention to the log of the “NuGet Push” task, you will notice that there is a warning about the license information, as shown below.
There is a warning saying that all published packages should have license information specified.
To solve this issue, we can use the <PackageLicenseExpression> property to specify the license of our package. If we’re licensing the package under a common license, like MIT or GPL 3.0, we can use the associated SPDX license identifier.
After specifying the license, the warning above will go away.
Alternatively, you can also choose to package a license file by using the <PackageLicenseFile> property to specify the package path, relative to the root of the package. This is similar to how we add an icon to the package, so I won’t repeat the steps of doing that here.
Please take note that only one of PackageLicenseExpression and PackageLicenseFile can be specified at a time. Also, PackageLicenseUrl is deprecated.
Conclusion
It takes a while for our package to be published on NuGet.org. We have to wait a bit longer if it’s the first release of our package.
Once the package is searchable on the NuGet Gallery, we shall be able to see our beautiful package page as shown below.
Hence, you may notice that the badge of “Build status” in the screenshot above cannot be loaded. This is because my Azure DevOps is on the old domain, i.e. *.visualstudio.com. The visualstudio.com unfortunately is not one of the trusted domains.
To solve that issue, we should get the badge from the new domain of Azure DevOps, i.e. dev.azure.com, instead.
We can get our status badge from Azure DevOps.
After the update is done, we should be able to see a complete homepage of our NuGet package as shown in the following screenshot.
The toolkit was first released in August 2016 with the name “UWP Community Toolkit” as an open-source project with a collection of helper functions, custom controls, and app services. The main goal of the project is to simplify the common developer tasks in UWP app development.
Two years after its first release, the UWP Community Toolkit was renamed to the Windows Community Toolkit with more helper functions and controls available. The new name is reflective of the increased focus and more inclusive of all Windows developers.
Today, in this article, we will look into how the helper function in the toolkit is able to help us in the task of binding an enum to a ComboBox control.
There is an extension in the toolkit that can exactly help us in doing just that. The extension is called the EnumValuesExtension.
EnumValuesExtension returns a collection of values of a specific enum type. Hence, we can use it to easily bind a collection of all possible values from a given enum type to a ComboBox control or some other UI controls.
Model and ViewModel
We will use the same enum, MyColors, the one we use in our previous blog post.
public enum MyColors
{
Red,
Green,
Blue,
Orange,
Pink,
Black
}
We will still have the same ViewModel for our MainPage.xaml which will contains the ComboBox control.
public class MainViewModel : ViewModelBase
{
private MyColors _selectedColor = MyColors.Black;
public MyColors SelectedColor
{
get => _selectedColor;
set
{
if (_selectedColor != value)
{
SetProperty(ref _selectedColor, value);
}
}
}
}
View
Now in the MainPage.xaml which contains the ComboBox control, without using our own custom value converter, we can directly bind the enum above to our front-end view with the help of EnumValuesExtension, as shown below.
That’s all for the quickstart steps to bind an enum to a ComboBox control in an UWP app with the help of the Windows Community Toolkit.
If you’d like to further customise the values shown in the ComboBox, you could also use a value converter. I have made the source code available on GitHub. The code will have more features where the colour of text in a TextBlock will be based on the colour we pick from the ComboBox, as shown below.