| Comments

I’ve been spending a lot of time looking at the GitHub Actions experience for .NET developers.  Right now I’m still using Azure Pipelines for my project, Alexa.NET, in building, testing, and deploying to NuGet.  As the tools and process for using DevOps tools for CI/CD have so vastly improved over the years, I’ve become a huge advocate for this being the means for your build/deploy steps…YES, even as a single developer or a smaller team.  It simply really helps you get to a purer sense of preserving the ability for your code to live on, for others to accurately build it, and for you to have peace of mind that your code works as intended.  I’m just such a huge fan now.  That said, I still think there is a place for ‘right-click publish’ activities in inner-loop development.  In fact, I use it regularly for a few internal apps I’ve written.  For simple solutions that method works well, but I certainly don’t think I can right-click-publish a full solution to a Kubernetes environment though.  I’m currently researching new tooling ways to help those ‘publish to CI/CD’ from Visual Studio (would love your opinions here) so I’ve been spending a lot more time in GitHub Actions.  I decided to look at publishing a Blazor app to Azure Storage as a static site…here’s what I did.

Setting up the Storage endpoint

The first thing you need is an Azure Storage account.  Don’t have an Azure account, no worries you can get a Free Azure account easily which includes up to 5GB of Azure Blob Storage free for the first 12 months.  Worried about pricing afterwards?  Well check out the storage pricing calculator and I’m sure you’ll see that even at 1TB storage it is cost-effective means of storage.  But any rate, you need a storage account and here are the configuration you need.

First, you may already have one.  As a developer do you create your infrastructure resources or are these provisioned for you by infra/devops roles in your company (leave comments)?  Earlier this year at Build we enabled static website hosting in Azure Storage.  You first create a Storage resource (ensuring you choose v2 which is the default, but that is the version that enables this feature).  After you create your resource scroll on the left and you’ll see ‘Static website' section.  Here’s what the configuration looks like and let me explain a few areas here:

Screenshot of the Static website configuration
All of this configuration is under the Static website area.  First you obviously need to enable it…that’s just toggling the enabled/disabled capability.  Enabling this now gets you two things: 2 endpoints (basically the URI to the website) and a specific blob contianer named $web where your static content needs to live.  The endpoints default map to this blob container without having to add a container name to the root URI.  Remember the resource group you’ve given to your storage instance here, you will need that later.

NOTE: You can later add CDN/custom domain to these endpoints, but I’m not covering those here.

The second thing you need is to set a default document and error page.  The default document for your SPA is your root entry point, and for most frameworks I’ve seen this is indeed index.html.  For Blazor WebAssembly (WASM) apps, this is also the default if you are using the template.  So you set the default document as ‘index.html’ and move on.  The error document path is another interesting one…you need to set this for SPA apps because right now the static website capability of Azure Storage does not account for custom routing rules.  What this means is that storage will throw an HTTP 404 error message when you go to something like /somepage where it actually doesn’t exist but your SPA framework knows how to handle it.  Until custom routing works on Azure Storage your error document becomes your route entry point.  So for this set the error document path to also be index.html for Blazor WASM.

NOTE: Yes this isn’t ideal for routing.  On top of that it still does show an HTTP 404 actual network message even though your route is being handled.  Azure Storage team has heard this request…working on advocating for y’all.

That’s it.  Now you have a storage endpoint with a blob container that you can begin putting your content in and browse to using your endpoint URI provided from the portal.  For a simple tool to navigate your storage, I’ve been using Azure Storage Explorer and it is intuitive to me and works well to quickly navigate your storage account and containers (and supports multi-account!). 

Setting up your Azure Service Principal credentials

The next thing you will need is a service principal credential.  This will be used to authenticate with your Azure account to be able to use DevOps tools to work on your behalf in your account.  It’s a simple process if you have a standard account.  I say this only because I know there might be some configurations for environments where you yourself don’t have access to create service principals and may need someone to create one on your behalf, or also there might be credentials you can already use.  Either way here is the process I used.

I used the Azure CLI so if you don’t have that installed go ahead and grab that and install it.  This should now be in your PATH environment and using your favorite terminal you should be able to start executing commands.  To start out, login to the CLI using `az login` – this will launch a browser for you to authenticate via your account and then issue the token in your environment so that for the remainder of your session you’ll be authenticated.  After logging in successfully running `az account show` will emit what subscription you are using and you’ll need the subscription ID later so grab that and put it somewhere on your scratch notepad for later command usage.

NOTE: If you have more than one subscription and have not set a default subscription you should set that using the `az account set` command.

Now you can use the CLI to create a new service principal.  To do that issue this command:

az ad sp create-for-rbac --name "myApp" --role contributor \
                            --scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group} \
                            --sdk-auth

Note on line 2 here that you need to replace {subscription-id} with your own actual subscription id (the GUID) and {resource-group} with the resource group name where your storage account is located.  On line 1 the “myApp” can be anything but I recommend making it meaningful as this is basically the account name of the principal.  The output of this command is your service principal.  The full JSON output.  Save this off in a place for now as we’ll need that later to configure GitHub Actions properly.  Great now to move on to the app!

Create your Blazor WASM app

I assume since you may be reading this far you aren’t new to Blazor and probably already have the tools.  As of this writing, Blazor WASM is still in preview so you have to install the templates separately to acquire them to show up in `dotnet new` and in Visual Studio File…New Project.  To do that from a terminal run:

dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.1.0-preview4.19579.2

Then you will be able to create a new project.  I’m showing Visual Studio here and this is the WASM template:

Screenshot of Blazor new project dialog

In the dialog here you will see Blazor WebAssembly App and that’s what you will use.  Now you have a choice to have it ASP.NET Core hosted using that checkbox, which if you were going to do other things in ASP.NET maybe that’s what you want.  For the purposes of this article we are talking about just having the WASM app and having a place to host it that isn’t a web server with other content…just hosting static content and using storage to do so…so we aren’t checking that box.  The result will be a Blazor WASM app with no host.  Now let’s add that to GitHub.  If you are using Visual Studio 16.4+ you’ll be able to take advantage of an improved flow for pushing to GitHub.  Once you have your project, in the lower right click ‘Add to Source Control’ choosing Git and then you’ll see the panel to choose GitHub and create/push a repo right away.  You don’t have to go to GitHub site first and clone later…all in one step:

Animation of Visual Studio GitHub flow

Great!  Now we have our WASM project and we’ve created and pushed the current bits to a new GitHub repository.  Now to create the workflow.

Setup the GitHub Action Workflow

Now we’ve got an Azure Storage blob container, a service principal, a Blazor WASM project, and a GitHub repository…all set to configure the CI/CD flow now.  First let’s put that service principal as a secret in our repository.  In the settings of your repository navigate to the Secrets section and add a secret named AZURE_CREDENTIALS.  The content of this is the full content of your service principal (the JSON blob) that we generated earlier:

Screenshot of GitHub Secrets configuration

This saves the secret for us to use in the workflow and reference as a variable.  You can add more secrets here if you’d like if you wanted to add your resource storage account name as well (probably a good idea).  Secrets are isolated to the original repository so no forks get the secrets at all.  Now that we have these let’s create the workflow file.

Today, Visual Studio isn’t too helpful in authoring the YAML files (would love your feedback here too!) but a GitHub Action is just a YAML file in a specific location in your repository: .github/workflows/azure-storage-deploy.yaml.  The file name can be anything but putting it in this folder structure is what is required.  You can start in the GitHub repo itself using the Actions tab and through the online editor get some level of completion assistance to help you navigate the YAML editing.  Go to the Actions tab in your repository and create a new workflow.  You’ll be offered a starter workflow based on what GitHub thinks your project is like.  As of this writing it thinks Blazor apps are Jekyll workflows so you’ll need to expand and either find the .NET Core one or just start from a blank workflow yourself.

Screenshot of GitHub Actions config

Four my workflow I want to build, publish and deploy my app.  I’ve separated it into a build and deploy jobs.  You can read all about the various aspects of GitHub Actions in their docs with regard to jobs and other syntax as I won’t try to expound upon that in this article.  Here is my full YAML for the entire workflow with some key areas highlighted:

name: .NET Core Build and Deploy

on: [push]

env:
  AZURE_RESOURCE_GROUP: blazor-deployment-samples
  BLOB_STORAGE_ACCOUNT_NAME: timheuerblazorwasm

jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    steps:
    - uses: actions/[email protected]
    - name: Setup .NET Core
      uses: actions/[email protected]
      with:
        dotnet-version: 3.1.100

    - name: Build with dotnet
      run: dotnet build --configuration Release
    
    - name: Publish with dotnet
      run: dotnet publish --configuration Release 
    
    - name: Publish artifacts
      uses: actions/[email protected]
      with:
        name: webapp
        path: bin/Release/netstandard2.1/publish/BlazorApp27/dist

  deploy:
    needs: build
    name: Deploy
    runs-on: ubuntu-latest
    steps:

    # Download artifacts
    - name: Download artifacts
      uses: actions/[email protected]
      with:
        name: webapp

    # Authentication
    - name: Authenticate with Azure
      uses: azure/[email protected]
      with:
        creds: ${{ secrets.AZURE_CREDENTIALS  }}

    # Deploy to storage using CLI
    - name: Deploy to storage using CLI
      uses: azure/[email protected]
      with:
        azcliversion: latest
        inlineScript: | 
          # show azure account being used
          az account show
          # az storage account upload
          az storage blob upload-batch --account-name ${{ env.BLOB_STORAGE_ACCOUNT_NAME }} -s webapp -d \$web
          # az set content type of wasm file until this is fixed by default from Azure Storage
          az storage blob update --account-name ${{ env.BLOB_STORAGE_ACCOUNT_NAME }} -c \$web -n _framework/wasm/mono.wasm --content-type application/wasm

So a few things going on here, let’s talk about them.

  • Lines 5-7: these are ‘local’ environment variables I set up.  The storage account name is NOT the blob container name but the actual storage account name.  This ideally probably should be a Secret as mentioned above.  Environment variables can be set here and then placeholders reference them later.
  • Starting at line 9 is where the ‘build’ portion is here.  We checkout the code, acquire the SDK and run the build and publish commands.  On line 26-30 is a step where we put the publish output to a specific artifact location for later retrieval of steps.  This is good practice.
  • Lines 40-42 is where we are now in the ‘deploy’ step of our CD and we retrieve those artifacts we previously pushed and we set them as a name ‘webapp’ that the later will use in deployment
  • Line 45 is where we are going to first authenticate to Azure using our service principal retrieved from the Secrets.  The ‘secrets’ object is not something you have to define and is part of the workflow so you just add the property you want to retrieve
  • Line 51 is where we start the deployment to Azure using the CLI commands and our param ‘webapp’ as the source.  This is the CLI command for uploading batch to storage as described in the docs for `az storage blob upload-batch`
  • Line 61 is an additional step that we need for .wasm files.  I believe this to be a bug because there is logic in the CLI to correctly map the content-type but for some reason it is not working…so for now you need to set the content-type for .wasm to `application/wasm` or the Blazor app will not work

This is made possible through a series of actions: checkout, dotnetcore, azure…all brining their functionality we can draw on and configure.  There are a bunch of Azure GitHub Actions we just released for specific tasks like deploying to App Service and such.  These don’t require CLI commands but rather just provide parameters to configure.  Because there is no Storage specific Action as of now, we can use the default CLI action to script what we want.  It is an enabler in lieu of a more strongly-typed action.  Now that we have this workflow YAML file complete we can commit and push to the repository.  In doing that we now have a CI/CD action that will trigger on any push (because that’s how we configured it).  We can see this action happening in my sample repo and you can see since we separate it in two jobs it will show them separately:

Screenshot of action deployment log

Summary

So now we have it complete end-to-end.  And subsequent check-in will trigger the workflow and deploy the bits to my storage account and I can now use my Azure Storage account as a host for my static website built on Blazor WASM.  This full YAML sample flow is available on my repo for this and you can examine it in more detail.

I would love to know how y’all are coming along using GitHub Actions with your .NET projects.  Please comment below! 

| Comments

I’ve been doing a lot of playing around with GitHub Actions lately.  GitHub has had access to repo activity via webhook capabilities for a while.  Actions basically gives similar capabilities in a DevOps flow on the repo itself, where the code for your ‘hook’ is an asset in the repo…using YAML configuration.  Recently an idea came up in one of our teams to provide better pro-active notification of certain types of Issues on our repos.  In GitHub, you can monitor activity in a few ways as a consumer: watching the repo and subscribing to a conversation.  In watching a repo you get a lot of noise of lots of notifications.  In subscribing to an Issue you can only do so after the issue is created and not notification when it is initially created.  What I wanted was simple: Notify me when an Issue is added to a repo that has been labeled as a breaking change.  So with that goal I set off to create this.

Creating the Action

Creating the action was simple.  I followed the great javascript-action template.  I recommend following the instructions in the template rather than the actual documentation as it is simpler to follow and more concise.  The cool thing about the template is you can click ‘Use this template’ and get a new repo for your action quickly:

actionstemplate1

I was able to configure my action quickly.  My goal was to accomplish the following things:

  • Look at an Issue
  • If the issue had a specific (or multiple) labels grab the content of the issue
  • Convert the contents from markdown to HTML and send an email to a set of folks

Actions are JavaScript apps and I was able to use two libraries to help me achieve this quickly: remarkable (to convert Markdown) and SendGrid (to email).  Aside from those you are able to use GitHub core SDKs to get access to the ‘context’ of what that Action is…well, acting upon.  In having this context, I can examine the payload and the specific Issue within that payload.  It looks something like this (relevant lines highlighted):

var core = require('@actions/core');
var github = require('@actions/github');
var sendgrid = require('@sendgrid/mail');
var moment = require('moment');
var Remarkable = require('remarkable').Remarkable;
var shouldNotify = false;

// most @actions toolkit packages have async methods
async function run() {
  try { 
    // set SendGrid API Key
    sendgrid.setApiKey(process.env.SENDGRID_API_KEY);

    // get all the input variables
    var fromEmail = core.getInput('fromMailAddress');
    var toEmail = core.getInput('toMailAddress');
    var subject = core.getInput('subject');
    var verbose = core.getInput('verbose');
    var labelsToMonitor = core.getInput('labelsToMonitor').split(",");
    var subjectPrefix = core.getInput('subjectPrefix');

    // check to make sure we match any of the labels first
    var context = github.context;
    var issue = context.payload.issue;

This context gives me all I need to inspect the Issue contents, labels, etc.  From that then I can decide that I need to perform the notification, convert, and send the email.  Simple and done.

Consuming the Action

Since the Action is now defined, something needs to consume it.  This is in the form of a GitHub Workflow.  This is a YAML file that decides when to operate and what to do.  Specifically you define a Trigger.  These can be things like when a push happens, a PR is issued, or, in my case, when an Issue happens.  So now on my repo I can consume the action and decide when it should operate.  As an example here is how I’m consuming it by putting a yaml file in .github/workflows folder in my repo.

name: "bc-notification"
on: 
  issues:
    types: [edited, labeled]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/[email protected]
    - uses: timheuer/[email protected]
      env:
        SENDGRID_API_KEY: ${{ secrets.SENDGRID_API }}
      with:
        fromMailAddress: '${{ secrets.BC_NOTIFY }}'
        toMailAddress: '${{ secrets.BC_NOTIFY }}'
        subject: 'BC:'
        subjectPrefix: 'BC:'
        labelsToMonitor: "Breaking change"

Looking at this workflow you can see in the highlighted areas that I’m triggering on Issues, and then secondarily only when they are edited or labeled.  Then later on this workflow defines using my new action I created (and now published as a tagged version) called issue-notifier.  Done.  Now whenever an Issue is labeled as a breaking change in this repo and email is sent to a set of partners via email proactively without them having knowledge that there may be something they want to subscribe to in the repo.  Here is an example of seeing it triggered:

issuesampleanim

and the result notification in my inbox:

samplemail

Dev experience for Actions

I’ve had a good experience working with GitHub Actions and learning the various ways of automating a few things beyond just build in my repos.  My #1 wish for the ‘inner loop’ experience in creating Actions is the debugging experience.  You have to actually push the workflow and trigger it so ‘test’ it.  This leads to a slow inner-loop development flow.  It would be nice to have some more local runner capability to streamline this process and not muddy the repo with a bunch of check-ins fixing dumb things as you are iterating.

Anyhow, if you want to use this action I created, feel free: https://github.com/marketplace/actions/github-issue-notifier

| Comments

On this lazy Sunday morning I was catching up among the feeds and saw Scott Hanselman’s post about customizing Windows Terminal a bit more.  I had already done this a while back and got my terminal all fancy looking with those cool Powerline fonts and such. 

Screenshot of Windows Terminal

It’s a simple thing, but actually does make my experience a bit nicer to work in the terminal environment.  I’m not really a CLI kind of person – it’s growing on me though – so these customizations help make it more pleasing to me.  I use PowerShell (PowerShell Core to be specific) as my default shell.  I use it because I like the ability of some of the modules from time to time that enable me to do some quick things working with Azure.  Scott’s post had a step to customize basically your startup script for PowerShell.  You can get to this from your shell by typing:

notepad $PROFILE

from a PowerShell prompt.

NOTE: PowerShell and PowerShell Core do NOT share the same profile script, so if you want similar customizations for both, you need to edit both profiles.  The $PROFILE trick above will take you to the right startup profile script for each shell.

As I inspected mine, I was reminded of my favorite command: Set-Location.  It’s simple but it will let you basically create aliases to quickly move to directories.  Take a look in action for me.  While I do have a startup directory configured for Windows Terminal, it’s nice to quickly navigate around.

Animated GIF image of using Windows Terminal shortcuts

So for me I’ve got a few quick shortcuts to get to some most-used directories while working in Terminal.  Here’s mine that I have:

# Helper function to change directory to my development workspace
function source { Set-Location c:\\users\\timheuer\\documents\\github }
function ghroot { Set-Location c:\\users\\timheuer\\documents\\github }
function sroot { Set-Location c:\\users\timheuer\\source\\repos }
function dl { Set-Location c:\\users\\timheuer\\downloads }
function desk { Set-Location c:\\users\\timheuer\\desktop }
function od { Set-Location c:\\users\\timheuer\\OneDrive }

It’s dumb simple, but it saves me keystrokes when I’m working in Terminal and moving back and forth.  What’s your favorite tips to use in Terminal?

| Comments

Yay! .NET Core 3.0 is now available!  You now are migrating your apps and want to get it to your favorite cloud hosting solution.  But they may not have it quite ready yet and you are still eager to deploy to production.  No worries, we have a solution for you.

As our documentation explains on different deployment models we have for .NET Core apps (simplified);

  • Framework-dependent (FDD): you are expecting the required framework to be where you are deploying…you are just deploying your code.
  • Self-contained (SCD) : you are packaging the required libraries and runtimes needed for your code to run, not expecting any shared runtime on your endpoint.
  • Framework-dependent executable (FDE): similar to FDD, but packaged as an executable, but expects a shared runtime to exist where you are deploying

Ideally your favorite cloud host provider will have your desired runtime available to you in their PaaS offerings.  But we’ve already established you are eager and want to get your new app there sooner.  No worries, then SCD is for you.  So how do you do that?  Here’s some helpful hints to get you going.

Producing the bits for SCD deployment is a part of the ‘publish’ pipeline to get the fully functional bits in all the right formats/places for you.  Here’s how you get it going in a few different environments.  First, though all of these require the understanding of runtime identifier values, or RID as we affectionately call it.  RIDs identify the target platform where the app will run.  You can see a list of all possible values in the RID catalog.  Understanding your value is key first.  For this, I’m going to deploy to Azure App Service for Linux and going to use linux-64 as my RID in all samples below.  This would vary based on what/where you are deploying.  I am also assuming in my samples that I’m executing these commands where they know the context of where my csproj file exists.  You can, of course, specify the path to your project files explicitly.

dotnet CLI

From the dotnet CLI you would first want to build and then publish using the RID.  Why build?  Well, it’s valuable to ensure your building against the RID that you will publish.  Catch any build errors in advance, ya know?  So from the CLI:

dotnet build -r linux-x64
dotnet publish --self-contained -r linux-x64

The key here is the ‘-r’ and ‘--self-contained’ arguments.  I’m using ‘-r’ but you can also use ‘--runtime’ for the long form.  What this will produce (unless you specified a different output argument) is a folder with your RID and then the publish folder within that.  So for me above in my project directory it would be in \bin\release\netcoreapp3.0\linux-x64\publish and everything in that \publish is what is needed for SCD. 

Using the CLI is the basic you can do.  Now it’s on your own to push the bits where they need to be, specify startup commands, etc.  But better, you can use some tools that build on top of this CLI goodness.

Azure DevOps 

Let’s first talk about DevOps.  You should be using this.  No let’s be real clear – USE CI/CD!!! “But I’m only myself as a developer!” So what, you should still use this.  I’m a believer now and it is so simple to set up and you then just worry about committing your code and your DevOps flow/pipelines take care of build/test/publish/release for you…it’s awesome.  With that out of the way, let’s show you how to do it in Azure DevOps. 

Honestly, if you haven’t gotten the hang of CI/CD you really should spend the time in one day and get your project configured.  Whether it is Azure DevOps, GitHub Actions, AppVeyor, Jenkins, whatever, you will be better off.  Azure DevOps is basically free and you’ll be happier once you’ve got it done.  You owe it to yourself.  But don’t worry, I explain the other ways below too.

In your pipeline you would add the .NET Core task and configure it for publish (assuming you also have the build/test).  This task basically calls the CLI so you put the same arguments as we just went through.  Here is my task in part one:

Screenshot of Azure DevOps pipeline showing publish arguments

Observe the ‘Zip Published Projects’ – I enabled this because this is what will be used for Azure App Service deploy next.

SPECIAL NOTE: When using the ‘Use .NET Core’ task in Azure DevOps, I recommend using the major.minor.x pattern to specify the version, so in this case 3.0.x.  This will ensure you get the latest patch updates for your specified major.minor version.  For self-contained this is smart since you are bringing the platform runtime with you and not getting auto-updates from the platform.

Then you want to deploy it, and for me to Azure App Service, I add the Azure App Service Deploy task and configure it using my published bits from the previous step.  Notice I’m still specifying the Runtime Stack value (even though at the time of this writing I need to use SCD) and specifying the startup command as the folder path to my app.

Screenshot of Azure DevOps pipeline showing deployment

And that’s it…now when I commit a change, these pipelines run and build/publish/deploy my app to my cloud provider without me having to worry if they have the runtime available yet or not.  You should be able to do this with Azure, AWS, wherever, and still use Azure DevOps to manage your workflow (or GitHub Actions).

Oh you’re a YAML person?  Sorry about that…here’s basically what it looks like using Azure DevOps tasks:

steps:
    # task for publish CLI command
    - task: [email protected]
    displayName: Publish
    inputs:
    command: publish
    publishWebProjects: True
    arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory) --self-contained -r linux-x64'
    zipAfterPublish: True

    #task for deploy to Azure App Service
    - task: [email protected]
    displayName: 'Azure App Service Deploy: tacticview'
    inputs:
    azureSubscription: 'Azure-Microsoft'
    appType: webAppLinux
    WebAppName: tacticview
    deployToSlotOrASE: true
    ResourceGroupName: 'timheuer-linuxappsvc'
    SlotName: staging
    RuntimeStack: 'DOTNETCORE|3.0'
    StartupCommand: /home/site/wwwroot/TacticView
    

So there you have it for Azure DevOps!

GitHub Actions

For GitHub Actions, it is very similar to Azure DevOps using the CLI.  Here’s the relevant snippet for those steps (minus the deploy part):

name: ASP.NET Core CI

    on: [push]

    jobs:
    build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/[email protected]
    - name: Setup .NET Core
    uses: actions/[email protected]
    with:
    dotnet-version: 2.2.108
    - name: Build with dotnet
    run: dotnet build --configuration Release
    - name: Publish with dotnet
    run: dotnet build --configuration Release -r linux-x64 --self-contained

    

You would them want to add the deploy pieces in GitHub Actions and you can follow along with that from Abel’s blog post.

Visual Studio

I STRONGLY recommend you adopt a full CI/CD workflow for all your projects.  This has gotten so simple for most cases that it would be a shame not to do that.  But we do also provide means in Visual Studio tools to publish as well.  In your ASP.NET Core application if you right-click and choose Publish, you’ll see the options.  You first create a publish profile which walks you through where you want to publish.  I won’t show that part because it can be different depending on your selection.  But once complete you’ll be presented with this view:

Screenshot of Visual Studio showing deployment mode option

In that view you click on the area highlighted that is labeled Deployment Mode and will be presented with the dialog to change:

Screenshot of Visual Studio showing deployment mode and target runtime options

Once those two things are changed, then when you click Publish it will use the self-contained version of your app and push to whichever endpoint you chose.  And you are done for Visual Studio.

VS Code

What about if you use Visual Studio Code, VS Code?  With the latest release of the Azure App Service extension, .NET projects got better support for deploying to Azure App Service.  The default flow for this is simpler and doesn’t assume you want to deploy using SCD, so you have to do a bit more setup.  When you have a C# project in VS Code you should get prompted (and you should accept) the option to add assets to your project.

Screenshot of Visual Studio Code dialog requesting to add assets

This adds a .vscode foler with a tasks.json file in it that looks like this:

{
    "version": "2.0.0",
    "tasks": [
    {
    "label": "build",
    "command": "dotnet",
    "type": "process",
    "args": [
    "build",
    "${workspaceFolder}/testwebdeploy.csproj",
    "/property:GenerateFullPaths=true",
    "/consoleloggerparameters:NoSummary"
    ],
    "problemMatcher": "$msCompile"
    },
    {
    "label": "publish",
    "command": "dotnet",
    "type": "process",
    "args": [
    "publish",
    "${workspaceFolder}/testwebdeploy.csproj",
    "/property:GenerateFullPaths=true",
    "/consoleloggerparameters:NoSummary"
    ],
    "problemMatcher": "$msCompile"
    },
    {
    "label": "watch",
    "command": "dotnet",
    "type": "process",
    "args": [
    "watch",
    "run",
    "${workspaceFolder}/testwebdeploy.csproj",
    "/property:GenerateFullPaths=true",
    "/consoleloggerparameters:NoSummary"
    ],
    "problemMatcher": "$msCompile"
    }
    ]
    }
    

Note lines 17-24 which show the CLI commands we’ve already been talking about here.  So these you’d have to change to add arguments.  For brevity I’m only showing the modified publish lines here to what you would need:

{
    "label": "publish",
    "command": "dotnet",
    "type": "process",
    "args": [
    "publish",
    "${workspaceFolder}",
    "--configuration",
    "Release",
    "/property:GenerateFullPaths=true",
    "/consoleloggerparameters:NoSummary",
    "--runtime",
    "linux-x64",
    "--output",
    "bin/release/netcoreapp3.0/publish",
    "--self-contained",
    ],
    "problemMatcher": "$msCompile",
    "dependsOn": "clean"
    }
    

But that’s not it.  Remember when we talked about how the CLI publishes to a /netcoreapp3.0/<RID>/publish folder?  You’ll need to know that for the Azure App Service deploy.  Notice I added an explicit output argument here.  Why?  Well laziness for one.  The App Service Extension when you deploy will also add a settings.json file in your .vscode folder that looks like this:

{
    "appService.preDeployTask": "publish",
    "appService.deploySubpath": "bin/Release/netcoreapp3.0/publish"
    }
    

Notice the appService.deploySubpath argument?  Well I just didn’t want to change that to /linuxx64/publish.  Either way, you could have changed the argument here or in tasks.json but the bottom line is they need to match paths so the extension knows what you want to publish!  With these two complete, when you choose to publish using the extension you’ll be publishing a self-contained app to your provisioned Azure App Service instance.

I hope this helps get an idea of the different ways you can publish and how to use the SCD option.  Realistically you don’t need to use this in cloud environments which support your framework version.  In fact, as soon as they do support your version, go back in to your DevOps flow and remove the SCD argument and kick off a new release…no need to change code in your project or use your dev tools…another benefit of using DevOps – you can even do it from your phone!

Thanks for reading this far!

| Comments

I don’t like C++.  There I said it…got it out of the way.  It’s not a fair statement, as I know many people who do and think I’m crazy for not only writing code in C++.  As someone who didn’t come from a traditional computer science background, I just never ‘grew up’ on C or C++ as fundamentals. 

NOTE: You can hear more about my journey (and others) to tech on the awesome CodeNewbie podcast.  Here’s my episode right here: From police recruit to developer.

Go ahead, insert ‘you’re not a real programmer’ comments below.  I’ve established enough thick skin over the years to hear that feedback.  Anyhow, I digress.  While I’m not a fan of C++ it really isn’t about the language but the iteration dev loop.  I’ve often found it slower than what I’d like and the mix/match of toolsets, settings, etc. has left a lot to be desired to me.  HOWEVER, all this said, looking at C++ code is an excellent way to learn and to actually appreciate more the language and code that others write.  No better source of learning development exists than code.

Last week at the Microsoft Build conference a fun new project was unleashed to the developer community…a new Windows Terminal.  AND it is Open Source! 

I’ve never seen as much excitement about a terminal app in this ecosystem as I have the past 10 days.  So much fun.  First, here’s the team that brings you the Windows Terminal project:

Great people, passionate about making Windows a great development environment for no matter what you are developing.  This is also the team bringing you WSL and the new capabilities there!  So you want to try out the terminal…your first question is Where can I get it?

Building Windows Terminal

Well, for now it is source only.  This will likely change over the course of the next few months, but for now if you really want to try it out, you need to build it yourself.  The repository on GitHub has all the bits and instructions for you to do so.  I jumped on this right away like others but ran into a few bumps because I was using Visual Studio 2019.  Luckily this has been solved by this PR to help make the scavenger hunt for dependencies less painful. 

SIDE NOTE: If you have an open source or team-based project, do yourself a favor and add a .vsconfig file now.  Details: Configure Visual Studio across your organization with .vsconfig

Basically the steps are:

If you use Visual Studio 2019 (recommended by me) opening the OpenConsole.sln file should prompt you with a screen that will give you a note to ‘install’ the missing dependencies in the Solution Explorer:

If you click Install it will run you through the missing components you may not have enabled for completing the build.  Finish that process.  Once done, you will have all the bits and can go back to the OpenConsole.sln project and choose ‘Build Solution’ and start the process.  Lots of stuff building for the first time so it will take a bit.  Now what?

Deploying/Running Windows Terminal

Building is step 1, now you want to run it.  Windows Terminal is a Universal Windows Platform (UWP) application and must be deployed first.  The easiest way for doing this is from Visual Studio, but you may want to know what project!?  The Terminal\CascadiaPackage project is the one you want and just right-click on that and choose Deploy.  This will deploy it to your machine (assuming you enabled developer mode).  And this will now be in your start menu:

Click/tap/whatever on that and you will launch Terminal for the first time!

Wait, where are the tabs I saw?

Ah, now that you have it running, let’s take a tour.  The settings right now are a JSON file that you can access via the settings menu.  How do you get the settings menu?  Hit CTRL + T to bring up the tab view and then you should have a drop-down menu in the upper right area:

If you click Settings it will open up the profile.json file in the default editor on your machine configured for editing JSON files.  For me this was Visual Studio, but you can use Code or other editor as well.  This is editing your own profile for your terminal environment.  By default this JSON is void of white-space so you may want to format it to make it more readable.  In Visual Studio that is CTRL + K, CTRL + D and it will prettify it for you.  You’ll then see some initial settings in the top of the file:

{
  "defaultProfile": "{a933a071-2a32-42c9-b03a-550845793252}",
  "initialRows": 30,
  "initialCols": 120,
  "alwaysShowTabs": true,
  "showTerminalTitleInTitlebar": true,
  "experimental_showTabsInTitlebar": false,

If you change line 5 here like I did to true then you will always start with tabs even if only one console host is running.  When you save the profile.json file it will re-format to no whitespace FYI (there is a watcher on the file in the code) but the settings will become immediate.

Navigating the profile.json options

The profile file main meat is in the profiles themselves.  These drive what shells/consoles you can launch and their configuration.  For example here is a snippet of what mine looks like right now a bit:

{
  "defaultProfile": "{a933a071-2a32-42c9-b03a-550845793252}",
  "initialRows": 30,
  "initialCols": 120,
  "alwaysShowTabs": true,
  "showTerminalTitleInTitlebar": true,
  "experimental_showTabsInTitlebar": false,
  "profiles": [
    {
      "startingDirectory": "c:\\users\\timheuer\\documents\\github",
      "guid": "{b056b6a8-89ba-4868-86c6-2ea078cd4fdd}",
      "name": "cmd",
      "colorscheme": "UbuntuLegit",
      "historySize": 9001,
      "snapOnInput": true,
      "cursorColor": "#FFFFFF",
      "cursorHeight": 25,
      "cursorShape": "vintage",
      "commandline": "cmd.exe",
      "fontFace": "Cascadia Code",
      "fontSize": 12,
      "acrylicOpacity": 0.75,
      "useAcrylic": true,
      "closeOnExit": true,
      "padding": "0, 0, 0, 0",
      "icon": "ms-appdata:///roaming/cmd-icon.png"
    },
    {
      "startingDirectory": "c:\\users\\timheuer\\documents\\github",
      "guid": "{a933a071-2a32-42c9-b03a-550845793252}",
      "name": "PowerShell",
      "background": "#0C0C0C",
      "colorscheme": "UbuntuLegit",
      "historySize": 9001,
      "snapOnInput": true,
      "cursorColor": "#FFFFFF",
      "cursorHeight": 25,
      "cursorShape": "vintage",
      "commandline": "powershell.exe",
      "fontFace": "Meslo LG M for Powerline",
      "fontSize": 12,
      "acrylicOpacity": 0.75,
      "useAcrylic": true,
      "closeOnExit": true,
      "padding": "0, 0, 0, 0",
      "icon": "ms-appdata:///roaming/powershell_64.png"
    },

While I don’t have all options enabled a little spelunking the source code helps you know what other options exist:

  • name: the name of the particular profile.  Right now this is what shows in the drop-down menu shown previously.  There is an issue logged to perhaps make this the name of the tab as well.
  • guid: the unique identifier of this profile.  Incidentally if you didn’t know and wanted to create your own profiles and need a GUID, VIsual Studio has a “Create GUID” tool available from the tools menu…choose registry format and then copy/paste
  • colorscheme: this maps to the color scheme for this area…which is an array of colors that maps to the various foreground/background/text/highlight/etc. settings that are also configurable.  You can see in your profile file that Campbell and a few others (Solarized Dark/Light) are pre-configured.  You can use iterm2colors values to create a new scheme.
  • foreground: the foreground, duh (overwriting colortable/scheme)
  • background: the background, duh (overwriting colortable/scheme)
  • colortable: an in-line version of the color array that would be within a scheme
  • historySize: I honestly haven’t looked at this one yet in the code to know
  • snapOnInput: I honestly haven’t looked at this one either
  • cursorColor: color of the cursor style you chose
  • cursorShape: different options to show the cursor: vintage (thick underscore), bar (vertial bar), underscore (thin underscore), filledBox, emptyBox
  • cursorHeight: height for the cursor
  • commandline: the command to run for the profile (full path or something that will be found in PATH)
  • fontFace: the fond to use for this profile.  You may see in mine that I am using ‘Meslo LG M for Powerline’ to get the cool customizations of the prompt.  Find more on how to do that here and it works in Windows Terminal
    • Note: the team will also be open sourcing a new font that will be used for the terminal, but is not yet available as of this post
  • fontSize: size of the font
  • acrylicOpacity: the opacity of the window if you choose to use the acrylic (transparency) feature.  value from 0-1
  • useAcrylic: true/false if you want to use the transparency
  • scrollbarState: hidden/visible are the options
  • closeOnExit: true/false if you want the tab to close when you initiate the exit command for your host
  • padding: this affects some of the output but honestly not working well right now, recommend leaving at 0,0,0,0
  • startingDirectory: the startup directory for this profile.  I have mine configured with my github directory where all my code is instead of $home (which is default)
  • icon: an icon that will show in the menu and the tab.  These live in your RoamingState directory (C:\Users\<yourusername>AppData\Local\Packages\WindowsTerminalDev_8wekyb3d8bbwe\RoamingState) and the format for the value is “ms-appdata:///roaming/<yourfilename>”

For now these are the available options that you should fitz around with to customize what you need to change.  I’ve messed around with these enough and got to where I want them to be for me right now.

Navigating the source and contributing

As I started this post about how I didn’t like C++, it is a good place to learn a lot.  The Windows Terminal source is a maze of someone else’s code and right now without a clear map.  So the best way is to really just dig in and follow a particular flow.  I find this is the best way to navigate any project source code: find one feature and try to find and follow it in the code.  For me, I was messing with the settings file so much and I hated using the menu with my mouse I wanted a keyboard shortcut.  I knew that CTRL + T launched a new tab, so hey I could implement a keyboard shortcut to launch the settings file quickly.  I first logged that feature as an issue on the project: Add keybinding for access to Settings and then went to work.  I knew that the new tab shortcut existed so I took my own advice: follow the code!  After a few tries and navigating all the headers and various code files I submitted a PR to the project for my own issue: PR#684.  There were 6 files changed to add this key binding and if you look at the change I proposed, there was already a function I could call (same one the menu uses) and I really just needed to map the key binding and have it call the existing function.  Simple, but I learned a bit of how the current code is structured in the repository.

So my recommendation would be to find something that you are seeing as a piece of existing functionality or something you want to add yourself and start looking at searching for where you think it might be.  You’ll quickly find out some of the structure around TerminalSettings, TerminalCore, TerminalControl, etc. and play around with making some changes.  Keep in mind this is active development and things will change around quite frequently.  The team even indicates much:

Note: The Command-Line Team is actively working out of this repository and will be periodically re-structuring the code to make it easier to comprehend, navigate, build, test, and contribute to, so DO expect significant changes to code layout on a regular basis.

I have already been bitten a bit by a merge, but no big deal just fix up a few things and move along again!

What’s Next?

The team has a good aspiration of what they want to accomplish and just getting started.  Quality will improve, bugs will get fixed, consumption of the terminal will be better, etc.  Read the README file in the repository and engage with the team on Twitter (their contact info in the README).

Hope this helps!