Updating .NET Core inside AppVeyor

I currently use appveyor to run Continuous Integration builds for FluentValidation. It’s really easy to get started with appveyor as the build images come pre-installed with most tools you’d want to use as part of a build. The downside is it’s not easy to specify which version of a particular tool you want to use, and appveyor often lags behind with new releases.

For example, for FluentValidation I want the master branch to be built against the current LTS version of the .NET Core SDK (2.1.400), and I want my vNext branch to build against 2.2-preview1. But appveyor only has version 2.1.300 installed, so both branches need a new version of the .NET SDK.

Based on this post by Andrew Lock, I updated the FluentValidation build script to download an install a newer .NET Core SDK if needed. The build script uses powershell with the posh-build helpers for defining targets:

target install-dotnet-core {
  # Ensures that .net core is up to date.
  # first get the required version from global.json
  $json = ConvertFrom-Json (Get-Content "$path/global.json" -Raw)
  $required_version = $json.sdk.version

  # Running dotnet --version stupidly fails if the required SDK version is higher 
  # than the currently installed version. So move global.json out the way 
  # and then put it back again 
  Rename-Item "$path/global.json" "$path/global.json.bak"
  $current_version = (dotnet --version)
  Rename-Item "$path/global.json.bak" "$path/global.json"
  Write-Host "Required .NET version: $required_version Installed: $current_version"

  if ($current_version -lt $required_version) {
    # Current installed version is too low.
    # Install new version as a local only dependency. 
    $urlCurrent = "https://dotnetcli.blob.core.windows.net/dotnet/Sdk/$required_version/dotnet-sdk-$required_version-win-x64.zip"
    Write-Host "Installing .NET Core $required_version from $urlCurrent"
    $env:DOTNET_INSTALL_DIR = "$path/.dotnetsdk"
    New-Item -Type Directory $env:DOTNET_INSTALL_DIR -Force | Out-Null
    (New-Object System.Net.WebClient).DownloadFile($urlCurrent, "dotnet.zip")
    Write-Host "Unzipping to $env:DOTNET_INSTALL_DIR"
    Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory("dotnet.zip", $env:DOTNET_INSTALL_DIR)
  }
}

Here I do the following:

  • Check which version of .net core is needed based on what’s defined in my global.json
  • Check the current .net core installed version (working around that dotnet --version fails if there’s a mismatch between required and expected version)
  • Download the new SDK and unzip it to a local directory
  • Update the environment PATH variable so that the new version is used whenever dotnet is called.

This script can then be called as part of the install section of your appveyor.yml.

The full example is available in the FluentValidation repository.

Written on August 28, 2018

FluentValidation 8.0 released

FluentValidation is out and available to download from Nuget. This is a major release with several breaking change, so please make sure you read the upgrade notes before upgrading.

Validating properties by path

You can now validate specific properties using a full path, eg:

validator.Validate(customer, "Address.Line1", "Address.Line2");

Validating a specific ruleset with SetValidator

Previously, if you defined a child validator with SetValidator, then whichever ruleset you invoked on the parent validator will cascade to the child validator. Now you can explicitly define which ruleset will run on the child:

RuleFor(x => x.Address).SetValidator(new AddressValidator(), "myRuleset");

AttrbiutedValidatorFactory has been moved to a separate package

The ValidatorAttribute and the AttributedValidatorFactory were typically used in MVC/WebApi projects to wire models to their validators. This is no longer recommended when usign AspNetCore as the built-in Service Provider is a better alternative. These classes can still be used by explicitly installing the FluentValidation.ValidatorAttribute package. Note this package is installed by default if you are using the legacy MVC5/WebApi integration rather than AspNetCore.

SetCollectionValidator is deprecated

RuleForEach provides a more flexible syntax for the same result.

Async changes

Internally, the asynchronous validation API has been cleaned up thanks to await\async. From a consumer’s point of view, the asynchronous methods should all continue to work as before with the exception of some methods that previously didn’t take a CancellationToken that now do.

The full changelog is available here

Written on August 16, 2018

Powershell SSH Connection manager

I’ve recently a connection manager for SSH connections to the posh-sshell project.

What is Posh-Sshell?

Posh-Sshell is a set of powershell scripts that making working with SSH agents and clients easier. These utilities were originally part of the posh-git project, but have been separated into a separate module in preparation for the Posh-Git 1.0 release.

Posh-Sshell can be downloaded from the powershell gallery by running the following in a powershell prompt:

Install-Module Posh-Sshell -Scope CurrentUser

Once installed, import the module with Import-Module Posh-Shell (you can add this to your powershell profile so you don’t need to run it every time you launch a new terminal).

Connection Manager

Since splitting the SSH functionality out of posh-git, I’ve been working on several new features the first of which is the Connection Manager.

The Connection Manager can be used to display a list of SSH connections as well as add/remove connections from the ~/.ssh/config file. By running Connect-Ssh, you’ll be presented with a list of connections stored in your .ssh/config file:

image

You can enter the number of the server into the prompt, and an SSH connection will be made to that server. If no username is specified in the configuration file, then you’ll also be prompted for a username.

Adding a New Connection

A new connection can be added by running Add-SshConnection. In its simplest form, it takes an alias and a URI, eg:

Add-SshConnection MyServer3 myserver.mydomain.com

You can also specify additional common properties such as the username, either by using the -User parameter or using the user@host syntax:

# These are both the same
Add-SshConnection MyServer3 myserver.mydomain.com -User jeremy
Add-SshConnection MyServer3 jeremy@myserver.mydomain.com

You can specify a custom key file by using -IdentityFile <path> as well as parameters for configuring an SSH tunnel with -LocalTunnelPort <port number> and -RemoteTunnelPort <port number>.

Additional parameters can also be specified by supplying a hashtable to the -AdditionalOptions parameter.

After running Add-SshConnection MyServer3 myserver.mydomain.com -User jeremy, your .ssh/config file will contain the new entry:

Host MyServer3
  HostName myserver.mydomain.com
  User jeremy

Removing an entry

Entries can be removed from the config file by running Remove-SshConnection <name>

Written on July 30, 2018

Thoughts on using Drupal

After 10 years working with ASP.NET and C#, I’ve spent the last few months working with Drupal 8 and PHP. The last time I worked with PHP was in 2005, and although the language is still full of inconsistencies, it’s nice to see that it’s a fully object-oriented language and supports more modern functionality like anonymous functions and traits.

Working with Drupal 8 has been quite a learning experience. Drupal is an extremely powerful platform, but has a huge learning curve. The documentation is pretty poor compared to the very well-written in-depth tutorials and API docs that ASP.NET provides. However It’s good to see that Drupal 8 has embraced a more MVC-based approach (by using Symfony controllers), dependency injection and plugins. Sadly the horrible hook-based extensibility system hasn’t completely gone away yet.

The hardest thing I’ve found with Drupal is following an application’s flow. Diving into an existing codebase and trying to find where a piece of functionality is implemented is something I found extremely challenging. For example a button on a form could be defined directly in a Form class, through a YAML file, or any number of hooks in any module within the application. I suppose this is the downside of an extremely flexible extensibility model.

I’ve also been enjoying getting much more into Linux and server management using the Bash CLI, something I’ve had very little experience with (my CLI of choice is usually Powershell)

Overall C#/.NET would still be my platform of choice, but learning something new and working on some very interesting projects with a great team is much more important to me than choice of language.

Written on July 28, 2018

Using the same Home Directory in Windows and WSL

I’ve recently been using the Windows Subsystem for Linux (WSL) a lot more recently now that I’m working with Linux servers.

One thing I’ve found frustrating is having to keep my home directories in sync, for example having to duplicate my ssh keys between my Windows home directory in c:\users\Jeremy\.ssh and my WSL installation in \home\jeremy\.ssh, along with various other files like .gitconfig.

It turns out it’s actually pretty straight forward to set WSL to use your Windows home directory.

First, within WSL edit the /etc/passwd file (eg with sudo nano /etc/passwd). Find the line that defines your username (probably at the bottom), which will look something like this:

jeremy:x:1000:1000:,,,:/home/jeremy:/bin/bash

…and change the home directory path so that it points to your Windows home directory using WSL path notation:

jeremy:x:1000:1000:,,,:/mnt/c/users/jeremy:/bin/bash

Exit WSL and re-open it, and your home directory will now match your windows directory.

Metadata and File Permissions

A problem with this approach is that your ssh keys need to be secured, but by default Windows files accessed through WSL are readable/writable by everyone and chmod has no affect on Windows files. This can be remedied by re-mounting your Windows partition inside WSL with the metdata option. Edit the /etc/wsl.conf file (create it if it doesn’t exist) and add the following:

[automount]
options = "metadata"

Log out from WSL and log in again, and now the windows partition will be mounted with metadata and chmod will work against windows files. You can now chmod 600 ~/.ssh/id_rsa and everything will work correctly.

Written on July 27, 2018