Azure hosted .NET web application

I needed a small application that manages attendance for a large people management / church management system known as RockRMS. We have this Rock system running in Azure hosted on a VM and using a SQL Database. There were some functionalities lacking in the Rock system and so I created this small extension application and deployed it into Azure using App Service. This article reviews the process of setting up and deploying such App Service applications. I have another article focused more on explaining some of the details of App Service here:

https://solidfish.com/azure-overview-and-app-service/

 

This article is focused more on the setup and deployment of the application. The application is based on .Net Core 2.1 MVC and using ADO.NET (no EF/ORM). Its a fairly simple application and the complete source code can be found here:

https://github.com/revivepres/rockx

 

Setup the App Service using Azure CLI

Azure App Service can be used to host a variety of applications. Refer to my article above for more details. In this example, I’m deploying a simple .Net Core application. Before we deploy into App Service we need to create a new App Service Plan and do some initial setup. There are many ways to do this – such as through Azure Portal, Visual Studios or using the Azure CLI. To use the Azure CLI we need to first download and install it. This can be found here:

https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest

 

Once Azure CLI is installed we can run commands to setup the App Service. First thing we need to do is create a Resource Group that will contain this App Service. We can create Resource Groups through the Azure CLI like shown below. Note I’m setting the region location with the command.

az group create -n solidfishrg --location eastus

Next we need to setup the App Service Plan. All App Services run under an App Service Plan, which is part of a Resource Group. The command below shows how I create an App Service Plan of size “S2” and attach it under the Resource Group I created above. It is considered best practice to include the word “plan” when naming App Service Plans.

az appservice plan create --name solidfishplan --resource-group solidfishrg --sku S1

With the App Service Plan created we can now create the actual App Service application that will run in that App Service Plan. The command example below shows me creating an application called “solidfishwebapp” under the App Service Plan I created above.

az webapp create -g solidfishrg -p solidfishplan -n solidfishwebapp

The webapp App Service is created and running. We are now ready to publish our application code into the App Service. When working with .Net and Visual Studio – perhaps the easiest way to deploy into an App Service is by right clicking on the Solution Explorer in Visual Studios and selecting “Publish to Azure”. This is an option that is available by default on Visual Studio 2015+. When selecting this option a wizard will be prompted for a couple of steps to finalize the deployment. For my example here, I did not use Visual Studio deployment path but instead used Local Git deployment. This is explained further in the next section.

 

App Service deployment through a Git repository

For this App Service I deploy my application through what is called a Local Git repository. Below on the screen shot you can see other ways of deploying the application. Most are using some sort of code repository though we can FTP files straight over as well.

After selecting the “Local Git” option I am prompted with a Git Clone address to where I can commit my code to. When using a Local Git repository as the deployment process, it also includes a build service using the Kudu build agent. This means every time code is checked in the Kudu build service will build before deployment. Also note that before I can push my code to the given Git Clone Uri I must first setup Deployment Credentials. If we have Azure AD setup we could leverage that service. In my case I created separate credentials which I’m prompted to enter every time I try to push changes through Git.

Below is an example of me pushing my changes to the Local Git repository. You will see that as part of the push, I see the remote actions for restoring .net packages and performing the MSBuild. At the end of it all we see it also restart the App Service to pickup the new deployment.

johnlee@solidfish:~/Documents/Projects/rockx$ git push azure
Counting objects: 77, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (72/72), done.
Writing objects: 100% (77/77), 523.15 KiB | 5.69 MiB/s, done.
Total 77 (delta 6), reused 0 (delta 0)
remote: Updating branch 'master'.
remote: Updating submodules.
remote: Preparing deployment for commit id '5363b72377'.
remote: Generating deployment script.
remote: Project file path: ./rockx.csproj
remote: Generated deployment script files
remote: Running deployment command...
remote: Handling ASP.NET Core Web Application deployment.
remote: .....
remote:   Restoring packages for /home/site/repository/rockx.csproj...
remote:   Installing Microsoft.AspNetCore.Razor.Design 2.1.2.
remote: /home/site/repository/rockx.csproj : warning NU1608: Detected package version outside of dependency constraint: Microsoft.AspNetCore.App 2.1.0 requires Microsoft.AspNetCore.Razor.Design (= 2.1.0) but version Microsoft.AspNetCore.Razor.Design 2.1.2 was resolved.
remote:   Generating MSBuild file /home/site/repository/obj/rockx.csproj.nuget.g.props.
remote:   Generating MSBuild file /home/site/repository/obj/rockx.csproj.nuget.g.targets.
remote:   Restore completed in 9.49 sec for /home/site/repository/rockx.csproj.
remote: Microsoft (R) Build Engine version 15.7.179.6572 for .NET Core
remote: Copyright (C) Microsoft Corporation. All rights reserved.
remote: 
remote: ...
remote: /home/site/repository/rockx.csproj : warning NU1608: Detected package version outside of dependency constraint: Microsoft.AspNetCore.App 2.1.0 requires Microsoft.AspNetCore.Razor.Design (= 2.1.0) but version Microsoft.AspNetCore.Razor.Design 2.1.2 was resolved.
remote:   Restore completed in 220.3 ms for /home/site/repository/rockx.csproj.
remote: /home/site/repository/rockx.csproj : warning NU1608: Detected package version outside of dependency constraint: Microsoft.AspNetCore.App 2.1.0 requires Microsoft.AspNetCore.Razor.Design (= 2.1.0) but version Microsoft.AspNetCore.Razor.Design 2.1.2 was resolved.
remote: ................
remote:   rockx -> /home/site/repository/bin/Release/netcoreapp2.1/rockx.dll
remote:   rockx -> /home/site/repository/bin/Release/netcoreapp2.1/rockx.Views.dll
remote: ..
remote:   rockx -> /tmp/8d67365c1c59a62/
remote: Kudu sync from: '/tmp/8d67365c1c59a62' to: '/home/site/wwwroot'
remote: Copying file: 'appsettings.Development.json'
remote: Copying file: 'appsettings.json'
remote: Copying file: 'rockx.Views.dll'
remote: Copying file: 'rockx.Views.pdb'
remote: Copying file: 'rockx.deps.json'
remote: Copying file: 'rockx.dll'
remote: Copying file: 'rockx.pdb'
remote: Copying file: 'rockx.runtimeconfig.json'
remote: Copying file: 'web.config'
remote: Deleting file: 'hostingstart.html'
remote: Copying file: 'wwwroot/favicon.ico'
remote: Copying file: 'wwwroot/css/site.css'
remote: Copying file: 'wwwroot/css/site.min.css'
remote: Copying file: 'wwwroot/images/banner1.svg'
remote: Copying file: 'wwwroot/images/banner2.svg'
remote: Copying file: 'wwwroot/images/banner3.svg'
remote: Copying file: 'wwwroot/js/site.js'
remote: Copying file: 'wwwroot/js/site.min.js'
remote: Copying file: 'wwwroot/lib/bootstrap/.bower.json'
remote: Copying file: 'wwwroot/lib/bootstrap/LICENSE'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/css/bootstrap.css'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/css/bootstrap.css.map'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/css/bootstrap.min.css'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/js/bootstrap.js'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/js/bootstrap.min.js'
remote: Copying file: 'wwwroot/lib/bootstrap/dist/js/npm.js'
remote: Copying file: 'wwwroot/lib/jquery/.bower.json'
remote: Copying file: 'wwwroot/lib/jquery/LICENSE.txt'
remote: Copying file: 'wwwroot/lib/jquery/dist/jquery.js'
remote: Copying file: 'wwwroot/lib/jquery/dist/jquery.min.js'
remote: Copying file: 'wwwroot/lib/jquery/dist/jquery.min.map'
remote: Copying file: 'wwwroot/lib/jquery-validation/.bower.json'
remote: Copying file: 'wwwroot/lib/jquery-validation/LICENSE.md'
remote: Copying file: 'wwwroot/lib/jquery-validation/dist/additional-methods.js'
remote: Copying file: 'wwwroot/lib/jquery-validation/dist/additional-methods.min.js'
remote: Copying file: 'wwwroot/lib/jquery-validation/dist/jquery.validate.js'
remote: Copying file: 'wwwroot/lib/jquery-validation/dist/jquery.validate.min.js'
remote: Copying file: 'wwwroot/lib/jquery-validation-unobtrusive/.bower.json'
remote: Copying file: 'wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt'
remote: Omitting next output lines...
remote: Finished successfully.
remote: Running post deployment command(s)...
remote: Deployment successful.
remote: App container will begin restart within 10 seconds.
To https://revivepres.scm.azurewebsites.net:443/revivepres.git
 * [new branch]      master -> master

From the Azure Portal I can see the similar status as shown below.

 

Azure SQL Database

As stated at the top of this article, this sample application connects to an existing system and SQL Server Database. During local development I wanted to use the DEV instance of that database. In order to do this I needed to setup a Firewall rule in that databases’s Firewall Settings page. You can see below how I did that through the Azure Portal.

With the firewall settings configured, I can login to the database through SQL Management Studio (as well as Visual Studio). Below I’m showing how through SQL Management Studio I created a login that my application would be using to connect into this database.  I added the reader and writer roles to this account.

CREATE USER rockx FOR LOGIN rockx
GO

EXEC sp_addrolemember 'db_datareader', 'rockx'
EXEC sp_addrolemember 'db_datawriter', 'rockx'
GO

 

This last screen shot from Azure SQL Database is the connection string that is automatically generated for you through the portal. We can copy this and simply replace the {your_username} and {your_password} fields with the login information I created above in SQL Management Studio.

I could save this connection string into my AppSettings.json file. However, this means this file would be part of source control (thereby exposing my credential information) or I would have to manage this separately from Git. The common best practice when using .net is to use User Secrets, however for this example application I take and discuss a different approach.

 

Deployment Slots and Application Settings

As part of the App Service service, we are able to store environment variables for each application running in App Service. These variables can be configured to work for a specific Deployment Slot. For more information on Deployment Slots refer to my other App Service article linked above.

 

For this application I’ve setup two deployment slots – Production and Staging. You can see this in the Azure Portal by going to the App Service and selecting the Deployment Slots page.

Each Deployment Slot has its own Local Git – however, on my local environment I only point to the staging slot. So when I commit code changes those changes are deployed to the staging instance only. Once the changes are reviewed and ready for production, I’m able to do a Deployment Swap. You can see that button in the screen shot above. The Deployment Swap will swap the code that is in staging with the code in production. Taking this deployment approach allows me to test in the staging area and know with high certainty how it will perform in production (since its deploying the exact same code).

Also, with this approach I’m able to get the production instance into the staging area and either continue pushing changes up into it or perform other tests in that staging area. For example, the production code is now in staging so I could connect Visual Studio into it and perform tests without fearing about disrupting Production. This is great when we have those issues that only came up in production and we couldnt replicate in our local or staging areas.

 

The last thing to discuss are Application Settings. I mentioned in the previous section that I could have stored the database connection string into the AppSettings.json configuration file. However, this causes risk with committing that information into source control. With App Service, we are able to set environment variables per each specific deployment slot. So in my staging slot, I have the following application settings set.

What is great about this approach is that I can configure variables that are specific to slots or general to the whole application. For example, things like connection strings are specific to slots – staging slot should connect to the staging database and production slot connects to production database. But other variables can be global across all slots. You can see in the screen shot above that those variables checked as “Slot Setting” are specific to this slot instance only.

 

Lastly – when using the out-of-box App Service to deploy applications, we get a default URL that always has SSL/TLS enabled. That url is in the format of:

https://{your application name}.azurewebsites.net

 

References

Azure App Service
https://azure.microsoft.com/en-us/services/app-service/?v=18.51