Skip to content

This repo contains notes I took while following .NET on Azure video series on YouTube.

Notifications You must be signed in to change notification settings

akhanalcs/dotnet-on-azure

Repository files navigation

dotnet-on-azure

This repo contains notes I took while following this video series on YouTube.

Common Services

  • Azure App Service
  • Azure Kubernetes Service
  • Azure Functions
  • Application Insights
  • Azure SignalR
  • Azure SQL
  • Key Vault
  • Container Registry
  • Blob Storage
  • Managed Identity
  • Azure Container Apps
  • App Config
  • Cosmos Db

Understanding Tenants and Subscriptions

graph TD
    A[Enrollment] ---> B["ABC Organization (Tenant aka Directory)<br>For eg: abc.onmicrosoft.com"]
    B --> C1["Contoso(Tenant)<br>For eg: contoso.onmicrosoft.com"]
    B --> C2["Fabrikam(Tenant)<br>For eg: fabrikam.onmicrosoft.com"]
    B ---> C3["Some Saas Non Azure Subscription"] %% Extra "-" to push it down a level
    C1 --> D11["Dev Sub"]
    C1 --> D12["Prod Sub"]
    D11 --> E111["Dev RG1"]
    D11 --> E112["Dev RG2"]
    D12 --> E121["Prod RGs"]
    E111 --> F1111["Resources.<br>For eg: AppService, SqlDb etc."]
    E112 --> F1121["..."]
    E121 --> F1211["..."]
    C2 --> D21["Dev Sub"]
    C2 --> D22["Prod Sub"]
    D21 --> E211["Dev RGs"]
    D22 --> E221["Prod RGs"]
    E211 --> F2111["..."]
    E221 --> F2211["..."]
    C3 --> D31["Office 365"]
    C3 --> D32["Dynamics 365"]
    D31 --> E311["For eg: 100 E5 licenses"]
    D32 --> E321["For eg: 50 licenses"]

    classDef hidden display: none;

Note: Users live at Tenant/ Directory level.

Enrollment

  • Azure billing and cost management construct for Enterprise Agreement customers. ea.azure.com.
  • For large companies.

Tenant aka Directory

  • Is associated with a single entity, i.e. person, company or org and can own one or several subscriptions.
  • In my case, it's a person with domain name: affableashkoutlook.onmicrosoft.com and Organization Id (tenant Id): 9d7f6902-2a61-4363-964c-c464b9eaf716(Found by going to Settings -> Directories + subscriptions OR Menu -> Azure Active Directory).
  • Contains user accounts and groups.
  • EVERY TENANT IS LINKED TO A SINGLE AZURE AD INSTANCE which is shared with all tenant's subscriptions.
  • Each tenant is referred to as an organization.
  • I can create multiple tenants after logging in to Azure Portal.
  • Directory Id is TenantId because of one to one relationship between tenant and Azure AD.
  • The reason it's called directory is because each directory has an Azure AD service associated with it.
  • Every MSFT service is always associated with an Azure AD even if we're not using Azure.
  • For eg: If I'm using O365, I'll have Azure AD at the top of it.
image

Subscriptions

  • Construct for creating separate billing and management boundaries. Is managed in portal.azure.com.
  • An agreement with MSFT to use one or more MSFT cloud platforms or services for which charges accrue based on either: β—‹ Per user license fee. For eg: Saas like Office 365 or Dynamics 365. β—‹ Cloud based resource consumption. For eg: Paas and IaaS.
  • A subscription is linked to a payment setup and each subscription will result in separate bill.
  • Subscription could be CSP (Cloud Service Provider), Pay-As-You-Go, EA etc.
  • For customers like Ashish Khanal who can use credit card and do Pay as you go.
  • Can create multiple subscriptions in Azure account to create separation.
  • A subscription can only be associated with a single Azure AD tenant at any given time.
  • Can be linked to existing identity stores for single sign on, or segregated into a separate area.
  • Becomes the major separation for assignment of RBAC within services.
  • Inside every subscription we can add Resources like VM, SqlDb etc.
  • Tenant or Directory has 1:M relationship with Subscription.

Resource Groups

  • A container that holds related resources.
  • Like App Service, Sql Db etc.
  • Resources in 1 RG are completely isolated from resources in another RG.

References:

  1. Subscriptions, licenses, accounts, and tenants
  2. Tenants and Subscriptions
  3. Difference between Tenant and Subscription

View TenandId and Subscription in Azure Portal

Create account in Azure

Go to portal.azure.com. It's pretty self-explanatory.

Setup cloud shell

Install homebrew

Follow instructions here.

Install Azure CLI for macOS using homebrew

Follow instructions here.

sudo chown -R $(whoami) /usr/local/var/homebrew
sudo chown -R $(whoami) /usr/local/opt
chmod u+w /usr/local/opt
brew update && brew install azure-cli

Login to Azure using cloud shell

az login
image

Register Microsoft.CloudShell namespace to your subscription

Why?

Cloud Shell needs access to manage resources. Access is provided through namespace that must be registered to your subscription.

Get your subscription Id using

az account list (Grab Id)

Then

az account set --subscription <Subscription Name or Id>
az provider register --namespace Microsoft.CloudShell

View tenantId

image

Note: tenantId is my DirectoryId and id is my SubscriptionID.

View Subscription

Search for Subscription from the search bar:

image

View Subscription costs

You can see cost of your services inside the Subscription. Click on the Azure subscription 1 shown above.

image

Create apps to deploy to Azure

Create a web app (MunsonPickles.Web) and an API (MunsonPickles.API). Take a look at the code to see how they look.

Note about Blazor Web App in .NET 8:

With .NET 8 comes a new template for Blazor applications simply called Blazor Web App and by default all components use Server-Side-Rendering. To add interactivity to it, you need to add following service and middleware. More info here.

builder.Services.AddRazorComponents() // πŸ‘ˆ Adds services required to server-side render components. Added by default.
	.AddServerComponents(); // πŸ‘ˆ Stuff I added for Server Side Interactivity

app.MapRazorComponents<App>() // πŸ‘ˆ Discovers routable components and sets them  up as endpoints. Added by default.
	.AddServerRenderMode();// πŸ‘ˆ Stuff I added for Server Side Interactivity

Add interactivity to the new Blazor Web App in .NET 8 using this guide.

Db Seed code in API

Line 13 will create the tables, and line 14 will seed the database.

image

Explicit migration is not required in the above approach that looks like

dotnet ef migrations add InitialCreate -o Data/Migrations
dotnet ef database update

So when line db.Database.EnsureCreated() runs, it'll create the database and the next line will initialize the database.

image

Create resource group

ashish@Azure:~$ az group create -g rg-pitstop-eastus-dev-001 -l eastus

πŸ‘‡ -g is for resource group name, -l is for location (Remember RALEIgh).

So the convention that I'll be using is:

ResourceGroup-AppName-Location-Environment-Instance

Create Azure App Service

app-APPNAME-WEB(Because it's a web app)-LOCATION-ENV-INSTANCE

image

Web app runs on App Service Plan which determines CPU and memory of it.

You can name it like this:

asp-APPNAME-LOCATION-ENV-INSTANCE

asp (App Service Plan)

Notice it doesn't have type like web, api etc. after APPNAME. It's because I want to put both the web app and the web api in that app service plan.

Database

Create Database server

Db Server: sqlserver-munson-eastus-dev-001

image

Allow connections to this SQL server from your IP.
They appear under Firewall rules. Only do this for dev scenarios, NOT for PROD.

image

And notice that's my IP address:
image

Create Database

Db Name: sqldb-munson-eastus-dev-001

Grab connection string (ADO.NET SQL auth):
Server=tcp:sqlserver-munson1-eastus-dev-001.database.windows.net,1433;Initial Catalog=sqldb-munson-eastus-dev-001;Persist Security Info=False;User ID=munson;Password={your_password};

Connect to Database using config (Go to the next section to see a better way of doing this)

Add this conncection string to dotnet-secrets.

  1. Go into the project folder and init (dotnet user-secrets init).

  2. This will appear in .csproj file.

    image

Set the connection string with this command:

dotnet user-secrets set ConnectionStrings:Default "Server=tcp:sqlserver-munson1-eastus-dev-001.database.windows.net,1433;Initial Catalog=sqldb-munson-eastus-dev-001;Persist Security Info=False;User ID=munson;Password={your_password};"

This will set the connection string like this in the secrets.json:

image

It actually looks like this (after installing this plugin):

image

This is where that file is stored. Reference.

image

Connect to Database without password (my preferred way, a better way)

Reference

Grab the connection string Server=tcp:sqlserver-munson1-eastus-dev-001.database.windows.net,1433;Initial Catalog=sqldb-munson-eastus-dev-001;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;Authentication="Active Directory Default";

The passwordless connection string includes a configuration value of Authentication=Active Directory Default, which enables Entity Framework Core to use DefaultAzureCredential to connect to Azure services. When the app runs locally, it authenticates with the user you're signed into Visual Studio with. Once the app deploys to Azure, the same code discovers and applies the managed identity that is associated with the hosted app, which you'll configure later.

At this point you need to be logged into Azure using Azure CLI (az login), if you are not logged in, you'll get this exception if you try to run the app:

image

Azure CLI login is shown here.

So far:

Resource Group: rg-munson-eastus-dev-001
App Service: app-munson-web-eastus-dev-001
App Service Plan: asp-munson-eastus-dev-001
Db Server: sqlserver-munson-eastus-dev-001
Db Name: sqldb-munson-eastus-dev-001

Managed Identity

A managed identity from Azure Active Directory (Azure AD) allows App Service to access resources through role-based access control (RBAC), without requiring app credentials. After assigning a managed identity to your web app, Azure takes care of the creation and distribution of a certificate. People don't have to worry about managing secrets or app credentials.

This is secret-less way of doing this, that's why I love it. For eg: No credentials in the connection string.

Any service that supports managed identity (B in the following image) can be securely accessed.

image

Internally, managed identities are service principals of a special type which are locked to only be used with Azure resources.

References

  1. Microsoft Learn
  2. Stackoverflow

Service Principal vs Managed Identity in Azure

image

User Principal vs Service Principal in Azure

Reference

image Β  Β  Β  Β  image

While I'm interacting with my Azure resources, I also talk to my AD to get my token and make requests. Look example here:

image

Deploy web app to Azure

Install Azure toolkit for Rider

Go to Plugins and install Azure Toolkit for Rider.

Sign into Azure toolkit

Go to Tools -> Azure -> Azure Sign In

image

Go with Device Login

image

Select my Subscription

image

Publish to Azure

Right click on Project -> Publish -> Azure

image

Select 'Use Existing Web App' and click on the app shown below, like so:

image

Click Apply -> Run

image

To publish it again, click configuration dropdown in the top right:

image

At this point, the app doesn't work correctly on Azure. You still need to configure the secure connection between the App Service and the SQL database to retrieve your data. Read it all about it here.

image

Connect App service instance to Azure SQL database

The following steps are required to connect the App Service instance to Azure SQL Database:

  1. Create a managed identity for the App Service. The Microsoft.Data.SqlClient library included in your app will automatically discover the managed identity, just like it discovered your local machine Azure User.
  2. Create a SQL database user and associate it with the App Service managed identity.
  3. Assign SQL roles to the database user that allow for read, write, and potentially other permissions.

Use Service connector to accomplish this:
Service Connector is a tool that streamlines authenticated connections between different services in Azure. Service Connector currently supports connecting an App Service to a SQL database via the Azure CLI using the az webapp connection create sql command. This single command completes the three steps mentioned above for you.

Go to Azure Portal and into the app service. You can see that it doesn't have anything under Identity -> System assigned

image

Now run this command (run it in Cloud Shell or Azure CLI):

image

which translates to:

az webapp connection create sql -g rg-sampleapp-eastus-dev-001 -n app-munson-web2-eastus-dev-001 --tg rg-sampleapp-eastus-dev-001 --server sqlserver-munson1-eastus-dev-001 --database sqldb-munson-eastus-dev-001 --system-identity --connection ThisCanBeAnything --client-type dotnet

At this point, you'll have managed Identity showing:

image

This connection string created by the above command will show up inside Configuration:

image

Data Source=sqlserver-munson1-eastus-dev-001.database.windows.net,1433;Initial Catalog=sqldb-munson-eastus-dev-001;Authentication=ActiveDirectoryManagedIdentity

The user should show up in the SQL Db as well

image

Troubleshooting app startup

Now go to the app url to see your app running.

Unfortunately, it didn't start. :(

image

Go to App Service -> Diagnose and solve problems -> Availability and Performance

image

Container crash ->

image

UPDATE: Running the command again solved the issue for me:

image

Now the app runs from Azure! πŸŽ‰

image

Keep in mind that when you deploy a web app to Azure, it treats it as Production. 'Production' is default if DOTNET_ENVIRONMENT and ASPNETCORE_ENVIRONMENT is not set. Reference.

Environment values set in launchSettings.json override values set in the system environment. That file is only used on the local dev machine.

Using Blob Storage

A binary large object (blob) is a collection of binary data stored as a single entity. Blobs are typically images, audio or other multimedia objects, though sometimes binary executable code is stored as a blob.

A general purpose v2 storage account provides access to all of the Azure Storage Services: blobs, files, queues, table and disks.

Blobs in Azure storage are organized into containers.
Before we can upload a blob, we must first create a container.

Create a storage account

image

Add a container to put images of my web app

For eg: I gave 'web' as a name of my folder.

image

The access level of this container is private by default. To change this to public, go to Configuration -> Allow Blob anonymous access -> Enabled -> Save

image

Now change access level of this container: -> Blob

image

Grant web app access to storage account

We need to grant our web app access to the storage account before we can create, read or delete blobs.

Using Azure RBAC, we can give the managed Identity of the web app access to another Azure resource just like any security principal (User Principal or Service Principal explained earlier in this page).

The 'Storage Blob Contributor' role gives the web app (represented by system assigned managed identity) read, write, and delete access to the blob container and data.

Go to my storage account to grant my web app access to it.

Go to IAM -> Role Assignments This shows who has access to this resource. There's ME!

image

Let's add role assignment to a robot πŸ€– (Managed Identity)

Select Add -> Add role assignment

Search for 'Storage block data contributor' role

image

Click Next to Select who needs this role

Managed Identity -> Select Members -> Subscription -> App Service (Web App) ->

The managed Identity shows up.

image

Select it and hit Next.

image

Hit 'Review + assign'.

The IAM page looks like this after the assignment:

image

Now go ahead and upload images to 'web' container using Azure portal. It's a simple file upload from your computer. I uploaded few images of pickles and preserves. πŸ˜ƒ

Put CDN on top of blob storage

The thing is these images are only available in the eat US. If I try to hit the blob url from Asia, it'll have to make bunch of internet hops to get to it. So what we can do is put a CDN on top of our blob storage.

CDN lives on the Azure edge.

Go to CDN ->

Give Profile name, Endpoint name and specify Query string caching behavior.

Hit create:

image

Go to CDN endpoint now

Grab the endpoint hostname that's served through CDN. Origin hostname is being served through the storage living in eastus.

Notice the urls.

image

Now grab the endpoint hostname + web + filename and update the db:

image

Update the code to show product photo in a "col" class.

image

Now the page looks like this:

image

Explanation on Query string caching behavior options:

  1. Ignore Query String: The first request is served from the origin server and the response is cached. Subsequent requests are served from the cache whatever the query string is. This sounds ludicrous!
    Request1:
    Browser (mydomain.com/articles?page=3) -> Azure CDN -> Server (mydomain.com/articles?page=3)
    Request2:
    Browser (mydomain.com/articles?page=42) -> Azure CDN (from cached whatever the query string)
    
  2. Bypass caching for query string: Azure CDN doesn't cache the requests that have a query string.
    Request1:
    Browser (mydomain.com/articles?page=3) -> Azure CDN -> Server (mydomain.com/articles?page=3)
    Request2:
    Browser (mydomain.com/articles?page=3) -> Azure CDN -> Server (mydomain.com/articles?page=3)
    
  3. Use query string: Each request with a unique url including the query string is treated as a unique asset with its own cache.
    Request1:
    Browser (www.example.ashx?q=test1) -> Azure CDN -> Server (www.example.ashx?q=test1)
    Request2:
    Browser (www.example.ashx?q=test1) -> Azure CDN (from cache)
    

The order of the query string parameters doesn't matter. For example, if the Azure CDN environment includes a cached response for the URL www.example.ashx?q=test1&r=test2, then a request for www.example.ashx?r=test2&q=test1 is also served from the cache.

Upload images using APIs

Now we want to give users the ability to upload images while giving a review of a product.

For this we need Azure SDKs.

Go to Dependencies -> Manage NuGet Packages and add these packages to the project:

  1. Azure.Storage.Blobs : To work with Blob storage.
  2. Microsoft.Extensions.Azure : Azure client SDK integration with Microsoft.Extensions libraries.
    For eg: To get this line to work:
    image

To setup connection to Blob. This article helped.

IMPORTANT: (This wasted few hours and caused a lot of frustration)
Your account needs to have Role Assignment to upload files even though I'm the owner.

image

Your account comes into the picture from DefaultAzureCredential used to setup the BlobServiceClient during local development.

Also as you can see in the screenshot above, Azure App Service (Web app) already has access to it through Managed Identity when it runs in the cloud.

image

Take a look at the code to see how I implemented file upload using minimal APIs. It's pretty nice!

Add Auth

Everything about adding auth to the app is documented here.

Deploy .NET Apps to Containers

Benefits of containerizing an app:

  1. All components in a single package.
  2. Assured new instances the same.
  3. Quickly spin up new instances.
  4. Instances can be deployed in many places.
  5. Helps with agile development because you don't have to pull all the services, you can just work with your "micro" service at a time.

Azure container services:

  1. Azure Container registry (sort of like Nuget but for images)
  2. Azure App Service
  3. Azure Functions
  4. Azure Container Apps
    Abstraction over Kubernetes which is really nice!
  5. Azure Kubernetes Service (AKS)
  6. Azure Container Instances
    Allows to new up containers in the cloud and run them. Not really great for prod scenarios because for eg: if the container goes down, it goes down, no orchestrator to replace it.

Create Azure Container Registry from Azure portal

For eg: munsonpicklesacr.azurecr.io

Create Dockerfiles for both API and Web app

# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:8.0-jammy AS build
WORKDIR /source
# copy csproj files of app and libraries, and restore as distinct layers
COPY "MunsonPickles.API/*.csproj" "MunsonPickles.API/"
COPY "MunsonPickles.Shared/*.csproj" "MunsonPickles.Shared/"
RUN dotnet restore "MunsonPickles.API/MunsonPickles.API.csproj"
# copy and build app and libraries
COPY "MunsonPickles.API/" "MunsonPickles.API/"
COPY "MunsonPickles.Shared/" "MunsonPickles.Shared/"
WORKDIR "/source/MunsonPickles.API"
# Currently it doesn't work with --no-restore flag so I've removed it. I plan to use it in the future.
# I opened this issue in Github for this: https://github.com/dotnet/sdk/issues/37291
RUN dotnet publish -o /app
# final stage/image
FROM mcr.microsoft.com/dotnet/nightly/aspnet:8.0-jammy-chiseled-composite
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["./MunsonPickles.API"]

# https://hub.docker.com/_/microsoft-dotnet
FROM mcr.microsoft.com/dotnet/sdk:8.0-jammy AS build
WORKDIR /source
# copy csproj files of app and libraries, and restore as distinct layers
COPY "MunsonPickles.Web/*.csproj" "MunsonPickles.Web/"
COPY "MunsonPickles.Shared/*.csproj" "MunsonPickles.Shared/"
RUN dotnet restore "MunsonPickles.Web/MunsonPickles.Web.csproj"
# copy and build app and libraries
COPY "MunsonPickles.Web/" "MunsonPickles.Web/"
COPY "MunsonPickles.Shared/" "MunsonPickles.Shared/"
WORKDIR "/source/MunsonPickles.Web"
# Currently it doesn't work with --no-restore flag so I've removed it. I plan to use it in the future.
# I opened this issue in Github for this: https://github.com/dotnet/sdk/issues/37291
RUN dotnet publish -o /app
# final stage/image
FROM mcr.microsoft.com/dotnet/nightly/aspnet:8.0-jammy-chiseled-composite
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["./MunsonPickles.Web"]

Reference.

Build docker images from Dockerfile and push it to ACR

You can build it in your local computer and push it to the Azure Container Registry (ACR). But for this example, I want to use cloud shell to do this.

Notice that you have docker in there already!

image

Steps

  1. Clone your repo (with Dockerfile checked in)
    gh repo clone https://github.com/akhanalcs/dotnet-on-azure.git
    
  2. Go to the repo folder
    cd ./dotnet-on-azure
    
  3. Build and push the image to your Azure Container Registry
    az acr build --file MunsonPickles.API.Dockerfile . --image pickles/my-cool-api:0.1.0 --registry munsonpicklesacr.azurecr.io
    
    It looks similar to building an image locally
    docker build -f MunsonPickles.API.Dockerfile . -t pickles/my-cool-api:0.1.0
    

Test running the image locally (by pulling it from acr)

Login to Azure ACR from your computer:

az acr login -n munsonpicklesacr

Pull the image down:

MunsonPicklesACR registry -> Repositories -> find the image -> copy the 'docker pull command'

docker pull munsonpicklesacr.azurecr.io/pickles/my-cool-api:0.1.0

Run the image:

docker run --rm -it -p 8000:8080 -e ASPNETCORE_ENVIRONMENT=Development munsonpicklesacr.azurecr.io/pickles/my-cool-api:0.1.0

Deploy the container to Azure App Service

Create Web App (Azure App Service)

Name: munson-api-linux-westus
Publish: Docker Container

...

After the deployment is complete, go to munson-api-linux-westus Web App -> Identity (under Settings)

Turn On 'Managed Identity'.

Now go to MunsonPicklesACR registry, give access to the managed identity you just created so the web app can pull images from this registry.

Go to Access Control -> Add -> Role Assignment (acr pull) -> Assign access to: managed identity -> choose your app service -> Next -> Review + assign

Now go back to app service to tell it which registry it should go to and which image it should pull.

Go to Deployment Center (under Deployment) -> Settings

Container type: Single container
Registry source: Azure Container Registry
Authentication: Managed Identity
Registry: MunsonPicklesACR
Image: pickles/api
Tag: 0.1.0

-> Save

Restart the web app and take it for a test ride by clicking the url. It should work at this point. πŸŽ‰

Serverless with Azure Functions

  1. Cloud provider manages infrastructure
  2. Allocates resources as necessary
  3. Scales down to zero. Reap this benefit by making your function focus on a specific task, and not do a whole lot so when it's free, it can scale down to zero.
  4. Lets you focus on business logic
  5. It launches in response to events
  6. Integrates with other Azure services

Scenarios of using Functions

  1. Build a web api
  2. Process file uploaded to Blob storages
  3. Respond to database changes
  4. Process message queues
  5. Analyze IoT streams
  6. Real time data with SignalR

Azure Functions "hooks"

Triggers

  • Events that start the function.
  • Have incoming data: For eg: let's say a HTTP request that triggered a function. You can get the request body or query string of that request as incoming parameters.
  • There are triggers for many different services like:
    • HTTP
    • Timer
    • Storage
    • Data
    • Event Grid
    • etc.

Bindings

  • Connect to another service like Send Grid if you wanted to send emails.
  • Input and Output bindings so you can have data come in or you can be writing data to various services.
  • Can have multiple bindings per Azure function. For eg: let's say we had a HTTP request coming in that triggered a function, we could have a binding to table storage that pull out data and we can have another binding to let's say blob storage that pulls out a blob and pulls that into your function and do something with it.

Example scenario

Whenever some image (picture) is uploaded, it's going to write a message to an Azure storage queue, once that starts it's going to kick off an Azure function queue trigger. The function runs and it's going to use a table output binding just to write some data to table storage.

image

It'll show input binding, trigger and an output table binding.

Send message to the storage queue

In MunsonPickles.API project:

  1. Add storage queue SDK. Install Azure.Storage.Queues nuget package.

  2. Add storage queue endpoint url to appsettings.json. It just has queue instead of blob in the connection string.
    For eg: This is my blob storage connection string: "https://stmunsoneastusdev001.blob.core.windows.net/", so the queue connection string will be: "https://stmunsoneastusdev001.queue.core.windows.net/"

  3. Register it in Program.cs inside .AddAzureClients.

     azureBuilder.AddQueueServiceClient(new Uri(azQueueConnection))
         .ConfigureOptions(opts =>
         {
             opts.MessageEncoding = QueueMessageEncoding.Base64; // Make sure any message I send is base64 encoded
         });
  4. Now go into ReviewEndpoint.cs and add logic to write a message to Azure storage queue after an image is uploaded.

    using Azure.Storage.Queues
    using Azure.Storage.Queues.Models
    
    // Inject QueueServiceClient queueServiceClient into the "UploadReviewImages" method and use it
    
    // Get a queue client for queue called "review-images"
    var queueClient = queueServiceClient.GetQueueClient("review-images");
    
    // Create the queue if it doesn't exist
    await queueClient.CreateIfNotExistsAsync(PublicAccessType.Blob);
    
    // Send a message to the queue
    await queueClient.SendMessageAsync($"{loggedInUser} {trustedFileNameForStorage}");

    Reference
    Reference

  5. Run the app, upload an image and go to your storage account in Azure Portal. Go to Storage Browser -> Queues. You'll see a new queue called review-images and you'll see a message there. The message body will be in the format: $"{loggedInUser} {trustedFileNameForStorage}".

Trigger a function by a new message written to storage queue

  1. Create a new Azure Functions project MunsonPickles.Functions.
    • Pick Queue trigger which means "hey run it off a queue".
    • Specify connection string name. For eg: I chose PickleStorageConnection.
    • Specify queue name which is review-images from previous step.
  2. Specify connection strings in local.settings.json.
    When function runs, it needs a storage where it stores its state. It uses a storage account for that, so specify connection string that points to our storage account as the value of AzureWebJobsStorage key. In real prod scenario, you should have a separate storage account dedicated to your Functions.
    {
      "IsEncrypted": false,
      "Values": {
         "AzureWebJobsStorage": "Put the connection string with Account Name and Account Key",
         "FUNCTIONS_WORKER_RUNTIME": "dotnet",
         "PickleStorageConnection": "Put the connection string with Account Name and Account Key"
        }
    }
  3. When a new message comes in to review-images storage queue, it'll trigger the below function which writes ReviewImageInfo data to reviewimagedata table.
     [StorageAccount("PickleStorageConnection")]
     public class QueueMonitor
     {
         [FunctionName("QueueMonitor")]
         [return: Table("reviewimagedata")] // If there's no reviewimagedata table present, it'll create it.
         public ReviewImageInfo Run(
             [QueueTrigger("review-images")]string message,
             ILogger log)
         {
             // split the message name based on the space
             var theParts = message.Split(' ');
    
             // user id is the first part
             var userId = theParts[0];
    
             // image name is the second part
             var imageName = theParts[1];
    
             log.LogInformation($"C# Queue trigger function processed: {message}");
    
             // write to table storage with information about the blob
             return new ReviewImageInfo {
                 BlobName = "{userId}/{imageName}",
                 PartitionKey = userId,
                 RowKey = Guid.NewGuid().ToString(),
                 UploadedDate = DateTime.Now,
                 ImageName = imageName
             };
         }
     }
    
     public class ReviewImageInfo
     {
         public string PartitionKey { get; set; }
         public string RowKey { get; set; }
         public string BlobName { get; set; }
         public string ImageName { get; set; } 
         public string UserId { get; set; }
         public DateTime UploadedDate { get; set; }
     }
  4. Run the function. Go to 'Storage Browser' -> Tables. You'll see a new table reviewimagedata that has been populated with ReviewImageInfo.

CI/CD with GitHub Actions

Github actions is

  1. CI/CD platform
  2. Integrated into your GitHub repository
  3. They are organized into workflows like build and deploy.
  4. 'Actions' run on events
    • Push
    • New Issue
    • Pull Request
    • etc.
  5. Defined in a YAML file

The components of GitHub actions

  1. Workflow (on a clean VM)
    • The overall process
    • Can have more than 1 workflow per repository
    • Executes on a "runner" or server
    • Server can be a windows, ubuntu or macos VM
  2. Event
    • Triggers a workflow to run
    • Many types (push, PR, issue)
  3. Job
    • A grouping of steps (actions) to execute
    • Each job will run inside its own virtual machine runner, or inside a container and has one or more steps.
  4. Action
    • Built-in task that performs a common complex task

Workflow:
image

Create an Action YAML file

Create a file in dotnet-on-azure/.github/workflows/deploy-api.yml

# Create an action to deploy MunsonPickles.API to Azure
name: Deploy MunsonPickles.API to Azure App Service
on:
# This gives an ability to run it manually
workflow_dispatch:
# Add environment variables
env:
AZURE_WEBAPP_NAME: "app-munson-web2-eastus-dev-001" # Copied from App service name
# Jobs has one or more steps that either run a script that you define or run an action
jobs:
build:
runs-on: ubuntu-latest # VMs are built new every single time
steps:
# Pull the code
- uses: actions/checkout@v3
# Setup .NET core on the ubuntu VM
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
# Nuget restore
- name: Install dependencies
run: dotnet restore MunsonPickles.API/MunsonPickles.API.csproj
# Build the app
- name: Build
run: dotnet build MunsonPickles.API/MunsonPickles.API.csproj -c Release --no-restore
# Publish the app
- name: Publish
run: dotnet publish MunsonPickles.API/MunsonPickles.API.csproj -c Release --no-build --output ./publish
# Deploy to Azure
- name: Deploy to Azure App Service
uses: azure/webapps-deploy@v3
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }}
publish-profile: ${{ secrets.API_PUBLISH_PROFILE }}
package: ./publish

Deploy to Azure App service using GitHub actions

  1. Go to your app service and download the publish profile
    image

  2. Add profile you downloaded from step 1 to your GitHub repo

    • Open the file you downloaded in step 1 using a text editor like notepad, copy it
    • Go to your GitHub repo -> Settings -> Secrets and Variables -> Actions
    • Under 'Repository secrets', click 'New repository secret', give it a name "API_PUBLISH_PROFILE" and paste the publish profile there
    image
  3. Add environment variables

    env:
      AZURE_WEBAPP_NAME: "app-munson-web2-eastus-dev-001" # Copied from App service name
  4. Run the workflow
    image

    It succeeds πŸŽ‰

    image
  5. Check the app running in Azure App Service by hitting an endpoint
    image

    Remember, it came from

    image

Congratulations on finishing dotnet-on-azure series! 🍾 πŸ™Œ πŸ˜ƒ

About

This repo contains notes I took while following .NET on Azure video series on YouTube.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published