Sunday, February 7, 2021

Microsoft Documentation: Does Microsoft follow up on User Feedback for their Documentation (Azure, Docker, ACR)?

Working through the sample code in Tutorial: Deploy and use Azure Container Registry, I noticed that a command prompt (a dollar sign) was included in a script snippet that could be copied from the aforementioned tutorial:


Clicking on the copy button copied the leading $ and the command, docker images:

$ docker images

The above issue is innocuous and most developers would immediately notice the issue when they tried to invoke "$ docket images." Still, the correct text to be copied is:

docker images

At the bottom of each page of documentation Microsoft provides feedback buttons including the This page button to provided feedback on the current page:


I submitted the issue to Microsoft and was pleasantly surprised fives hours later when Bhargavi Annadevara of Microsoft sent an email saying she had submitted a pull request (PR) to fix the issue (see below):



Friday, February 5, 2021

Azure CLI: Resource Groups, Resource Clean Up (Docker, ACR, Kubernetes, AKS)

Overview

In the post, Azure/PowerShell: Resource Groups, Resource Clean Up (Docker, ACR, Kubernetes, AKS), a demonstration on how to clean up Azure resources was given using PowerShell. The current post will present the same strategy (using the removal of an Azure resource group to clean up resources) but instead will use the Azure CLI. Bash accesses Azure using the Azure CLI. The concepts introduced in the previous post are as follows:


All source code (Azure CLI) is provided in text form in Appendix A: Source Code at the end of this post.

Azure CLI

Like Azure PowerShell, the Azure CL can be accessed using Cloud Shell which can be launched using the url, https://shell.azure.com.

Using Cloud Shell and Azure CLI a resource group can be created as follows (in location West US 2 with name rgdockerkubernetes00) using Azure CLI's az group create:

 
The code to remove the resource group is as follows using Azure CLI's az group delete:


It should be noted in the previous examples that the resource group was created at line 7 and the resource group removed at line 33. Any Azure objects (such as am ACR or AKS) created after line 7 and before line 33  for the same resource are cleaned up when the resource group is removed.

The code to create an ACR associated with resource group, rgdockerkubernetes00, is as follows using Azure CLI's az acr create:
 

The ACR will be removed when the resource group is removed.

The code to create an AKS associated with resource group, rgdockerkubernetes00, is as follows using Azure CLI's az aks create (line 17 and line 23):

 

The AKS will be removed when the resource group is removed. After line 29 code could be added in order experiment with Docker/Kubernetes. The ultimate invocation of Azure CLI's az group delete would insure that all Azure resources are cleaned up for the resource group.

A bit of explanation is needed of the above code. A Kubernetes cluster internally uses Linux virtual machines. These virtual machines require an SSH key in order to be accessed. Line 16 above detects if the key exists. If the key does not exist then Azure CLI's az aks create is invoked with the generate-ssh-keys parameter. When this parameter is specified no user response is required as the SSH keys are automatically created. The following text is generated when az aks create is invoked with the generate-ssh-keys parameter:


The generate-ssh-keys parameter was useful given this was a sample script. A more real world approach would be to create the SSH key backup the SSH key before creating the AKS. The following link from Microsoft, Quick steps: Create and use an SSH public-private key pair for Linux VMs in Azure., demonstrates how to create an SSH key.

Since the Azure CLI's az group delete cleans up the ACR and AKS resources, there is no need to explicitly invoke az acr delete or az aks delete.

Appendix A: Source Code

The Azure CLI source code for this post is as follows:

#!/bin/bash -x
resource_group_name='rgdockerkubernetes00'
acr_name='crdockerkubernetes00'
aks_name='ksdockerkubernetes00'
node_count=2

az group create \
     --name $resource_group_name \
     --location 'West US 2'

az acr create \
    --resource-group $resource_group_name \
    --name $acr_name \
    --sku Basic

if [ -f ~/.ssh/id_rsa ]; then
    az aks create \
        --resource-group $resource_group_name \
        --name $aks_name \
        --node-count $node_count \
        --attach-acr $acr_name
else    
    az aks create \
        --resource-group $resource_group_name \
        --name $aks_name \
        --node-count $node_count \
        --attach-acr $acr_name \
        --generate-ssh-keys
fi

# Manipulate Azure resources here

az group delete \
    --name $resource_group_name \
    --yes


Thursday, February 4, 2021

Azure: Toggling Cloud Shell between PowerShell and Bash

Azure Cloud Shell, https://shell.azure.com, supports both PowerShell and Bash but just not both at the same time. When Cloud Shell is invoked, there is a dropdown in the upper left corner that identifies the current scripting environment. Below (see upper left) the scripting environment for Cloud Shell is PowerShell:

The term PowerShell above is a dropdown. Clicking on the dropdown allows the supported scripting of Cloud Shell to be changed to Bash (see below):



Azure/PowerShell: Resource Groups, Resource Clean Up (Docker, ACR, Kubernetes, AKS)

Overview

Developers and DevOps engineers should be conscious of the resource they create under Azure as these resources come at a cost. Engineers with MSDN subscription receive a $150 per-month in Azure credit and engineers who sign up for Azure receive a $200 credit for their first month (Create your Azure free account today) can quickly burn up their complimentary allotment. A simple approach to controlling Azure costs is to:
  • Create a new Azure resource group
  • Perform a development/devops task using Azure resources associated with the newly created resource group
  • Delete the newly created resource group
By deleting the resource group the resources are released and hence Azure will no longer charge for said resources. 

A specific scenario using a resource group to insure Azure object clean up is:
  • Create a new Azure resource group
  • Create an Azure Container Registry (ACS) that would be used to manage Docker containers,
  • Create an Azure Kubernetes Service (AKS) cluster
  • Perform specific Docker/Kubernetes tasks 
  • Delete the Azure resource group thus cleaning up the ACS and the AKS

The post demonstrates the above sequence of tasks using PowerShell. All source code is provided in text form in Appendix A: Source Code at the end of this post.

PowerShell

PowerShell can access Azure from a physical or virtual host but an elegant way to access Azure with PowerShell is to login to the Azure Portal (https://portal.azure.com/) and launch Cloud Shell. Once logged into the Azure Portal the button for launch Cloud Shell is highlighted by an ellipse below:


The https://shell.azure.com url brings up Cloud Shell directly.

Using Cloud Shell and PowerShell a resource group can be created as follows (in location West US 2 with name rgdockerkubernetes00) using the New-AzResourceGroup cmdlet:


The code to remove the resource group using Powershell is as follows using the Remove-AzResourceGroup cmdlet:


It should be noted in the previous examples that the resource group was created at line 6 and the resource group removed at line 37. Any Azure objects (such as a container registry or a Kubernetes service) created after line 8 and before line 37 for the same resource are cleaned up when the resource group is removed.

The code to create an ACR associated with resource group, rgdockerkubernetes00, is as follows using the New-AzContainerRegistry cmdlet:


The ACR will be removed when the resource group is removed.

The code to create an AKS associated with resource group, rgdockerkubernetes00, is as follows using the New-AzAksCluster cmdlet (line 17 and line 27):


The AKS will be removed when the resource group is removed. After line 33 code could be added in order experiment with Docker/Kubernetes. The ultimate invocation of Remove-AzResourceGroup would insure that all Azure resources are cleaned up for the resource group.

A bit of explanation is needed for the above code. A Kubernetes cluster internally uses Linux virtual machines. These virtual machines require an SSH Key in order to be accessed. Line 15 above detects if the key exists. If the key does not exist then the New-AzAksCluster cmdlet is invoked with the GeneratesSshKey parameter. When this parameter is specified a user is required to respond to the following two prompts used in creating the SSH key:


Most Azure PowerShell scripts are not meant to be run with user interaction. A practical approach would be to create the SSH key in advance and appropriately backup the SSH keys. Microsoft provides an excellent tutorial on creating an SSH key at Quick steps: Create and use an SSH public-private key pair for Linux VMs in Azure.

Since Remove-AzResourceGroup cleans up the ACR and AKS resources, there is no need to explicitly invoke Remove-AzContainerRegistry or Remove-AzAksCluster.

Appendix A: Source Code

The source code in its entirety is as follows:

[string] $resourceGroupName = 'rgdockerkubernetes00'
[string] $acrName = 'crdockerkubernetes00'
[string] $aksName = 'ksdockerkubernetes00'
[int] $nodeCount = 2

New-AzResourceGroup `
    -Name $resourceGroupName `
    -Location 'West US 2' | Out-Null

New-AzContainerRegistry `
    -ResourceGroupName $resourceGroupName `
    -Name $acrName `
    -Sku 'Basic' | Out-Null

if (Test-Path '~/.ssh/id_rsa' -PathType Leaf) {
    Write-Host 'SSH Keys Exist'
    New-AzAksCluster `
        -ResourceGroupName $resourceGroupName `
        -AcrNameToAttach $acrName `
        -NodeCount $nodeCount `
        -Name $aksName
}

else {
    # -GenerateSshKey generates a prompt
    Write-Host 'Generate SSH Keys'
    New-AzAksCluster `
        -ResourceGroupName $resourceGroupName `
        -AcrNameToAttach $acrName `
        -NodeCount $nodeCount `
        -Name $aksName `
        -GenerateSshKey
}

<# code here that uses Docker/Kubernetes #>

Remove-AzResourceGroup `
    -Name $resourceGroupName `
    -Force # | Out-Null


Wednesday, February 3, 2021

Visual Studio Code: Disabling Code References on OS/X (a.k.a. Disabling CodeLens)

In the post Visual Studio Code: Disabling Code References on Windows (a.k.a. Disabling CodeLens) it was shown how to disable CodeLens on Visual Studio Code running on Windows.The steps required to disable CodeLens on OS/X (a Macintosh) are shown in this post.

The CodeLens feature of Visual Studio Code shows (amongst other things) the number of references to a method or function such as shown below in an excerpt from the post PowerShell: Fibonacci Interview Question




In order to disable Visual Studio Code's CodeLens on OS/X select the Code menu from Visual Studio Code and invoke Preferences | Settings:


From the Settings dialog select the Text Editor tab and scroll down to the Code Lens checkbox:


Uncheck the Code Lens checkbox. On OS/X it is necessary to close and reopen VisuaL Studio Code in order to have the CodeLens settings changes recognized.



Docker: Installing Docker on OS/X (the final step)

The site where the Docker installer for OS/X (a Mac). can be downloaded is Install Docker Desktop on Mac. After the Docker install is run on an OS/X computer Docker is not completely installed. The Docker Desktop actually needs to be run in order to finish installing Docker.

The Docker community forums site, https://forums.docker.com/, has at least one thread on this topic, Docker:command not found after installing Docker desktop on Mac:



Tuesday, February 2, 2021

PowerShell: Fibonacci Interview Question

The first Fibonacci program I wrote was on my assembly language final in 1984 (PDP-11 assembly). During a DevOps interview I was asked to write an example of a Fibonacci program so I answered the question using PowerShell. The complete source code in text form can be found at the end of this post. 

An example of a Fibonacci number being computed recursively (method Recursive) and sequentially (method Iterative):

The methods, Iterative and Recursive, are hidden. The publicly visible method, Compute, is used to compute the Fibonacci number:

The SequenceRecusrsive method is a hidden method that returns a Fibonacci sequence using a recursive computation:


The SequenceIterartive method is a hidden method that returns a Fibonacci sequence using an iterative computation:

The Sequence method is publicly visible and returns a Fibonacci sequence computed using either an iterative or recursive algorithm: 


The test functions (below) verify the Fibonacci number computed and the Fibonacci sequence computed by comparing the value returned by the iterative implementation to the value returned by the recursive implementation:


Repeatedly invoking the functions TestFibonacci and TestFibonacci (see above) for values of 0, 1, 5, and 10 tests test the iterative against recursive implementations for multiple input values.

Appendix A: Source Code

Set-StrictMode -Version 3.0

class Fibonacci  {    
    hidden static [int] Recursive([int] $n) {
        if ($n -gt 1) {
            return `
              [Fibonacci]::Recursive($n - 1) + 
              [Fibonacci]::Recursive($n - 2)
        }

        else {
            return $n
        }
    }

    hidden static [int] Iterative([int] $n) {
        if ($n -le 1) {
            return $n
        }

        [int] $nMinus2 = 0
        [int] $nMinus1 = 1
        [int] $current = 0

        for ([int] $i = 2; $i -le $n; $i++)  
        {  
            $current = $nMinus2 + $nMinus1 
            $nMinus2 = $nMinus1;  
            $nMinus1 = $current
        } 

        return $current
    }

    static [int] Compute([int] $n, [bool] $isIterative) {
        if ($isIterative) {
            return [Fibonacci]::Iterative($n)
        }

        else {
            return [Fibonacci]::Recursive($n)
        }
    }

    hidden static [int[]] SequenceRecursive([int] $n) {
        [Collections.Generic.List[int]] $sequence = `
            [Collections.Generic.List[int]]::new()

        if ($n -le 1) {
            $sequence.Add($n)

            return $sequence
        }

        for ([int] $i = 0; $i -le $n; $i++) {
            $sequence.Add([Fibonacci]::Recursive($i))
        }

        return $sequence
    }

    hidden static [int[]] SequenceIterative([int] $n) {
        [Collections.Generic.List[int]] $sequence = `
            [Collections.Generic.List[int]]::new()

        if ($n -le 1) {
            $sequence.Add($n)

            return $sequence
        }

        [int] $nMinus2 = 0
        [int] $nMinus1 = 1
        [int] $current = 0

        for ([int] $i = 2; $i -le $n; $i++)  
        {  
            $sequence.Add($nMinus2)            
            $current = $nMinus2 + $nMinus1 
            $nMinus2 = $nMinus1;  
            $nMinus1 = $current
        } 

        $sequence.Add($nMinus2)            
        $sequence.Add($current)            

        return $sequence
    }

    static [int[]] Sequence([int] $n, [bool] $isIterative) {
        if ($isIterative) {
            return [Fibonacci]::SequenceIterative($n)
        }

        else {
            return [Fibonacci]::SequenceRecursive($n)
        }
    }
}

function  TestFibonacci([int] $n) {
    [int] $iterative = [Fibonacci]::Compute($n, $true)
    [int] $recursive = [Fibonacci]::Compute($n, $false)

    if ($iterative -ne $recursive) {
        throw 
            "Fibonacci n = $n " +
            "(iterative $iterative -ne recursive $recursive)"
    }

    return $iterative
}

function TestFibonacciSequence([int] $n) {
    [string] $iterative = [Fibonacci]::Sequence($n, $true)
    [string] $recursive = [Fibonacci]::Sequence($n, $false)

    if ($iterative -ne $recursive) {
        throw 
            "Fibonacci n = $n " +
            "(iterative $iterative -ne recursive $recursive)"
    }

    return $iterative
}

TestFibonacci 0
TestFibonacciSequence 0
TestFibonacci 1
TestFibonacciSequence 1
TestFibonacci 5
TestFibonacciSequence 5
TestFibonacci 10
TestFibonacciSequence 10




Monday, February 1, 2021

Azure/Kubernetes: New-AzAksCluster contributed to Azure/azure-powershell (again) Documentation Fix

February 1, 2021 has been a productive day tweaking inconsequential issues in https://github.com/jdnark/azure-powershell. The examples in the documentation for New-AzAksCluster, https://docs.microsoft.com/en-us/powershell/module/az.aks/new-azakscluster?view=azps-5.4.0, used New-AzAks which is the legacy cmdlet. New-AzAksCluster is the cmdlet that is currently used to create a new Kubernetes cluster. New-AzAks is the alias for New-AzAksCluster so New-AzAksCluster should be used in the documentation examples: 



Azure/Kubernetes: contributed to Azure/azure-powershell

This was not the greatest contribution to the azure-powershell Git repository, https://github.com/jdnark/azure-powershell. The PR below fixes four instances of the same typo (changed SshKeyVaule to SshKeyValue) which manifests itself in the New-AzAksCluster cmdlet where a duplicate or missing SSH key is detected:





Sunday, January 31, 2021

Azure/PowerShell: Listing All Locations

Certain Azure PowerShell cmdlets require a location parameter value be specified (e.g. East Asia, West US 2, Brazil South, etc.). In order to display list of of locations the following PowerShell script can be invoked:

Get-AzureRmLocation | Select-Object -Property Location, DisplayName

The location parameter of a cmdlet should be passed the DisplayName of a given resource location. The below example demonstrates this where a new resource group is created for Norway East:

New-AzResourceGroup `
    -Name 'rg_ahha_take_on_me' `
    -Location 'Norway East' | Out-Null

Invoking the above script on January 31, 2020 listed the following locations:


Saturday, January 30, 2021

Azure: Installing Azure CLI on OS/X

Microsoft documents installing the Azure CLI on OS/X at Install Azure CLI on macOS but unfortunately following these instructions results in an error. The aforementioned documentation shows the following introduction and brew command to be used to install the AzureCLI on OS/X 


The error generated by using the above brew command to install the Azure CLI on OS/X is as follows:

This command may take a few minutes to run due to the large size of the repository.
This restriction has been made on GitHub's request because updating shallow
clones is an extremely expensive operation due to the tree layout and traffic of
Homebrew/homebrew-core and Homebrew/homebrew-cask. We don't do this for you
automatically to avoid repeatedly performing an expensive unshallow operation in
CI systems (which should instead be fixed to not use shallow clones). Sorry for
the inconvenience!

The following command (removing the brew update &) install the Azure CLI on OS/X without error:

brew install azure-cli


Monday, January 25, 2021

C# 9.0: Record Types Non-Destructive Mutation (the With expression)

A C# 9.0 record type is immutable but there are times when it is useful to make a copy of a record type but only modify a subset of the properties. Under this scenario, the with express can be used with facilitates non-destructive mutation. To quote Steve Fenton's blog (C# 9 Non-Destructive Mutation): 


To demonstrate non-destructive mutation consider the following record type, a positional record (see C# 9.0: Positional Records):

public record Employee (
                  string Name, 
                  string Department,
                  string Office)
{
}    

The code below uses the with expression to make a copy of a record but allows specific properties to be changed in the newly created record and obviously does not modify the original record:


In the coding sample above, the first Employee record created is assigned to variable, victoria (line 13). The above code utilizes with expressions and behaves as follows:
  • Line 14: the victoria record instance is used to create a new employee, mario, but assigns a new value to the Name property, Mario Aguayo,
  • Line 15: the victoria record instance  is used to create a new employee, tarun, but assigns a new value to the Name property, Tarun Tiwari.
  • Line 16: the victoria record instance  is used to create a new employee, kai, but assigns a new value to the Name property, Kai Feng and Department property, QA.
  • Line 17: the kai record instance is used to create  a new employee, daisuke, but assigns a new value to the Name property, Daisuke Katchi and Office property, Via Carmen Suites.
 

Sunday, January 24, 2021

C# 9.0: Positional Records

This post introduces C# 9.0 positional records. Recall C# 9.0 record types were introduced in the post, C# 9.0: Record Types. A positional record is declared with a list of parameters such those shown below in lines 25 to line 29:


The record created contains an auto-property for each parameter in the declaration (PatientProperty, Count, Start, End, and DateRange). Each auto-property has a public access level. Auto-properties have a get and init access specifier (see: C# 9.0: Init Only Setters). The names of the parameters shown above were made uppercase so that the names of the auto-properties would be uppercase following the naming convention for properties.

Five parameters were specified in declaring the Patients record. The compiler creates a corresponding primary constructor taking the same five parameters as arguments. An example of the invocation of the primary constructor is as follows where the factory method, CreateDay, creates an instance of Patients where the parameters are passed at lines 43 through 47:


The auto-property, PatientProperty, is an enumeration also named PatientProperty. Use of the auto-property PatienProperty is shown below being used internally by the Patients class (lines 31, 33, and 35 below):


The PatientProperty property is never explicitly declared as all properties of the record corresponding to the declaration parameters are auto-properties and the compiler auto created each property. 

The PatientProperty enumeration is decorated by the Flags attribute (see FlagsAttribute Class) meaning that the enumerations values can be combined as they are bit fields. Lines 31, 33, and 35 above show the PatientProperty auto-property being used with the bitwise AND operator (&) in order to determine if the Patients in the date range are IsCovid19, IsIcu, or IsVentilator.

The source for this example is follows using screenshots from Visual Studio Code:

The source code in text form is found in Appendix A; Source Code at the end of this post.

Default Constructor

There is no default (parameterless) constructor created by the compiler for a positional record. It is illegal to create a parameterless constructor for a positional record. Below is an example (which will not compile) where the parameterless constructor is created and invoked at lines 47 to 53 using nominal object creation:


The error created while compiling the code above using Visual Studio Code is as  follows:

Appendix A: Source Code

The source code in its entirety is as shown below and of note:

  • There is no constructor explicitly declared for the class as the primary constructor is created by the compiler based on the parameters specified when the record was declared. 
  • The five parameters specified when the record was declared do not have explicit property declarations as the compiler automatically create this properties (auto-properties).
  • Other properties can be declared such as IsIcu, IsCovid19, and IsVentilator. The values for these parameters are computed using auto-properties assigned based on the parameters passed to the primary constructor. 
  • There is no implicit default constructor created for the record.

using System;

namespace HealthDashboard.Models 
{
    public enum DateRangeType 
    {
        Day,
        Week,
        Month,
        Quarter,
        Biannual,
        Year,
        Custom
    }

    [Flags]
    public enum PatientProperty
    {
        Covid19 = 0x1,
        Icu = 0x2,
        Ventilator = 0x4
    }

    public record Patients(
        PatientProperty PatientProperty,
        int Count,
        DateTime Start,
        DateTime End,
        DateRangeType DateRange) 
    {
        public bool IsCovid19 => 
           (0 != (PatientProperty & PatientProperty.Covid19));

        public bool IsIcu  => 
            (0 != (PatientProperty & PatientProperty.Icu));

        public bool IsVentilator => 
             (0 != (PatientProperty & PatientProperty.Ventilator));

        public static Patients CreateDay(
                    PatientProperty patientProperty, 
                    int count, 
                    DateTime start)
        {
            return new (
                    patientProperty,
                    count, 
                    start, 
                    start.AddDays(1), 
                    DateRangeType.Day);
        }                            

        public static Patients CreateWeek(
                    PatientProperty patientProperty, 
                    int count, 
                    DateTime start)
        {
            return new (
                    patientProperty,
                    count, 
                    start, 
                    start.AddDays(7), 
                    DateRangeType.Week);
        }                            

        public static Patients CreateMonth(
                    PatientProperty patientProperty, 
                    int count, 
                    DateTime start)
        {
            return new (
                    patientProperty,
                    count, 
                    start, 
                    start.AddMonths(1), 
                    DateRangeType.Month);
        }                           

        public static Patients CreateQuarter(
                    PatientProperty patientProperty, 
                    int count, 
                    DateTime start)
        {
            return new (
                    patientProperty,
                    count, 
                    start, 
                    start.AddMonths(3), 
                    DateRangeType.Quarter);
        }                            

        public static Patients CreateBiannual(
                    PatientProperty patientProperty, 
                    int count, 
                    DateTime start)
        {
            return new (
                    patientProperty,
                    count, 
                    start, 
                    start.AddMonths(6), 
                    DateRangeType.Biannual);
        }                            

        public static Patients CreateYear(
                    PatientProperty patientProperty, 
                    int count, 
                    DateTime start)
        {
            return new (
                    patientProperty,
                    count, 
                    start, 
                    start.AddYears(1), 
                    DateRangeType.Year);
        }                            

        public static Patients CreateCustom(
                    PatientProperty patientProperty, 
                    int count, 
                    DateTime start,
                    DateTime end)
        {
            return new (
                    patientProperty,
                    count, 
                    start, 
                    end, 
                    DateRangeType.Custom);
       }    
    }
}







Saturday, January 23, 2021

C# 9.0: Record Types

C# 9.0 introduces a read-only object type created using the record keyword versus the keywords struct or class. This post introduces record instances created using positional object creation (recall C# Nominal versus Positional Object Create). A future post will introduce creating record instances defined using nominal syntax, demonstrate declaring record types with positional syntax, and show the use of the with keyword in conjunction with record types.

Up until C# 9.0 objects were defined using the class (reference type) or struct (value type) keywords. Such objects are typical mutable. It should also be recognized that anonymous types are reference types and tuples are value types, but objects are explicitly defined using the class and struct keywords. 

The record keyword, to no surprise, defines an object of type record where record instances are immutable, reference object types. It is possible to define immutable types using class and struct such as the System.String class. The elegance of the record keyword is that it is clear that an instance of a record is immutable and the compiler creates constructs that support immutability that are built into each record instance. The following sections of this post expand on these ideas:

  • Appendix A: Comparing Record Type Instances
  • Appendix B: Record Type Compiler Synthesized Methods

The C# record to be implemented will be used to track patient hospitalization for a heath dashboard. That is certainly information that is important to provide but should also be immutable. The properties exposed by the Patients record are as follows:

  • IsCovid19: true if patients are Covid-19 positive
  • IsIcu: true if patients are in the I.C.U.
  • IsVentilator: true if patients use ventilators.
  • Count; number of patients
  • Start: patient information start date
  • End: patient information end date
  • RangeType: type of patient information date range (day, week, month, quarter, biannual, year or custom)

The DataRangeType enumeration is used to specify the RangeType property of the Patients record:


The code for the Patients record is shown below. Some things to note with regards to the code:

  • There is no set for each property (lines 18 to 30) as the properties can only be assigned at initialization. The init keyword could also be explicitly specified (see C# 9.0: Init Only Setters).
  • Fit and Finish (discussed in C# 9.0: Fit and Finish) is used at lines 50, 60, 70, 90, 100, and 111.
  • All source code below is provide in text form in Appendix C: Source Code.



Assigning a value to a Property After Instantiation

The code snippet below results in a complication error as it is a compile time error to modify a field or property of a record after it is instantiated:


The compiler errors from Visual Studio Code caused by line 116 (above) are as follows:



Appendix A: Comparing Record Type Instances

Microsoft's documentation on record types, Create Record Types, provides an excellent explanation of how record type instances are compared:

Appendix B: Record Type Compiler Synthesized Methods

Microsoft's overview of C# 9.0 features contains a comprehensive overview of record types (see What's new in C# 9.0: Record Types). To quote Microsoft's documentation on records: 

Appendix C: Source Code

using System;

namespace HealthDashboard.Models 
{
    public enum DateRangeType 
    {
        Day,
        Week,
        Month,
        Quarter,
        Biannual,
        Year,
        Custom
    }

    public record Patients 
    {
        public bool IsCovid19 { get; }

        public bool IsIcu { get; }    

        public bool IsVentilator { get; }    

        public int Count { get; }

        public DateTime Start { get; }   

        public DateTime End { get; }   

        public DateRangeType RangeType { get; }

        public Patients(
                    int count, 
                    bool isCovid19, 
                    bool isIcu, 
                    bool isVentilator,
                    DateTime start,
                    DateTime end,
                    DateRangeType rangeType) => 
                        (Count, IsCovid19, IsIcu, IsVentilator, 
                                Start, End, RangeType) =
                            (count, isCovid19, isIcu, isVentilator, 
                                start.Date, end.Date, rangeType);

        public static Patients CreateDay(
                    int count, 
                    bool isCovid19, 
                    bool isIcu, 
                    bool isVentilator,
                    DateTime start)
        {
            return new(count, isCovid19, isIcu, isVentilator, start, 
                       start.AddDays(1), DateRangeType.Day);
        }                  
          
        public static Patients CreateWeek(
                    int count, 
                    bool isCovid19, 
                    bool isIcu, 
                    bool isVentilator,
                    DateTime start)
        {
            return new(count, isCovid19, isIcu, isVentilator, start, 
                       start.AddDays(7), DateRangeType.Week);
        }             
               
        public static Patients CreateMonth(
                    int count, 
                    bool isCovid19, 
                    bool isIcu, 
                    bool isVentilator,
                    DateTime start)
        {
            return new(count, isCovid19, isIcu, isVentilator, start, 
                       start.AddMonths(1), DateRangeType.Month);
        }              
             
        public static Patients CreateQuarter(
                    int count, 
                    bool isCovid19, 
                    bool isIcu, 
                    bool isVentilator,
                    DateTime start)
        {
            return new(count, isCovid19, isIcu, isVentilator, start, 
                       start.AddMonths(3), DateRangeType.Quarter);
        }              
              
        public static Patients CreateBiannual(
                    int count, 
                    bool isCovid19, 
                    bool isIcu, 
                    bool isVentilator,
                    DateTime start)
        {
            return new(count, isCovid19, isIcu, isVentilator, start, 
                       start.AddMonths(6), DateRangeType.Biannual);
        }              
              
        public static Patients CreateYear(
                    int count, 
                    bool isCovid19, 
                    bool isIcu, 
                    bool isVentilator,
                    DateTime start)
        {
            return new(count, isCovid19, isIcu, isVentilator, start, 
                       start.AddYears(1), DateRangeType.Year);
        }              
              
        public static Patients CreateCustom(
                    int count, 
                    bool isCovid19, 
                    bool isIcu, 
                    bool isVentilator,
                    DateTime start,
                    DateTime end)
        {
            return new(count, isCovid19, isIcu, isVentilator, start, 
                       end, DateRangeType.Custom);
        }                            
    }
}