Azure DevOps CI Pipeline Fails with .NET Core 2.2

I tried putting a test CI build from Azure DevOps (formally VSTS) to a test web site using NET Core 2.2 today, but it failed with “Packages failed to restore”. Further investigation revealed that it failed in the “_CheckForUnsupportedNETCoreVersion” step.

It appears that if you are using .NET Core 2.2 as a target it will not work right off the bat, you need to tailor your CI Pipeline to add a task to the agent job to install the version of the .NET Core SDK you are using, in my case 2.2.102.

I assume this is only a temporary issue, but you may run into a similar problem in the future as new versions on .NET Core are released.

Posted in ASP.NET, Azure | Tagged , , | Leave a comment

Customising Bootstrap in ASP .NET Core 2.2 Web Application Projects

The default Web Application template for .NET Core 2.2 in Visual Studio 2017 contains a default implementation of Bootstrap in the “wwwroot/lib” folder, which is fine if you are not concerned about your application/site being styled like every other Bootstrap site or including sizeable css and js files regardless of whether you are using all the features of Bootstrap or not.

My solution for a customised, trimmed down Bootstrap is to use npm and webpack to create your own custom bundle of Bootstrap css and js. My choice of npm is simply because I use this when developing frontend apps so it is familiar territory

Whilst this post is concerned with customising Bootstrap for use in a .NET Core app, the same technique can be used for other projects.

If you have not installed them already, ensure you have Visual Studio 2017 (Community or other), .NET Core SDK 2.2 and Node.js installed. You will also need the “ASP.NET and web development” workload installed within Visual Studio 2017.

Include “ASP.NET and web development” in Visual Studio 2017
Install .NET Core 2.2 SDK
Install node.js which includes npm

When you first create a .NET Core Web Application you will end up with a folder structure looking something like this:

Inital file list

Delete the “wwwroot/lib/bootstrap” folder and all its contents and sub-folders. We will not be using it anymore.

In Visual Studio 2017 add a “npm Configuration File” in the project’s root folder called package.json.

Add the npm config file

Note: When installing packages with npm we need to be in the project folder, not the solution folder (if different). Also, be careful you are not editing the npm config file when you install a package as npm will try to update the config file and may fail to do so if you have it locked.

Open a command prompt in the project’s root folder and issue the following commands to install the packages we are going to use:

npm install --save bootstrap popper.js jquery
npm install --save-dev webpack webpack-cli
npm install --save-dev sass-loader node-sass
npm install --save-dev babel-loader @babel/core @babel/preset-env
npm install --save-dev uglifyjs-webpack-plugin

Update the package.json file to have a scripts section with commands that we will use to build our bundled files, snip:

"scripts": {
    "bootstrap-js": "webpack --mode production --progress --profile --config webpack.bootstrap.js",
    "bootstrap-css": "node-sass --output-style compressed client/css/bootstrap.scss wwwroot/css/bootstrap.min.css",
    "bundles": "npm run bootstrap-js && npm run boostrap-css"
}

Create a “client” folder in the project’s root folder, and within the “client” folder create a “js” and “css” folder.

Create “client/css/_custom.scss” with the following test content (we can override any of the Bootstrap sass variables in this file to change the Bootstrap look and feel):

// Will change the backcolor of the body to yellow instead of white
$body-bg: #f9fbaf;

Create “client/css/bootstrap.scss” with the following test content:

// import our application specific bootstrap overrides in _custom.scss
@import "custom";

// import whole of bootstrap
@import "~bootstrap/scss/bootstrap";

// or just import the bits of bootstrap we will be using 
// (look in bootstrap's bootstrap.scss file for full list)
//@import "~bootstrap/scss/functions";
//@import "~bootstrap/scss/variables";
//@import "~bootstrap/scss/mixins";
//@import "~bootstrap/scss/root";
//@import "~bootstrap/scss/reboot";
//@import "~bootstrap/scss/type";
//@import "~bootstrap/scss/grid";
//@import "~bootstrap/scss/buttons";

Within “client/js” create this bootstrap.js file:

// include all of bootstrap javascript
//import 'bootstrap';

// or include just the bits of bootstrap javascript we want
import 'bootstrap/js/dist/modal';
import 'bootstrap/js/dist/tooltip';

Use Visual Studio 2017 to add the following javascript file in the project’s root folder; “webpack.bootstrap.js”:

// path is so we can resolve relative paths later regardless 
// of operating system differences
const path = require('path');
// uglifyjs-webpack-plugin will allow us to minify our js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');

module.exports = {
    entry: {
        // js entry point
        bootstrap: './client/js/bootstrap.js'
    },
    // default mode
    mode: 'production',
    // apply rules
    module: {
        rules: [
            {
                // use babel to transpile latest js into 
                // something earlier browsers understand
                // may not be needed depending on source
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            }
        ]
    },
    optimization: {
        minimizer: [
            // js minification options
            new UglifyJsPlugin({
                cache: true,
                parallel: true,
                sourceMap: true
            )}
        ]
    },
    output: {
        // output js and map filenames
        filename: 'js/[name].min.js',
        path: path.resolve(__dirname, 'wwwroot'),
        sourceMapFilename: 'js/[name].js.map'
    },
    // make sure a .map is created
    devtool: 'source-map'
};

You can now use the following at a command prompt in the project’s root folder to generate the initial file bundles:

npm run bundles

We can now undertake customisation, for instance, changing the colours and fonts used in bootstrap, perhaps only including the javascript for modals and tooltips, and only including a selection of the Bootstrap CSS that we will actually be using. This is achieved by editing the content of the “client/js/bootstrap.js”, “client/css/_custom.scss” and “client/css/bootstrap.scss” files, and then re-running “npm run bundles” to recreate our bundle files.

We will also need to update the “Pages/Shared/_Layout.cshtml” file to reference our bootstrap files rather than the bootstrap cdn and lib folder javascript and css files, snip:

...
    <link rel="stylesheet" href="~/css/bootstrap.min.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
...
    <script src="~/js/bootstrap.min.js" asp-append-version="true"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
...

Whether you need to add or remove other libraries will be entirely dependant on what you plan on delivering within the web application.

One further change you can make is to ensure the bundles are built every time you build your project. To do this select “Tools > Other Windows > Task Runner Explorer”. In the Task Runner Explorer you should see your package.jso file and the script items we created under “Custom”. Right-click on the “bundles” item, choose “Bindings” and then “Before Build”, now whenever you build your project the bundles will be built first. However, you may find the build takes a little too long for your liking, in which case just go back to building the Bootstrap files on demand.

If you want to use Azure DevOps to build the project and Bootstrap bundles as part of a CI pipeline there are a few steps you will need to take.

Currently, you will need to include the .NET Core 2.2 SDK as it is not ready in Azure DevOps automatically as of this date. It maybe available when you come to follow this article, in which case this first step can be skipped. Add a “.NET Core SDK Installer” step at the top of the pipeline like this:

You will also need to have the pipeline install npm and its dependencies, so add a npm step with the install command after the Restore step ensuring you select the correct working folder, similar to this:

You can then add a npm command to build your bundles by adding a further npm step with a custom command of “run bundles” (or whatever script command you setup), again ensuring the correct working folder is selected, similar to this:


Posted in ASP.NET | Tagged , , , | Leave a comment

OpenCover and ReportGenerator Unit Test Coverage in Visual Studio 2013 and 2015

The usual choices for getting code coverage metrics in Visual Studio is to buy the Enterprise version of Visual Studio or buy the dotCover third party tool, both of which are costly, especially if you are developing small applications yourself and want to check the coverage of your unit tests.

There is a free NuGet package for generating code coverage metrics called OpenCover, and together with another free NuGet package, ReportGenerator, can give you code coverage reports for free.

The problem I found, however, was the documentation available to get the tool working was less than adequate for my purposes and it took several hours of faff to get it working for me. To save you time and money I have documented my experience here.

The following were all referenced in the creation of this document:

https://www.nuget.org/packages/OpenCover/

https://www.nuget.org/packages/ReportGenerator/

https://www.nuget.org/packages/NUnit.ConsoleRunner/

http://www.allenconway.net/2015/06/using-opencover-and-reportgenerator-to.html

http://www.nootn.com.au/2014/01/code-coverage-with-opencover-example.html#.VxiNn_krLDc

I have tested that this works in Visual Studio 2013 Professional and Visual Studio 2015 Community Edition, with both MSTest and NUnit.

MSTest unit tests can be run with either mstest.exe or the newer vstest.console.exe. As I primarily use NUnit I have not done anything other than basic testing to ensure OpenCover works with both these test runners so cannot say how either might work on large projects.

I assume you already have a solution with application project that you are testing with a test project, and where you are using NUnit for that testing that you have already included the NUnit NuGet package.

You will need to open your solution in Visual Studio and get the following packages using Package Manager Console:

REM PM> Install-Package OpenCover
REM PM> Install-Package ReportGenerator

If you are using NUnit rather than MSTest you will also need this package:

REM PM> Install-Package NUnit.ConsoleRunner

In the solution root folder you will then need to create a batch file that you will use to produce the code coverage report. I have three batch files shown below, one for use where you are using NUnit, one where you are using MSTest and the mstest.exe test runner, and one for where you are using MSTest and the vstest.console.exe test runner.

You will need to modify the batch file to specify the DLL that contains your unit tests. i.e. change this line as appropriate (note that %~dp0 is a symbol that is replaced automatically with the folder path of the batch file):

SET DllContainingTests=%~dp0WebApp.Tests\bin\Debug\WebApp.Tests.dll

You may also want to change the filter applied to the results to exclude parts of your solution where you are not interested in the code coverage metrics, in my case I have asked it to include everything (+[*]*), and then exclude my Tests project (-[*.Tests*]*) and all classes ending in Config (-[*]*.*Config), i.e.:

 -filter:"+[*]* -[*.Tests*]* -[*]*.*Config"

If you are using MSTest you may need to change the TestRunnerExe variable to point to the correct version of mstest.exe or vstest.runner.exe. The examples below are pointing to the ones in Visual Studio 2013, but if you are using 2015 you will need to change this to Microsoft Visual Studio 14.0 instead of Microsoft Visual Studio 12.0.

All that then remains is to execute the batch file to produce a report of code coverage that allows you to drill down to see the actual colour coded code being covered, or not.

NUnit Version:

@ECHO OFF

REM OpenCover-NUnit.bat

REM Run opencover against NUnit tests in your test project and show report of code coverage

REM Derivative work based on work by: 
REM  Shaun Wilde - https://www.nuget.org/packages/OpenCover/
REM  Daniel Palme - https://www.nuget.org/packages/ReportGenerator/
REM  Charlie Poole - https://www.nuget.org/packages/NUnit.ConsoleRunner/
REM  Allen Conway - 
REM   http://www.allenconway.net/2015/06/using-opencover-and-reportgenerator-to.html
REM  Andrew Newton - 
REM   http://www.nootn.com.au/2014/01/code-coverage-with-opencover-example.html#.VxiNn_krLDc


SET DllContainingTests=%~dp0WebApp.Tests\bin\Debug\WebApp.Tests.dll


REM *** IMPORTANT - Change DllContainingTests variable (above) to point to the DLL 
REM ***             in your solution containing your NUnit tests
REM ***
REM ***             You may also want to change the include/exclude filters (below) 
REM ***             for OpenCover
REM ***
REM ***             This batch file should dbe placed in the root folder of your solution

REM *** Before being able to use this to generate coverage reports you 
REM *** will need the following NuGet packages
REM PM> Install-Package OpenCover
REM PM> Install-Package ReportGenerator
REM PM> Install-Package NUnit.ConsoleRunner
REM

REM NUnit Test Runner (done this way so we dont have to change the code 
REM when the version number changes)
for /R "%~dp0packages" %%a in (*) do if /I "%%~nxa"=="nunit3-console.exe" SET TestRunnerExe=%%~dpnxa

REM Get OpenCover Executable (done this way so we dont have to change the 
REM code when the version number changes)
for /R "%~dp0packages" %%a in (*) do if /I "%%~nxa"=="OpenCover.Console.exe" SET OpenCoverExe=%%~dpnxa

REM Get Report Generator (done this way so we dont have to change the code 
REM when the version number changes)
for /R "%~dp0packages" %%a in (*) do if /I "%%~nxa"=="ReportGenerator.exe" SET ReportGeneratorExe=%%~dpnxa

REM Create a 'GeneratedReports' folder if it does not exist
if not exist "%~dp0GeneratedReports" mkdir "%~dp0GeneratedReports"

REM Run the tests against the targeted output
call :RunOpenCoverUnitTestMetrics

REM Generate the report output based on the test results
if %errorlevel% equ 0 ( 
 call :RunReportGeneratorOutput 
)

REM Launch the report
if %errorlevel% equ 0 ( 
 call :RunLaunchReport 
)
exit /b %errorlevel%

:RunOpenCoverUnitTestMetrics 
REM *** Change the filter to include/exclude parts of the solution you want 
REM *** to check for test coverage
"%OpenCoverExe%" ^
 -target:"%TestRunnerExe%" ^
 -targetargs:"\"%DllContainingTests%\"" ^
 -filter:"+[*]* -[*.Tests*]* -[*]*.*Config" ^
 -mergebyhash ^
 -skipautoprops ^
 -register:user ^
 -output:"%~dp0GeneratedReports\CoverageReport.xml"
exit /b %errorlevel%

:RunReportGeneratorOutput
"%ReportGeneratorExe%" ^
 -reports:"%~dp0\GeneratedReports\CoverageReport.xml" ^
 -targetdir:"%~dp0\GeneratedReports\ReportGenerator Output"
exit /b %errorlevel%

:RunLaunchReport
start "report" "%~dp0\GeneratedReports\ReportGenerator Output\index.htm"
exit /b %errorlevel%

MSTest Version Using mstest.exe:

@ECHO OFF

REM OpenCover-MSTest.bat

REM Run opencover against MSTest tests in your test project and show report of code coverage

REM Derivative work based on work by: 
REM  Shaun Wilde - https://www.nuget.org/packages/OpenCover/
REM  Daniel Palme - https://www.nuget.org/packages/ReportGenerator/
REM  Allen Conway - 
REM   http://www.allenconway.net/2015/06/using-opencover-and-reportgenerator-to.html
REM  Andrew Newton - 
REM   http://www.nootn.com.au/2014/01/code-coverage-with-opencover-example.html#.VxiNn_krLDc


SET DllContainingTests=%~dp0WebApp.Tests\bin\Debug\WebApp.Tests.dll


REM *** IMPORTANT - Change DllContainingTests variable (above) to point to the DLL 
REM ***             in your solution containing your NUnit tests
REM ***
REM ***             You may also want to change the include/exclude filters 
REM ***             (below) for OpenCover
REM ***
REM ***             This batch file should dbe placed in the root folder of your solution

REM *** Before being able to use this to generate coverage reports you will 
REM *** need the following NuGet packages
REM PM> Install-Package OpenCover
REM PM> Install-Package ReportGenerator
REM

REM *** MSTest Test Runner (VS2013, will need to change 12.0 to 14.0 for VS2015)
SET TestRunnerExe=%PROGRAMFILES(X86)%\Microsoft Visual Studio 12.0\Common7\IDE\MSTest.exe

REM Get OpenCover Executable (done this way so we dont have to change 
REM the code when the version number changes)
for /R "%~dp0packages" %%a in (*) do if /I "%%~nxa"=="OpenCover.Console.exe" SET OpenCoverExe=%%~dpnxa

REM Get Report Generator (done this way so we dont have to change the code 
REM when the version number changes)
for /R "%~dp0packages" %%a in (*) do if /I "%%~nxa"=="ReportGenerator.exe" SET ReportGeneratorExe=%%~dpnxa

REM Create a 'GeneratedReports' folder if it does not exist
if not exist "%~dp0GeneratedReports" mkdir "%~dp0GeneratedReports"

REM Run the tests against the targeted output
call :RunOpenCoverUnitTestMetrics

REM Generate the report output based on the test results
if %errorlevel% equ 0 ( 
 call :RunReportGeneratorOutput 
)

REM Launch the report
if %errorlevel% equ 0 ( 
 call :RunLaunchReport 
)
exit /b %errorlevel%

:RunOpenCoverUnitTestMetrics 
REM *** Change the filter to include/exclude parts of the solution you want to 
REM *** check for test coverage
"%OpenCoverExe%" ^
 -target:"%TestRunnerExe%" ^
 -targetargs:"/noisolation /testcontainer:\"%DllContainingTests%\"" ^
 -filter:"+[*]* -[*.Tests*]* -[*]*.Global -[*]*.RouteConfig -[*]*.WebApiConfig" ^
 -mergebyhash ^
 -skipautoprops ^
 -register:user ^
 -output:"%~dp0GeneratedReports\CoverageReport.xml"
exit /b %errorlevel%

:RunReportGeneratorOutput
"%ReportGeneratorExe%" ^
 -reports:"%~dp0\GeneratedReports\CoverageReport.xml" ^
 -targetdir:"%~dp0\GeneratedReports\ReportGenerator Output"
exit /b %errorlevel%

:RunLaunchReport
start "report" "%~dp0\GeneratedReports\ReportGenerator Output\index.htm"
exit /b %errorlevel%

MSTest Version Using vstest.console.exe:

@ECHO OFF

REM OpenCover-VSTest.bat

REM Run opencover against MSTest tests in your test project and show report of code coverage

REM Derivative work based on work by: 
REM  Shaun Wilde - https://www.nuget.org/packages/OpenCover/
REM  Daniel Palme - https://www.nuget.org/packages/ReportGenerator/
REM  Allen Conway - 
REM   http://www.allenconway.net/2015/06/using-opencover-and-reportgenerator-to.html
REM  Andrew Newton - 
REM   http://www.nootn.com.au/2014/01/code-coverage-with-opencover-example.html#.VxiNn_krLDc


SET DllContainingTests=%~dp0WebApp.Tests\bin\Debug\WebApp.Tests.dll


REM *** IMPORTANT - Change DllContainingTests variable (above) to point to the DLL 
REM ***             in your solution containing your NUnit tests
REM ***
REM ***             You may also want to change the include/exclude filters 
REM ***             (below) for OpenCover
REM ***
REM ***             This batch file should dbe placed in the root folder of your solution

REM *** Before being able to use this to generate coverage reports you will 
REM *** need the following NuGet packages
REM PM> Install-Package OpenCover
REM PM> Install-Package ReportGenerator
REM

REM *** MSTest Test Runner (VS2013, will need to change 12.0 to 14.0 for VS2015)
SET TestRunnerExe=%PROGRAMFILES(X86)%\Microsoft Visual Studio 12.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe

REM Get OpenCover Executable (done this way so we dont have to change 
REM the code when the version number changes)
for /R "%~dp0packages" %%a in (*) do if /I "%%~nxa"=="OpenCover.Console.exe" SET OpenCoverExe=%%~dpnxa

REM Get Report Generator (done this way so we dont have to change the code 
REM when the version number changes)
for /R "%~dp0packages" %%a in (*) do if /I "%%~nxa"=="ReportGenerator.exe" SET ReportGeneratorExe=%%~dpnxa

REM Create a 'GeneratedReports' folder if it does not exist
if not exist "%~dp0GeneratedReports" mkdir "%~dp0GeneratedReports"

REM Run the tests against the targeted output
call :RunOpenCoverUnitTestMetrics

REM Generate the report output based on the test results
if %errorlevel% equ 0 ( 
 call :RunReportGeneratorOutput 
)

REM Launch the report
if %errorlevel% equ 0 ( 
 call :RunLaunchReport 
)
exit /b %errorlevel%

:RunOpenCoverUnitTestMetrics 
REM *** Change the filter to include/exclude parts of the solution you want to 
REM *** check for test coverage
"%OpenCoverExe%" ^
 -target:"%TestRunnerExe%" ^
 -targetargs:"\"%DllContainingTests%\"" ^
 -filter:"+[*]* -[*.Tests*]* -[*]*.Global -[*]*.RouteConfig -[*]*.WebApiConfig" ^
 -mergebyhash ^
 -skipautoprops ^
 -register:user ^
 -output:"%~dp0GeneratedReports\CoverageReport.xml"
exit /b %errorlevel%

:RunReportGeneratorOutput
"%ReportGeneratorExe%" ^
 -reports:"%~dp0\GeneratedReports\CoverageReport.xml" ^
 -targetdir:"%~dp0\GeneratedReports\ReportGenerator Output"
exit /b %errorlevel%

:RunLaunchReport
start "report" "%~dp0\GeneratedReports\ReportGenerator Output\index.htm"
exit /b %errorlevel%
Posted in ASP.NET, Unit Testing, Web API | Leave a comment

Anti Forgery Tokens with AngularJS and ASP.NET Web API

Single Page Applications using AngularJS with ASP.NET will by default leave our web api methods open to forgery abuse. A few simple steps will allow you to add anti forgery protection. Continue reading

Posted in Web API | Tagged , | Leave a comment