.Net Core

Azure DevOps Pipelines: Reusable YAML

In this post, we are going to refactor our sample Azure DevOps Pipeline to move some of the redundant YAML to a new file and replace the redundant parts of our main YAML file. This post is going to build on the Azure DevOps project created in previous posts. If you are just joining this series check out the previous posts to find out how the project has progressed.

Getting Started with Azure DevOps
Pipeline Creation in Azure DevOps
Azure DevOps Publish Artifacts for ASP.NET Core
Azure DevOps Pipelines: Multiple Jobs in YAML

Starting YAML

The following is the YAML for our current pipeline that builds two different web applications using two different jobs. Looking at the two jobs you will notice that they both have the same steps. The only difference in the steps is which project to build (WebApp1.csproj or WebApp2.csproj) and what to call the published artifact (WebApp1 or WebApp2). When developing applications we would never stand for this level of duplication and the same should apply to our pipelines.

trigger: none

variables:
  buildConfiguration: 'Release'

jobs:
- job: WebApp1
  displayName: 'Build WebApp1'
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  - task: [email protected]
    displayName: 'Use .NET 3.1.x'
    inputs:
      packageType: 'sdk'
      version: '3.1.x'

  - task: [email protected]
    displayName: 'Build'
    inputs:
      command: 'build'
      projects: '**/WebApp1.csproj'
      arguments: '--configuration $(buildConfiguration)' 
  
  - task: [email protected]
    displayName: 'Publish Application'
    inputs:
      command: 'publish'
      publishWebProjects: false
      projects: '**/WebApp1.csproj'
      arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'

  - task: [email protected]
    displayName: 'Publish Artifacts'
    inputs:
      targetPath: '$(Build.ArtifactStagingDirectory)'
      artifact: 'WebApp1'
      publishLocation: 'pipeline'

- job: WebApp2
  displayName: 'Build WebApp2'
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  - task: [email protected]
    displayName: 'Use .NET 3.1.x'
    inputs:
      packageType: 'sdk'
      version: '3.1.x'

  - task: [email protected]
    displayName: 'Build'
    inputs:
      command: 'build'
      projects: '**/WebApp2.csproj'
      arguments: '--configuration $(buildConfiguration)' 
  
  - task: [email protected]
    displayName: 'Publish Application'
    inputs:
      command: 'publish'
      publishWebProjects: false
      projects: '**/WebApp2.csproj'
      arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'

  - task: [email protected]
    displayName: 'Publish Artifacts'
    inputs:
      targetPath: '$(Build.ArtifactStagingDirectory)'
      artifact: 'WebApp2'
      publishLocation: 'pipeline'

Add a New File

To attack the duplication above we need to take the shared steps from above and move them somewhere they can be reused. We will be walking through the steps using the Azure DevOps web site and committing directly to the master branch, but these same steps could be performed locally or on the web on any branch. First, from the Repos section of the site we need to add a new file by clicking the three dots at the level we want the file added. In this case, we are adding to the root of the repo but the same option is available on any folder.

A dialog will show where you can enter the New file name, we are going to use build.yml in this case. Next, click Create to continue.

Shared YAML

Now that we have a new file we can start building the new YAML that will handle the repeated steps from the original jobs. The first thing we are going to do is define a set of parameters that this set of steps can be called with. We are going to use this to pass what project to build, which build configuration to use, and what name the published artifact. The following is the definition of our parameters.

parameters:
- name: buildConfiguration
  type: string
  default: 'Release'
- name: project
  type: string
  default: ''
- name: artifactName
  type: string
  default: ''

We can then use these parameters in the rest of the file using the ${{ parameterName }} syntax. Note that any pipeline variables are also available using the $(variableName) syntax. The following bit of YAML shows both types in the arguments line.

- task: [email protected]
  displayName: 'Publish Application'
  inputs:
    command: 'publish'
    publishWebProjects: false
    projects: '**/${{ parameters.project }}'
    arguments: '--configuration ${{ parameters.buildConfiguration }} --output $(Build.ArtifactStagingDirectory)'

While you can use pipeline variables I recommend passing all the values you need via parameters for the same reason that we try to avoid global variables when doing general programming. I’m using both here to show the usage of each. The following is the full YAML in our new file.

parameters:
- name: buildConfiguration
  type: string
  default: 'Release'
- name: project
  type: string
  default: ''
- name: artifactName
  type: string
  default: ''

steps:
  - task: [email protected]
    displayName: 'Use .NET 3.1.x'
    inputs:
      packageType: 'sdk'
      version: '3.1.x'

  - task: [email protected]
    displayName: 'Build'
    inputs:
      command: 'build'
      projects: '**/${{ parameters.project }}'
      arguments: '--configuration ${{ parameters.buildConfiguration }}' 
  
  - task: [email protected]
    displayName: 'Publish Application'
    inputs:
      command: 'publish'
      publishWebProjects: false
      projects: '**/${{ parameters.project }}'
      arguments: '--configuration ${{ parameters.buildConfiguration }} --output $(Build.ArtifactStagingDirectory)'

  - task: [email protected]
    displayName: 'Publish Artifacts'
    inputs:
      targetPath: '$(Build.ArtifactStagingDirectory)'
      artifact: ${{ parameters.artifactName }}
      publishLocation: 'pipeline'

Finally, commit the changes to the new file.

Using Shared YAML

Not that we have the YAML that is the same between our two build jobs we can switch back over to our main YAML file, azure-pipelines.yml in the sample, and remove the steps we are wanting to replace. While the jobs will both have a steps section the only thing we will have left in them is a template call to our other YAML file, build.yml for the sample, that passes the parameters to run the other file with. The following is the resulting YAML file with the call to the shared file in both jobs highlighted.

trigger: none

variables:
  buildConfiguration: 'Release'

jobs:
- job: WebApp1
  displayName: 'Build WebApp1'
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  - template: build.yml
    parameters:
      buildConFiguration: $(buildConfiguration)
      project: WebApp1.csproj
      artifactName: WebApp1

- job: WebApp2
  displayName: 'Build WebApp2'
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  - template: build.yml
    parameters:
      buildConFiguration: $(buildConfiguration)
      project: WebApp2.csproj
      artifactName: WebApp2

Wrapping Up

Being able to remove duplication from your YAML files should help improve the maintainability of your pipelines. I know the samples don’t show it, but the template is just a step and you could have other steps before or after it just like you would with normal tasks.

Azure DevOps Pipelines: Multiple Jobs in YAML

This post is going to show how to run multiple jobs out of a single YAML file from an Azure DevOps Pipeline. This post is going to build on the Azure DevOps project created in previous posts. If you are just joining this series check out the previous posts to find out how the project has progressed.

Getting Started with Azure DevOps
Pipeline Creation in Azure DevOps
Azure DevOps Publish Artifacts for ASP.NET Core

Starting Point and the Plan

As the sample stands now we have a single Pipeline that builds two different ASP.NET Core web applications in a single job using the following YAML.

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

variables:
  buildConfiguration: 'Release'

steps:
- task: [email protected]
  inputs:
    packageType: 'sdk'
    version: '3.1.x'

- script: dotnet build --configuration $(buildConfiguration)
  displayName: 'dotnet build $(buildConfiguration)'
  
- task: [email protected]
  inputs:
    command: 'publish'
    publishWebProjects: true
    arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'

- task: [email protected]
  inputs:
    targetPath: '$(Build.ArtifactStagingDirectory)'
    publishLocation: 'pipeline'

This post is going to take this pipeline and split the build and publish of the two web applications and make each application its own job. In Pipelines a job is something that a single agent takes and runs. By splitting into multiple jobs the pipeline can run multiple jobs at the same time if you have enough build agents available. One reason to do this would be to speed up the total Pipeline run if you have parts of your build that are independent. Another example of why you would need jobs is if the different jobs need different agents such as one needing a Windows agent and another a Linux agent.

Creating the Jobs

Having different jobs means we are going to have to move things like what agent pool to use and the steps for the job under a jobs element and then declare a specific job and the details that job needs to run. As you can see in the following example the end goal is the same as the YAML from above (except it is dealing with a specific project), but the details are nested under jobs and defined under a job.

trigger:
- master

variables:
  buildConfiguration: 'Release'

jobs:
- job: WebApp1
  displayName: 'Build WebApp1'
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  - task: [email protected]
    displayName: 'Use .NET 3.1.x'
    inputs:
      packageType: 'sdk'
      version: '3.1.x'

  - task: [email protected]
    displayName: 'Build'
    inputs:
      command: 'build'
      projects: '**/WebApp1.csproj'
      arguments: '--configuration $(buildConfiguration)' 
  
  - task: [email protected]
    displayName: 'Publish Application'
    inputs:
      command: 'publish'
      publishWebProjects: false
      projects: '**/WebApp1.csproj'
      arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'

  - task: [email protected]
    displayName: 'Publish Artifacts'
    inputs:
      targetPath: '$(Build.ArtifactStagingDirectory)'
      artifact: 'WebApp1'
      publishLocation: 'pipeline'

Also notice that you can still define variables that can be used across jobs as is done above with the buildConfiguration variable. The following is the full YAML file that builds and publishes the artifacts for both web applications.

trigger:
- master

variables:
  buildConfiguration: 'Release'

jobs:
- job: WebApp1
  displayName: 'Build WebApp1'
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  - task: [email protected]
    displayName: 'Use .NET 3.1.x'
    inputs:
      packageType: 'sdk'
      version: '3.1.x'

  - task: [email protected]
    displayName: 'Build'
    inputs:
      command: 'build'
      projects: '**/WebApp1.csproj'
      arguments: '--configuration $(buildConfiguration)' 
  
  - task: [email protected]
    displayName: 'Publish Application'
    inputs:
      command: 'publish'
      publishWebProjects: false
      projects: '**/WebApp1.csproj'
      arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'

  - task: [email protected]
    displayName: 'Publish Artifacts'
    inputs:
      targetPath: '$(Build.ArtifactStagingDirectory)'
      artifact: 'WebApp1'
      publishLocation: 'pipeline'

- job: WebApp2
  displayName: 'Build WebApp2'
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  - task: [email protected]
    displayName: 'Use .NET 3.1.x'
    inputs:
      packageType: 'sdk'
      version: '3.1.x'

  - task: [email protected]
    displayName: 'Build'
    inputs:
      command: 'build'
      projects: '**/WebApp2.csproj'
      arguments: '--configuration $(buildConfiguration)' 
  
  - task: [email protected]
    displayName: 'Publish Application'
    inputs:
      command: 'publish'
      publishWebProjects: false
      projects: '**/WebApp2.csproj'
      arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'

  - task: [email protected]
    displayName: 'Publish Artifacts'
    inputs:
      targetPath: '$(Build.ArtifactStagingDirectory)'
      artifact: 'WebApp2'
      publishLocation: 'pipeline'

After all your edits are done commit the changes to your YAML file and then run the pipeline. As you can see from the following screenshot of my sample pipeline run the pipeline has two jobs instead of one that the original YAML resulted in. Also, note that the pipeline results in two published artifacts (one per job in our case) instead of the one with the original.

Wrapping Up

As mentioned above there are a lot of reasons you might want to split up your pipeline into multiple jobs and hopefully, you now have a good idea of how that is done. Make sure and check back in the future for a post on how to take repeated tasks and make them reusable.

Azure DevOps Publish Artifacts for ASP.NET Core

This post is going to build on the Azure DevOps project we created in the last few posts and get the build pipeline to the point you have the application’s binaries. If you are just joining this series check out the previous posts to catch up.

Getting Started with Azure DevOps
Pipeline Creation in Azure DevOps

Edit the Pipeline

First, we need to get back to the pipeline we were working on. From the Project menu select Pipelines.

This will land you on a page that lists your recently run pipelines. If you don’t see your pipeline list you might have to click the All option near the top of the page. Since we only have one pipeline in this project we can use the ellipsis to open a context menu and click Edit.

Publish the Application

At this point, the YAML for our pipeline looks like the following.

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

variables:
  buildConfiguration: 'Release'

steps:
- task: [email protected]
  inputs:
    packageType: 'sdk'
    version: '3.1.x'

- script: dotnet build --configuration $(buildConfiguration)
  displayName: 'dotnet build $(buildConfiguration)'

The pipeline will currently tell us if the included project builds, but doesn’t provide us with the results of that build. Using the Task panel on the right search for the .NET Core task and then click the resulting task. This is the task you would want to use to invoke any of the .NET CLI commands.

Use the drop-down for Command and select publish. For this sample, the defaults for the rest of the settings will be fine. Finally, click Add to add the task to the YAML file.

The following is the resulting YAML.

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

variables:
  buildConfiguration: 'Release'

steps:
- task: [email protected]
  inputs:
    packageType: 'sdk'
    version: '3.1.x'

- script: dotnet build --configuration $(buildConfiguration)
  displayName: 'dotnet build $(buildConfiguration)'
  
- task: [email protected]
  inputs:
    command: 'publish'
    publishWebProjects: true

Before we move on I want to point out the Settings link above the tasks in the YAML editor. Clicking Settings will load that task into the task panel on the right of the screen where you can make changes and then if you hit the add button it will replace your existing task with a new one with your new options selected. Be careful to not change the selection in the YAML editor as the add button is just replacing the selected text not remembering what task you click settings on. When finished click the Save button and go through the commit process. When that is finished click the Run button to execute the pipeline.

Publish Build Artifacts

The pipeline run should succeed, but we still don’t have any files we can use. Learning what variables are available in the pipeline and how to use them is one of the hardest parts of getting started with Azure Pipelines. For our example, we are trying to get the two zip files created by the publish step above which means our pipeline will need to publish artifacts to make the files available. We are going to tweak the publish command from above with an output directory using the builtin Build.ArtifactStagingDirectory variable. The following is the full task with the changes.

- task: [email protected]
  inputs:
    command: 'publish'
    publishWebProjects: true
    arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'

With the file we need in the artifact staging directory we need to publish those files using the Publish Pipeline Artifact task. The following is the full task that publishes the artifact staging directory to the pipeline.

- task: [email protected]
  inputs:
    targetPath: '$(Build.ArtifactStagingDirectory)'
    publishLocation: 'pipeline'

For reference, the following is the full YAML for the pipeline with all the above changes.

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

variables:
  buildConfiguration: 'Release'

steps:
- task: [email protected]
  inputs:
    packageType: 'sdk'
    version: '3.1.x'

- script: dotnet build --configuration $(buildConfiguration)
  displayName: 'dotnet build $(buildConfiguration)'
  
- task: [email protected]
  inputs:
    command: 'publish'
    publishWebProjects: true
    arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'

- task: [email protected]
  inputs:
    targetPath: '$(Build.ArtifactStagingDirectory)'
    publishLocation: 'pipeline'

Save and run the pipeline. When the pipeline is complete on the result page you will see 1 published for artifacts.

Click on 1 published and it will take you to a page that lists the artifacts. If you mouse over any of the rows you will see the option to download the associated file(s).

Quick Tip

As I stated above getting a handle on what directories are where can be a pain. If you ever need to see what files are where you can use the following publish task to output the full set of files the pipeline is using by publishing the pipeline’s entire workspace. This has helped me in the past to orient myself.

- task: [email protected]
  inputs:
    targetPath: '$(Pipeline.Workspace)'
    publishLocation: 'pipeline'

Wrapping Up

Our pipeline is now at the point we have files we could deploy. Hopefully, this gives you a good jump start on your own build pipelines. Azure Pipelines is a huge topic and this is a very basic build so keep an eye out for more posts on this topic in the future.

Pipeline Creation in Azure DevOps

This post is going to walk through creating a new build pipeline in Azure DevOps. This post is going to stick with a very simple example which we will build on in future posts. If you are new to this series of post check out the related posts.

Getting Started with Azure DevOps

This post will all happen from the Azure DevOps website so get logged in to your account select the project you will be working with before continuing. The project this sample is using is named Playground.

Pipeline Creation

From the project menu on the right of the site click the Pipelines option.

Since our sample project doesn’t have any pipelines setup we will see a landing page telling us to create a new pipeline. Once you have some pipelines this page is a lot more useful. Click the Create Pipeline button to continue.

The next step is to pick where our code is stored. For this sample, the code is in an Azure Repo Git repository, but as you can see Azure DevOps is pretty open about where your code is stored. As you will see from the screenshot there are a bunch of YAML based options and a very small option to use the classic editor. The classic editor is much easier to get started with, but the YAML options are getting the most attention from Microsoft and have the advantage of being stored in Git with your source so I recommend going with a YAML option even though there is more of a learning curve.

Next, select the repo this pipeline is for.

In the next step, Configure, you are given a list of templates to pick from which really helps when your new to yaml. Our sample applications are ASP.NET Core so click the Show More button and click ASP.NET Core. As you can see from the screenshot Azure DevOps can build just about anything and isn’t restricted to Microsoft based tech.

The result is the following YAML file. At this point, we aren’t doing to dive into the particulars of what the YAML is doing and go with the default. To continue to click the Save and run button.

Since the YAML is stored in the repo the save process is actually making a commit to a branch. Click the Save and run button on the commit dialog and it will save the YAML file to your branch and run the pipeline.

Pipeline Results

The following are the results from the pipeline run and it turns out to have failed. If you click the highlighted error it will take you to the detailed logs of the pipeline which will normally give you a good indication of why the pipeline failed.

The following screenshot is the result of clicking on the error. As you can see it provides a the output of the build command.

In this case line, 43 provides us with the reason the build failed. The following is the full line since the screenshot cuts it off. In this case, the issue is the agent running the build doesn’t have .NET Core 3.1 installed.

usr/share/dotnet/sdk/3.0.102/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.TargetFrameworkInference.targets(127,5): error NETSDK1045: The current .NET SDK does not support targeting .NET Core 3.1. Either target .NET Core 3.0 or lower, or use a version of the .NET SDK that supports .NET Core 3.1. [/home/vsts/work/1/s/src/WebApp1/WebApp1.csproj]

Fixing the Pipeline

Click the back button in your browser to return to the pipeline results page. Click the three dots in the top right of the results page and select Edit pipeline. This will open an edit with the YAML for the build open.

Using the Tasks helper on the right side of the screen we are going to select the Use .NET Core task which will allow us to install the version of .NET Core we need to build our applications.

Enter the version of .NET Core your application needs, 3.1 in this case and hit add.

The following is the resulting YAML. Note that the above helper isn’t required and you can hand-edit the YAML if you want.

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

variables:
  buildConfiguration: 'Release'

steps:
- task: [email protected]
  inputs:
    packageType: 'sdk'
    version: '3.1.x'

- script: dotnet build --configuration $(buildConfiguration)
  displayName: 'dotnet build $(buildConfiguration)'

Hit the Save button in the top right of the page, enter a commit message and click the Save button in the bottom right of the page. This will return you back to the edit screen for your YAML. Click the Run button on the top right of the page to start the pipeline.

This round (for the sample application) the pipeline will succeed.

Wrapping Up

Hopefully, this will give you a good jumping-off point to build your first Azure DevOps Pipeline. There is a lot a depth in Pipeline some of which I will explore in some future posts.

Getting Started with Azure DevOps

I have been doing a lot of work lately with our build and release pipelines in Azure DevOps and while it is fresh on my mind I’m going to do a few posts to remind me of some of the things I have leaned and may or may not have gotten to use. This post is going to serve as a jumping-off point for the post that will follow for readers who may not have used Azure DevOps in the past.

Before moving forward make sure and sign up for a free Azure DevOps account.

Create a new Project

When you first log in to Azure DevOps you should end up at something like the following that lists all your organizations on the left, ericlanderson being the sample organization I’m using. In the man section of the page make sure you are on the Projects tab and then click the New project button.

On the next screen, all that is required is a Project name. If you do want to allow public access to the project you will have to tweak an organization policy. Under Advanced you can also tweak how you want to manage work for the project, but that is out the scope of the point of this post. When done click the Create button.

Initialize a Repo

When project creation is complete you will be forwarded to the project landing page as you can see below. Now we want to initialize a new repo for this project. Click Repos button.

Since the sample projects are going to be .NET based I opted to use a .gitignore for Visual Studio. When the options you want are selected click the Initialize button.

When the initialization process is complete you will be taken to a files view of the repo.

Connect to project and clone the repo from Visual Studio

Now that we have the Azure DevOps setup it is time to switch over to Visual Studio and interact with our new project. In Visual Studio all the interactions with Azure DevOps will happen via the Team Explorer window. The first step is to connect to the project. In the Team Explorer window hit the plug icon.

Next, click Manage Connections and then click Connect to a Project.

In the dialog that shows you will need to use the drop-down and select Add an account if you already have an account connected as I do, if not then the process might be slightly different.

After completing the login process with your Azure DevOps credentials you should see your organization listed and under it your project. In this case, the project and repo have the same name. Select the repo and click Clone.

Create Sample Applications

Now that the repo is cloned I’m creating a couple of sample applications using the .NET CLI. Open a command prompt in the root folder of the clone from above and use the following command to create a new Visual Studio solution.

dotnet new sln -n Playground

Next, use the following command to create a new web application.

dotnet new webapp -o src/WebApp1

Then add the new project to the solution file.

dotnet sln add src/WebApp1/WebApp1.csproj

I then repeated the project creation process a second mostly to give us more to work with in future posts. Here are the commands to create the second project and add it to the existing solution.

dotnet new webapp -o src/WebApp2
dotnet sln add src/WebApp2/WebApp2.csproj

Commit Code and Push to Azure DevOps

Back in Visual Studio in the Team Explorer window, we need to switch to the Changes area. There are a couple of ways to get to the changes area. If you are in the home area you can click the changes button.

Another option is to click the current area and select the new area from the drop-down menu. For example, in the following screenshot, I clicked on Branches to show the drop-down and could then click Changes.

Enter a commit message and if you use the dropdown on the Commit All button and select Commit All and Sync it will push all of the changes to the associated Azure DevOps branch.

Wrapping Up

Hopefully if you are new to Azure DevOps this will give you a good jump-off point and will provide a base for some future posts.

Upgrade an ASP.NET Core Application to Bootstrap 4

As part of the work, I did on the application referenced in the web.config transform post from last week I also upgraded the application from Bootstrap 3 to Bootstrap 4 and this post is going to walk through a process to get the new version of Bootstrap up and running in a site.

Create a Sample Application

In case you want a project with Bootstrap 3 installed to play around with before attempting a production site the following command, run in a command prompt, will create an ASP.NET Core 2.1 application that has Bootstrap 3.

dotnet new webapp -f netcoreapp2.1

Upgrade Bootstrap Files

The first step I took was to delete the existing Bootstrap related file which can be found in the wwwroot/lib directory and delete the whole bootstrap directory.

Now open the project in Visual Studio and right-click on the project file and select Add > Client-Side Library.

The dialog that shows will allow you to search for client-side libraries to include in your application. In this case, we are looking for twitter-bootstrap. Your version number may be higher than the one shown in the screenshot below. I change the target location to match where Bootstrap was installed by the template by default, but that isn’t required. Finally, click Install.

The dialog above and the installation of the file is part of LibMan, which provides a lot of functionality for managing client-side libraries. Check out the official LibMan docs for more information.

Dealing with the Bootstrap Changes

This section is going to be really light on details as Bootstrap 4 was a reboot of Bootstrap and a ton of things changed. I will give an example or two here, but really the official Bootstrap Migrating to v4 guide is where you will want to go for all the details.

Here are a couple of examples of the type of changes you will be making.

Before:
<label asp-for="User" class="control-label"></label>

After:
<label asp-for="User" class="col-form-label"></label>

The look of buttons has changed so where we had used default before we changed to secondary.

Before:
<input type="submit" value="Load" class="btn btn-default" />

After:
<input type="submit" value="Load" class="btn btn-secondary" />

Wrapping Up

Hopefully, this will help you get a jump start on upgrading sites from Bootstrap 3 to Bootstrap 4. Thankfully after you get the files in the right spot Bootstrap’s migration guide should get you the rest of the way.

ASP.NET Core 3.1 Web.config Transform for Production

I recently upgraded an application from ASP.NET Core 2.2 to 3.1 around the same time the application got moved to a new server. Side note, don’t make two large changes like that at the same time if you can help it as it always makes tracking down the cause of the issue much harder.  The application in question was created using the ASP.NET Core React template initially.

The Error

After the changes above the site started returning the following error.

HTTP Error 500.0 – ANCM In-Process Handler Load Failue

To diagnose the issue I enabled standard out logging by changing the stdoutLogEnabled to true. The following is the line from the application’s web config with the logging enabled.

<aspNetCore processPath="dotnet" arguments=".\YourApplication.dll" stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout">

The Cause

The logging showed that the application was trying to start the React part of the application using the React Development Server, which uses npm. Here is the related code from the Configure function of the Startup class.

app.UseSpa(spa =>
           {
               spa.Options.SourcePath = "ClientApp";

               if (env.IsDevelopment())
               {
                   spa.UseReactDevelopmentServer(npmScript: "start");
               }
           });

As you can see from the highlighted area the Reac Development Server should only be used when the environment is set to development and sure enough, the web.config has environment variable for ASPNETCORE_ENVIRONMENT and the value is Development as you can see in the following example.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" arguments="YourApplication.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="InProcess">
        <environmentVariables>
          <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" />
        </environmentVariables>
      </aspNetCore>
    </system.webServer>
  </location>
</configuration>

A Fix

My knee jerk reaction was to find a way to get the value of that variable changed when the application is published in release mode. This helped me find the Microsoft docs for Transform web.config.

For this fix, I used a build configuration base transform. To do this I added a new file named web.Release.config. With this new file present, the transforms in the file will be executed when a release build is run. The following is the transform I used to get ASPNETCORE_ENVIRONMENT set to Production.

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <location>
    <system.webServer>
      <aspNetCore>
        <environmentVariables>
          <environmentVariable xdt:Transform="Replace" xdt:Locator="Match(name)" name="ASPNETCORE_ENVIRONMENT" value="Production" />
        </environmentVariables>
      </aspNetCore>
    </system.webServer>
  </location>
</configuration>

It is important that the structure of your transforms match what is in your actual web.config or the transform won’t be able to locate the element that needs to be transformed which in this case we are looking for an element under configuration/location/system.webServer/aspNetCore/enviromentVariables.

In our example, we are telling the transform we want to replace the element  (xdt:Transform=”Replace”) that matches the name (xdt:Locator=”Match(name)”) ASPNETCORE_ENVIRONMENT with the value Production.

Wrapping Up

This is just one small example of what can be done with web.config transforms. The official docs gave me a general high-level idea of what transforms can do, but wasn’t super helpful for what I needed to do. If the docs don’t cover your use case get ready to do a fair amount of searching.

Side note in the application I was referencing above it turns out that the web.config in the published for .NET Core 2.1 didn’t contain a value for ASPNETCORE_ENVIRONMENT which was why it wasn’t an issue before.

Using NSwag to Generate an Aurelia Client for an ASP.NET Core 3.1 API

This week we are going to add an Aurelia project that will utilize the contacts API we created a few weeks ago using a client-generated by NSwag. This post is part of the revamp of the ASP.NET Core Basics repo that was kicked off when .NET Core 3.0 was released which is now targeting .NET Core 3.1. For details on how the associated samples got to their current point check out the following posts.

Swagger/OpenAPI with NSwag and ASP.NET Core 3
ASP.NET Core 3: Add Entity Framework Core to Existing Project
New Razor Pages Project Backed with an API
Using NSwag to Generate Angular Client for an ASP.NET Core 3 API
Using NSwag to Generate React Client for an ASP.NET Core 3 API
Using NSwag to Generate Blazor Server Client for an ASP.NET Core 3.1 API
Using NSwag to Generate a Vue Client for an ASP.NET Core 3.1 API

The sample code before any changes from this post can be found here.


 

Create the Aurelia Project

As with Vue there is no .NET CLI template from Microsoft that has Aurelia support so to crate the Aurelia project we will be using the Aurelia CLI. Before getting started ensure you have npm installed.

Install the Aurelia CLI using the following command from a command prompt.

npm install -g aurelia-cli

Next, use the following command to start the project creation process using the Aurelia CLI. Keep in mind that the CLI creates a directory with the project name.

au new

The above will result in a walkthrough of the project creation process. First is the name of the project, contacts-aurelia in this case. Next is the setup of the project and here we will be using the Default TypeScript App.

Finally, select how you would like to manage dependencies. The sample project is using npm, but Yarn is also an option. If you do go with Yarn some of the following steps will need the npm commands translated to Yarn commands.

After the project creation process is complete use the following command to change to the new directory created for the project.

cd contacts-aurelia

Now the project needs a few more dependencies installed. We are going to install a couple of UI related items, Bootstrap and Font Awesome, as well as the Aurelia Fetch Client which we will need to hit our API.

npm install bootstrap
npm install font-awesome
npm install aurelia-fetch-client

The application that the Aurelia CLI outputs is very basics so the Aurelia docs for creating a to-do application and creating a contact manager were used to build the basics of the sample application. I will be coving the contact related bits of the UI, but the application stops short of implementing the save functionality at this point.

Use NSwagStudio to Generate an API Client

NSwag provides multiple options for client generation including a CLI, code, or a Windows application. This post is going to use the Windows application which is called NSwagStudio. NSwagStudio can be downloaded and installed from here.

Next, make sure your API is running and get the URL of its OpenAPI/Swagger specification URL. For example, using a local instance of the sample solution’s Contacts API the URL is https://localhost:5001/swagger/v1/swagger.json. If you are using the Swagger UI you can find a link to your swagger.json under the API title.

Now that we have the OpenAPI/Swager specification URL for the API switch over to NSwagStudio. The application will open with a new document ready to go. There are a few options we will need to set. First, select the OpenAPI/Swagger Specification tab and enter your API’s specification URL in the Specification URL box.

In the Outputs section check the TypeScript Client checkbox and then select the TypeScript Client tab. There are a lot of options to play with, the highlighted options are the ones that are important for this sample. For Template, we just need an Aurelia based client. The final option that needs to be set is the Output file path and this is the location you want the generated file to be. I output to the Aurelia project directory under /src/contactApi.ts. After all the options are set click Generate Files.

Create UI and Use Generated Client

Again the UI bit mostly comes from the docs, but I’m going to show the bits for the contact list here and the rest of the UI you can look at the sample code. All of the following will be taking place in the src directory of the Aurelia project.

First, add a file named contact-list.html which will hold the template for the UI of the contact list with the following contents. This is a mix of HTML and Aurelia’s syntax. We aren’t really going into the Aurelia specific bits, but even if you are new to Aurelia this should be readable.

<template>
  <div class="contact-list">
    <ul class="list-group">
      <li repeat.for="contact of contacts" class="list-group-item ${contact.id === $parent.selectedId ? 'active' : ''}">
        <a route-href="route: contacts; params.bind: {id:contact.id}" click.delegate="$parent.select(contact)">
          <h4>${contact.firstName} ${contact.lastName}</h4>
          <p>${contact.email}</p>
        </a>
      </li>
    </ul>
  </div>
</template>

Next, add a contact-list.ts file which is what the template from above will be bound to. The lines specific to the usage of the NSwag generated client are highlighted.

import {ContactsClient, Contact} from './contactsApi';
import {inject} from 'aurelia-framework';

@inject(ContactsClient)
export class ContactList {
  contacts: Contact[];
  api: ContactsClient;
  selectedId: any;
  
  constructor(api: ContactsClient) {
    this.api = api;
    this.contacts = [];
  }

  created() {
    this.api.getContacts().then(contacts => this.contacts = contacts);
  }

  select(contact) {
    this.selectedId = contact.id;
    return true;
  }
}

As you can see from the above Aurelia is injecting an instance of the ContactsClient via the class’s construction and then that client is used in the created function to call the API client’s getContacts function and using the resulting data from the API to replace the contacts field with the results of the API call.

The application is displaying the contact list in app.html via the contact-list element. The import and usage of the contact list component are highlighted in the following chunk of code.

<template>
  <require from="./styles.css"></require>
  <require from="./contact-list"></require>

  <nav class="navbar navbar-light bg-light fixed-top" role="navigation">
    <a class="navbar-brand" href="#">
      <i class="fa fa-user"></i>
      <span>Contacts</span>
    </a>
  </nav>

  <div class="container">
    <div class="row">
      <contact-list class="col-md-4"></contact-list>
      <router-view class="col-md-8"></router-view>
    </div>
  </div>
</template>

At this point, I tried out the application and it wasn’t pulling back any data. After doing some digging in the network tab of my browser’s dev tool I noticed that the API call was missing the base part of the URL. This hasn’t come up before for the other times I have used the NSwag generated client and if you look at the constructor of the client it defaults the base URL to the endpoint that was used to generate the client, see the following code.

constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
    this.http = http ? http : <any>window;
    this.baseUrl = baseUrl ? baseUrl : "https://localhost:5001";
}

It turns out that the Aurelia dependency injection system calls the constructor with an empty string instead of null. One option would have been to change the above constructor to handle an empty string, but that would mean any time the client got regenerated I would have to remember to follow it up with the constructor modification which would be too easy to screw up. After some digging, I found out that Aurelia provides a way to control how an instance of a class is created. Open main.ts and make the following highlighted changes. I’m injecting the URL, but using null instead would also work and the base URL from the ContactClient would get used.

import { HttpClient } from 'aurelia-fetch-client';
import {Aurelia} from 'aurelia-framework'
import * as environment from '../config/environment.json';
import {PLATFORM} from 'aurelia-pal';
import 'bootstrap/dist/css/bootstrap.css';
import 'font-awesome/css/font-awesome.css';
import { ContactsClient } from 'contactsApi';

export function configure(aurelia: Aurelia) {
  aurelia.use
    .standardConfiguration()
    .feature(PLATFORM.moduleName('resources/index'))
    .instance(ContactsClient, 
              new ContactsClient("https://localhost:5001",
                                 aurelia.container.get(HttpClient)));

  aurelia.use.developmentLogging(environment.debug ? 'debug' : 'warn');

  if (environment.testing) {
    aurelia.use.plugin(PLATFORM.moduleName('aurelia-testing'));
  }

  aurelia.start().then(() => aurelia.setRoot(PLATFORM.moduleName('app')));
}

After all the change from a command prompt set to the root of the Aurelia project, you can use the following command to run the application. If you drop the open it will run the application without opening a browser.

au run --open

Wrapping Up

As always NSwag makes it very easy to create a client to interact with an API. Hopefully, this was useful even if my Aurelia code might not be idiomatic.

The sample projects after all the changes in this post can be found here.

Using NSwag to Generate a Vue Client for an ASP.NET Core 3.1 API

This week we are going to add a Vue project that will utilize the contacts API we created a few weeks ago using a client-generated by NSwag. This post is part of the revamp of the ASP.NET Core Basics repo that was kicked off when .NET Core 3.0 was released which is now targeting .NET Core 3.1. For details on how the associated samples got to their current point check out the following posts.

Swagger/OpenAPI with NSwag and ASP.NET Core 3
ASP.NET Core 3: Add Entity Framework Core to Existing Project
New Razor Pages Project Backed with an API
Using NSwag to Generate Angular Client for an ASP.NET Core 3 API
Using NSwag to Generate React Client for an ASP.NET Core 3 API
Using NSwag to Generate Blazor Server Client for an ASP.NET Core 3.1 API
Using NSwag to Generate a Vue Client for an ASP.NET Core 3.1 API

The sample code before any changes from this post can be found here.

Create the Vue Project

Unlike the rest of the projects in this series, there is no .NET CLI template from Microsoft that has Vue support so to crate the Vue project we will be using the Vue CLI. Before getting started ensure you have npm installed.

Install the Vue CLI using the following command from a command prompt.

npm install -g @vue/cli

Next, use the following command to start the project creation process using the Vue CLI. Keep in mind that the CLI creates a directory with the project name.

vue create contacts-vue

The above command kicks off a series of questions about the application. This sample is going to use TypeScript which means that the default can’t be used so we need to select Manually select features.

For the next question, we need to select TypeScript. I also included the Router and Linter / Formatter. I also found out later in the process that Babel was needed so feel free to select it on this question.

The project creation process asked a bunch more questions that I basically took the defaults on. Here is a screenshot of all the questions and the options I used.

Now that the project is created if we need to change directories to the one created in the above process.

cd contacts-vue

Use the following command to run the new project.

npm run serve

Use NSwagStudio to Generate an API Client

NSwag provides multiple options for client generation including a CLI, code, or a Windows application. This post is going to use the Windows application which is called NSwagStudio. NSwagStudio can be downloaded and installed from here.

Next, make sure your API is running and get the URL of its OpenAPI/Swagger specification URL. For example, using a local instance of the sample solution’s Contacts API the URL is https://localhost:5001/swagger/v1/swagger.json. If you are using the Swagger UI you can find a link to your swagger.json under the API title.

Now that we have the OpenAPI/Swager specification URL for the API switch over to NSwagStudio. The application will open with a new document ready to go. There are a few options we will need to set. First, select the OpenAPI/Swagger Specification tab and enter your API’s specification URL in the Specification URL box.

In the Outputs section check the TypeScript Client checkbox and then select the TypeScript Client tab. There are a lot of options to play with, the highlighted options are the ones that are important for this sample. First, make sure Module name and Namespace are both empty. I’m sure there is a way to get the client working with a module or namespace, but I didn’t have any luck.   For Template, we just need a Fetch based client. The final option that needs to be set is the Output file path and this is the location you want the generated file to be. I output to the Vue project directory under /src/apis/contactApi.ts. After all the options are set click Generate Files.

Create UI and Use Generated Client

Note that I haven’t touch Vue in a long time so the actually UI bits may or may not be the “proper” way to do this stuff in Vue, but it should be understandable enough that you can see how the API client is used. As with the other post in this same vein, we are going to create a contact list that gets its data from an API.

First, we are going to create a new component for the contact list in the /src/component directory with the filename of ContactList.vue with the following contents. The lines specific to the usage of the NSwag generated client are highlighted.

<template>
  <div>
    <table class="table table-striped" aria-labelledby="tabelLabel">
      <thead>
        <tr>
          <th>Name</th>
          <th>Address</th>
          <th>City</th>
          <th>State</th>
          <th>Postal Code</th>
          <th>Phone</th>
          <th>Email</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="contact in contacts" v-bind:key="contact.id">
          <td>{{contact.name}}</td>
          <td>{{contact.address}}</td>
          <td>{{contact.city}}</td>
          <td>{{contact.state}}</td>
          <td>{{contact.postalCode}}</td>
          <td>{{contact.phone}}</td>
          <td>{{contact.email}}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
import { ContactsClient, Contact } from '../apis/contactsApi'
@Component
export default class HelloWorld extends Vue {
  name: string = 'ContactList';
  contacts: Contact[] = [];
  private created () {
    let client = new ContactsClient()
    client.getContacts().then(data => (this.contacts = data))
  }
}
</script>

<style scoped>
</style>

As you can see from the created function above we are creating a new instance of the ContactsClient and calling its getContacts function and using the data we get back from the API to replace the contacts field with the results of the API call.

Next, we are going to create a ContactList.vue under the /src/views directory with the following code. This is basically a wrapper around the component we created above.

<template>
  <div>
    <ContactListComponent/>
  </div>
</template>

<script>
import ContactListComponent from '@/components/ContacList'
export default {
  name: 'contactList',
  components: {
    ContactListComponent
  }
}
</script>

Now that we have our view ready we need to add a link to the application’s navigation so a user can get to the contact list. Open App.vue and a router link to the nav div for the contact list.

<div id="nav">
    <router-link to="/">Home</router-link> |
    <router-link to="/contactList">Contacts</router-link> |
    <router-link to="/about">About</router-link>
</div>

Now we need to add the new component to the routes for the application. Open index.ts in the /src/router directory and add an import for the contact list view.

import ContactList from '../views/ContactList.vue'

Finally, add the contact list to the routes array.

const routes = [
  {
    path: '/',
    name: 'home',
    component: Home
  },
  {
    path: '/contactList',
    name: 'contactList',
    component: ContactList
  },
  {
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

Wrapping Up

As always NSwag makes it very easy to create a client to interact with an API. Hopefully, this was useful even if my Vue code might not be idiomatic.

The sample projects after all the changes in this post can be found here.

Using NSwag to Generate Blazor Server Client for an ASP.NET Core 3.1 API

This week we are going to add a Blazor Server project that will utilize the contacts API we created a few weeks ago. This post is part of the revamp of the ASP.NET Core Basics repo that was kicked off when .NET Core 3.0 was released. For details on how the associated sample got to the current point in the application check out the following posts.

Swagger/OpenAPI with NSwag and ASP.NET Core 3
ASP.NET Core 3: Add Entity Framework Core to Existing Project
New Razor Pages Project Backed with an API
Using NSwag to Generate Angular Client for an ASP.NET Core 3 API
Using NSwag to Generate React Client for an ASP.NET Core 3 API
Using NSwag to Generate Blazor Server Client for an ASP.NET Core 3.1 API
Using NSwag to Generate a Vue Client for an ASP.NET Core 3.1 API

The sample code before any of the changes in this post can be found here.

Create the Blazor Server Project

Add a new directory for the Blazor Server project and then open a terminal set to that directory. The following command can be used to create a new Blazor Server project.

dotnet new blazorserver

Next, use the following command to add the new project to the solution file which is in the root of the repo. Your filenames and paths will vary of course.

dotnet sln ..\..\BasicsRefresh.sln add ContactsBlazorServerApp.csproj

Using NSwageStudio to Generate an API Client

NSwag provides multiple options for client generation including a CLI option, code, and a Windows application. This post is going to use the Windows application which is called NSwagStudio. Download and install NSwagStudio from here.

Next, make sure your API is running and get the URL of its OpenAPI/Swagger specification URL. For example, I am using a local instance of my API and the URL I need is https://localhost:5001/swagger/v1/swagger.json. If you are using the Swagger UI you can find a link to your swagger.json under the API title.

Now that we have the OpenAPI/Swager specification URL for the API switch over to NSwagStudio. The application will open with a new document ready to go. There are a few options we will need to set. First, we want to use the NetCore30 Runtime. Next, select the OpenAPI/Swagger Specification tab and enter your API’s specification URL in the Specification URL box.

In the Outputs section check the CSharp Client checkbox and then select the CSharp Client tab. For this example, we are taking the defaults for all of the options except for Namespace, which is set to ContactsApi, Generate interfaces for Client classes, which should be check, and Output file path, which is only needed if you use the Generate Files option. Click the Generate Files button and NSwagStudio will create a file that contains all the code needed to access the API described in the OpenAPI/Swager specification selected in the Input section.

The Generate Outputs button can be used if to populate the Output tab with the same code that the Generate Files process creates which provides a nice way to play with settings to and see the output without having to open another file.

Setting Up the Generated Client in the Blazor Server Project

In the sample project, create an APIs directory and dropped the ContactsApi.cs created with NSwagStudio there. The files generated with NSwagStudio are expecting JSON.NET to be present so the sample project will need a reference to the Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet package.

With the client-generated and in our local Apis directory in the Razor Pages project we can now work on getting it configured and registered for use in our new project. First, open the apppsetting.json file and add a setting for the URL of our API, which is the ContactsApi value in the following sample.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ContactsApi": "https://localhost:5001"
}

Now that the project has the configuration change and a reference to JSON.NET in the ConfigureServices function of the Startup class we need to tell the app to make JSON.NET available via dependency injection by using AddNewtonsoftJson as in the following example.

services.AddRazorPages()
        .AddNewtonsoftJson();

Also in the ConfigureServices function, we need to register our API client.

services.AddHttpClient<IContactsClient, 
                       ContactsClient>(client => 
         client.BaseAddress = new Uri(Configuration.GetSection("ContactsApi").Value));

Create the UI and Usage of the Generated Client

Now that all the setup work is done we can add the contact list UI which will show the usage of the API client. The following is the full code which for the sample is in a new ContactList.razor file in the Pages directory. The specific lines related to the API client are highlighted.

@page "/contactlist"

@using Apis
@inject IContactsClient ContactClient

<h1>Contact List</h1>

@if (_contacts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table className='table table-striped' aria-labelledby="tabelLabel">
        <thead>
            <tr>
                <th>Name</th>
                <th>Address</th>
                <th>City</th>
                <th>State</th>
                <th>Postal Code</th>
                <th>Phone</th>
                <th>Email</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var contact in _contacts)
            {
                <tr>
                    <td>@contact.Name</td>
                    <td>@contact.Address</td>
                    <td>@contact.City</td>
                    <td>@contact.State</td>
                    <td>@contact.PostalCode</td>
                    <td>@contact.Phone</td>
                    <td>@contact.Email</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private ICollection<Contact> _contacts;

    protected override async Task OnInitializedAsync()
    {
        _contacts = await ContactClient.GetContactsAsync();
    }
}

Finally to add our new page to the navbar open the NavMenu.razor file found in the Shared directory. Add the following list item to the unordered list.

<li class="nav-item px-3">
    <NavLink class="nav-link" href="contactlist">
        <span class="oi oi-list" aria-hidden="true"></span> Contacts
    </NavLink>
</li>

Wrapping Up

As with the other posts I have been doing utilizing NSwag for client generation this process is pretty easy and simplifies API consumption.

The sample code in its final state can be found here.