Mike Cole

A blog about development technologies, with a Microsoft and JavaScript tilt.

profile for Mike Cole at Stack Overflow, Q&A for professional and enthusiast programmers

Azure App Service deployments using slot swapping is an easy way to do zero-downtime deployments. The basic workflow is to deploy your app to the staging slot in Azure, then swap the slot with production. Azure magic ensure that the application will cut over to the new version without a disruption as long as you’ve architected your application in a way that supports this. When reading about the az webapp deployment slot swap command, one of the first steps is -supposed- to warm up the slot automatically. In reality what we were finding was a lengthy operation (20+ minutes) followed by a failure with no helpful details. As part of the troubleshooting process I added a step to ping the staging slot after deployment, and I saw that it resulted in random errors, mostly 503 Service Unavailable. This was happening on several different systems. I ended up adding a few retries and saw that the site would eventually successfully return 200 OK after a minute or so and then the slot swap step would work reliably within 2 minutes. It seems that when your site does not immediately return 200 OK when starting a slot swap, the command would freak out.

In our deployment GitHub Actions jobs, I added the following step to manually warm up the site. The warmup_url should point to a page on your staging slot. We have it pointing to a health checks endpoint so we can ensure stability before swapping. --connect-timeout=30 will attempt to connect to the site for 30 seconds, then retry 4 times with a 30 second delay in between attempts. -f argument will tell your retry loop to not fail the step if it receives an http error. --retry-all-errors is an aggressive form of retry which ensures a retry on pretty much everything that isn’t a 200 level success code. The second curl statement instructs the step to fail, which will fail the entire job altogether.

- name: Warmup
  if: inputs.warmup_url != ''
  run: |
    curl \
      --connect-timeout 30 \
      --retry 4 \
      --retry-delay 30 \
      -f \
      --retry-all-errors \
      $
    curl \
        --fail-with-body \
        $

After adding this to all of our slot swap deployments, we consistently saw quite a bit more reliable behavior, and no more frustrating 20 minute black hole failures.

I maintain several Chocolatey community packages on GitHub. I am using the chocolatey-au tool to automatically update new versions of software, running in an AppVeyor scheduled build. A few months ago the Zoom package stopped automatically updating, and it appeared that the AppVeyor build agent was not seeing the latest version of the Zoom version feed. After troubleshooting what I thought was a caching issue, I realized that the build agent was still running on a Windows Server 2012 OS. It appears the Zoom version feed observes the user agent HTTP request header, and serves the latest version that your operating system can support. Once I updated the build agent to use the Visual Studio 2022 image, the chocolatey-au tool worked as expected.

#TIL that you can RDP into an AppVeyor build agent to help troubleshoot issues. First you need to add the following to the on_finish block in your .yml file:

on_finish:    
- ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))

This will block the job from succeeding and output the RDP info in the console. Before re-running the job, you need to add an environment variable named APPVEYOR_RDP_PASSWORD to your build settings. The value of this will be the password of your RDP connection.

In a few projects I've taken on lately, the client has stated their desire to modernize their system. In both cases we were dealing with ASP.NET WebForms applications that have been around for years. Both systems worked great and there was no justification for a rewrite, although the architectural parts of the systems left something to be desired. We wanted to minimize the creation of new WebForms code and focus on React.

In addition to using WebForms, the client also had dated DevOps practices. No build server, no build scripts – just Right Click => Publish. Although this is something we will focus on later, at this time they had a feature they wanted implemented, so that took precedence. I was worried about the client's ability to consistently handle the webpack part of things since they were generally pretty new to JavaScript in general. I wanted to make it as pain free as possible and not force them into practices with which they were not yet comfortable.

I'll summarize the steps taken that are outside the scope of this post: I upgraded to the latest version of .NET Framework, configured WebAPI in the project, added SimpleInjector for DI, added some test projects, created a Cake build script to properly package up the project for deployment including running unit tests, publishing to disk, managing npm, and building the React application. I quickly realized the WebForms Master Page had layers and layers of logic that I didn't want to unravel and apply to an MVC Layout, so new pages are going to be basic ASPX pages that contain the React targets.

My main goal was to enable them to easily adopt React without necessarily having to understand the complexities of npm or webpack. I wanted to automate that stuff as much as possible. Step 1 was to install the NPM Task Runner extension into Visual Studio. This would give us the ability to bind npm scripts to Visual Studio events. The goal was to bind our webpack dev build to run in watch mode when the project was loaded.

{
	"name": "the-project",
	"version": "1.0.0",
	"private": true,
	"dependencies": {
		"axios": "^0.18.0",
		"babel-polyfill": "^6.26.0",
		"evergreen-ui": "^4.6.0",
		"react": "^16.4.1",
		"react-bootstrap": "^0.32.4",
		"react-dom": "^16.4.1"
	},
	"optionalDependencies": {
		"fsevents": "*"
	},
	"devDependencies": {
		"babel-core": "^6.26.3",
		"babel-loader": "^7.1.5",
		"babel-preset-env": "^1.7.0",
		"babel-preset-react": "^6.24.1",
		"babel-preset-stage-2": "^6.24.1",
		"clean-webpack-plugin": "^0.1.19",
		"css-loader": "^1.0.0",
		"node-sass": "^4.9.2",
		"sass-loader": "^7.0.3",
		"style-loader": "^0.21.0",
		"uglifyjs-webpack-plugin": "^1.2.7",
		"webpack": "^4.16.1",
		"webpack-cli": "^3.1.0",
		"webpack-dev-server": "^3.1.5",
		"webpack-merge": "^4.1.3"
	},
	"scripts": {
		"dev-build": "webpack -d --config ./webpack.dev.js",
		"prod-build": "webpack --config ./webpack.prod.js"
	},
	"babel": {
		"presets": [
			"env",
			"react",
			"stage-2"
		]
	},
	"-vs-binding": {
		"ProjectOpened": [
			"dev-build"
		]
	}
}

This is the project.json file in its entirety. You can see a few dependencies that I like to use, and a bunch of development dependencies for our build. We'll cover some of those later. Two things to note: the build scripts (one for dev and one for prod) and the -vs-binding section which instructs Visual Studio through NPM Task Runner to run the dev-build script when the project is loaded.

Now we'll look at three webpack files: the dev setup, the prod setup, and the common setup that is shared with dev and prod. I'm using the webpack-merge package to link them together.

const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
    entry: {
        login: './Scripts/apps/login.jsx',
        'mass-update': './Scripts/apps/mass-update/app.jsx',
    },
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                use: ['babel-loader'],
            },
            {
                test:/\.(s*)css$/,
                use:['style-loader','css-loader', 'sass-loader'],
            },
        ]
    },
    resolve: {
        extensions: ['*', '.js', '.jsx'],
    },
    output: {
        path: path.join(__dirname, 'scripts', 'dist'),
        publicPath: '/',
        filename: '[name].bundle.js',
    },
    plugins: [
        new CleanWebpackPlugin([path.join(__dirname, 'scripts', 'dist')]),
    ]
};
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common,
{
    mode: 'development',
    devtool: 'inline-source-map',
    devServer: {
        contentBase: './Scripts/dist'
    },
    watch: true,
    watchOptions: {
        aggregateTimeout: 300,
        poll: 1000,
        ignored: './node_modules/'
    }
});
const webpack = require('webpack');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');

module.exports = merge(common, {
    mode: 'production',
    devtool: 'source-map',
    plugins: [
        new UglifyJSPlugin({
            sourceMap: true
        }),
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify('production')
        })
    ]
});

The webpack.common.js file contains the base settings I want to use in both dev and prod. I have two SPAs defined: login and mass-update. It's a fairly generic webpack script other than the merging. The webpack.dev.js file merges in the webpack.common.js file, sets my development settings, and specifies to watch the content for changes which it will rebuild. The webpack.prod.js file is the production version which parses/compresses the compiles JS and sets an environment flag to production which I can test later in the project.

I am using both SPAs differently in the site. The mass-update app was a brand new feature, so it gets its own barebones aspx page and runs the show. The login app was an addition to an existing feature and is more of an applet that's bound to an existing page.

So far this setup has worked very well. We're able to develop new features using modern technologies in the existing codebase (albeit with some architectural improvements). We didn't have to do a costly rewrite which would have been completely unreasonable, and we're able to feel pretty good about working with React and WebAPI. We expect most of the new development will be done using our new platform, and we'll port over other sections when it makes sense.

Most companies have similar systems sitting around that they feel like they need to re-write to stay modern. If it works, it works, and if it's in no danger of becoming unsupported or incompatible then you don't need to rewrite it. Don't let consulting companies pressure you into a rewrite. I am available for hire as a strategic consultant, an architect, and/or a developer and I can help you make sense of your situation and plot a course of action. You can find my info at https://cole-consulting.net. Feel free to reach out if you find yourself in a similar situation and feel like you could use my services.

We've been using Vuetify for a component framework in the Vue project we've been working on the past few months. It's a great framework and I highly recommend it, especially if you're trying to follow Material Design patterns. Today I ran into an issue customizing a grid header that was difficult to solve.

Here's the example on CodePen: https://codepen.io/mikecole/pen/zLNKbG

If you check the header of any column, you can see that I added a second v-icon that will eventually pop open a dropdown menu. However, when sorting the column Vuetify doesn't distinguish that column from the arrow sorting icon, and it spins both of them.

It turns out that there doesn't seem to be any options to disable this, and that the solution seems to be fixing it with stying: ``` ... filter_list ... .v-datatable thead th.column.sortable.active.desc .no-rotate { transform: none !important; } ```

Fixed example on CodePen: https://codepen.io/aldarund/pen/yqgVgy