diff --git a/SUPPORT.md b/SUPPORT.md index 291d4d43..ac25ef7d 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,25 +1,15 @@ -# TODO: The maintainer of this repo has not yet edited this file - -**REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? - -- **No CSS support:** Fill out this template with information about how to file issues and get help. -- **Yes CSS support:** Fill out an intake form at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). CSS will work with/help you to determine next steps. -- **Not sure?** Fill out an intake as though the answer were "Yes". CSS will help you decide. - -*Then remove this first heading from this SUPPORT.MD file before publishing your repo.* - # Support -## How to file issues and get help +## How to file issues and get help -This project uses GitHub Issues to track bugs and feature requests. Please search the existing -issues before filing new issues to avoid duplicates. For new issues, file your bug or +This project uses GitHub Issues to track bugs and feature requests. Please search the existing +issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new Issue. -For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE +For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER CHANNEL. WHERE WILL YOU HELP PEOPLE?**. -## Microsoft Support Policy +## Microsoft Support Policy Support for this **PROJECT or PRODUCT** is limited to the resources listed above. diff --git a/TasksTracker.Processor.Backend.Svc/TasksTracker.Processor.Backend.Svc.csproj b/TasksTracker.Processor.Backend.Svc/TasksTracker.Processor.Backend.Svc.csproj index 2d8fa840..ef2d618b 100644 --- a/TasksTracker.Processor.Backend.Svc/TasksTracker.Processor.Backend.Svc.csproj +++ b/TasksTracker.Processor.Backend.Svc/TasksTracker.Processor.Backend.Svc.csproj @@ -9,9 +9,9 @@ - + - + diff --git a/TasksTracker.TasksManager.Backend.Api/TasksTracker.TasksManager.Backend.Api.csproj b/TasksTracker.TasksManager.Backend.Api/TasksTracker.TasksManager.Backend.Api.csproj index 7dd65ce8..bdbf8255 100644 --- a/TasksTracker.TasksManager.Backend.Api/TasksTracker.TasksManager.Backend.Api.csproj +++ b/TasksTracker.TasksManager.Backend.Api/TasksTracker.TasksManager.Backend.Api.csproj @@ -9,9 +9,9 @@ - + - + diff --git a/TasksTracker.WebPortal.Frontend.Ui/TasksTracker.WebPortal.Frontend.Ui.csproj b/TasksTracker.WebPortal.Frontend.Ui/TasksTracker.WebPortal.Frontend.Ui.csproj index 0b98d023..3e544ed3 100644 --- a/TasksTracker.WebPortal.Frontend.Ui/TasksTracker.WebPortal.Frontend.Ui.csproj +++ b/TasksTracker.WebPortal.Frontend.Ui/TasksTracker.WebPortal.Frontend.Ui.csproj @@ -9,8 +9,8 @@ - + - + diff --git a/docs/aca/00-workshop-intro/4-prerequisites.md b/docs/aca/00-workshop-intro/4-prerequisites.md index c2088b0f..dcb0a236 100644 --- a/docs/aca/00-workshop-intro/4-prerequisites.md +++ b/docs/aca/00-workshop-intro/4-prerequisites.md @@ -1,5 +1,5 @@ --- -title: Prerequisites +title: Prerequisites parent: Workshop Introduction has_children: false nav_order: 4 @@ -7,12 +7,12 @@ nav_order: 4 ## Prerequisites -The workshop is divided into separate modules. Each module will guide you through building the solution code step-by-step. Ensure that you finish the modules in the right order as they have dependencies on each other. +The workshop is divided into separate modules. Each module will guide you through building the solution code step-by-step. Ensure that you finish the modules in the right order as they have dependencies on each other. Make sure you have your development environment set up and configured. 1. An Azure account with an active subscription - [Create an account for free](https://azure.microsoft.com/free/?ref=microsoft.com&utm_source=microsoft.com&utm_medium=docs&utm_campaign=visualstudio){target=_blank} -1. .NET 8 or a higher version - [Install](https://dotnet.microsoft.com/en-us/download){target=_blank} +1. .NET 8 or a higher version (we primarily focus on LTS versions) - [Install](https://dotnet.microsoft.com/en-us/download){target=_blank} 1. PowerShell 7.0 or higher version (For Windows Users only!) - [Install](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4#installing-the-msi-package){target=_blank} 1. Docker Desktop - [Install](https://docs.docker.com/desktop/install/windows-install/){target=_blank} 1. Visual Studio Code - [Install](https://code.visualstudio.com/){target=_blank} @@ -81,14 +81,7 @@ This workshop typically spans several days. As such, you may close your tools, e - Copy the [Set-Variables.ps1 script](../../aca/30-appendix/03-variables.md){target=_blank} into the newly-created `Set-Variables.ps1` file and save it. -- Perform an initial commit of the `Set-Variables.ps1` file. - - ```shell - git add .\Set-Variables.ps1 - git commit -m "Initialize Set-Variables.ps1" - ``` - -- Execute the script. You will do this repeatedly throughout the modules. The output of the script will inform you how many variables are written out. +- Execute the script. You will do this repeatedly throughout the modules. The output of the script will inform you how many variables are written out. As we have not yet defined any variables, the output will indicate that the script has exited. This is intentional and expected at this stage. ```shell .\Set-Variables.ps1 diff --git a/docs/aca/01-deploy-api-to-aca/Program.cs b/docs/aca/01-deploy-api-to-aca/Program-dotnet8.cs similarity index 100% rename from docs/aca/01-deploy-api-to-aca/Program.cs rename to docs/aca/01-deploy-api-to-aca/Program-dotnet8.cs diff --git a/docs/aca/01-deploy-api-to-aca/Program-dotnet9.cs b/docs/aca/01-deploy-api-to-aca/Program-dotnet9.cs new file mode 100644 index 00000000..4be47da9 --- /dev/null +++ b/docs/aca/01-deploy-api-to-aca/Program-dotnet9.cs @@ -0,0 +1,26 @@ +using TasksTracker.TasksManager.Backend.Api.Services; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddSingleton(); +builder.Services.AddControllers(); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/docs/aca/01-deploy-api-to-aca/global.json b/docs/aca/01-deploy-api-to-aca/global-dotnet8.json similarity index 67% rename from docs/aca/01-deploy-api-to-aca/global.json rename to docs/aca/01-deploy-api-to-aca/global-dotnet8.json index 7d3b56c9..49857e96 100644 --- a/docs/aca/01-deploy-api-to-aca/global.json +++ b/docs/aca/01-deploy-api-to-aca/global-dotnet8.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100", + "version": "8.0.403", "rollForward": "latestFeature" } } \ No newline at end of file diff --git a/docs/aca/01-deploy-api-to-aca/global-dotnet9.json b/docs/aca/01-deploy-api-to-aca/global-dotnet9.json new file mode 100644 index 00000000..9e34cdc3 --- /dev/null +++ b/docs/aca/01-deploy-api-to-aca/global-dotnet9.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "9.0.100-rc.2.24474.11", + "rollForward": "latestFeature" + } +} \ No newline at end of file diff --git a/docs/aca/01-deploy-api-to-aca/index.md b/docs/aca/01-deploy-api-to-aca/index.md index 584e089c..70daf661 100644 --- a/docs/aca/01-deploy-api-to-aca/index.md +++ b/docs/aca/01-deploy-api-to-aca/index.md @@ -17,30 +17,37 @@ In this module, we will accomplish three objectives: 1. Create the first microservice, `{{ apps.backend}}`, which serves as the API for our tasks. 1. Create the initial Azure infrastructure that we will need throughout this workshop. -1. Deploy the ``{{ apps.backend }}` container app to Azure. +1. Deploy the `{{ apps.backend }}` container app to Azure. ## Module Sections ### 1. Create the backend API project (Web API) -- From VS Code's *Terminal* tab, select *New Terminal* to open a (PowerShell) terminal in the project folder *TasksTracker.ContainerApps* (also referred to as *root*). +- If a terminal is not yet open, from VS Code's *Terminal* tab, select *New Terminal* to open a (PowerShell) terminal in the project folder *TasksTracker.ContainerApps* (also referred to as *root*). - We need to define the .NET version we will use throughout this workshop. In the terminal execute `dotnet --info`. Take note of the intalled .NET SDK versions and select the one with which you wish to proceed. - In the root folder create a new file and set the .NET SDK version from the above command: - === "global.json" - ```json hl_lines="3" - --8<-- "docs/aca/01-deploy-api-to-aca/global.json" - ``` + === ".NET 8" + + === "global.json" + ```json hl_lines="3" + --8<-- "docs/aca/01-deploy-api-to-aca/global-dotnet8.json" + ``` + + === ".NET 9" + + === "global.json" + ```json hl_lines="3" + --8<-- "docs/aca/01-deploy-api-to-aca/global-dotnet9.json" + ``` - Now we can initialize the backend API project. This will create and ASP.NET Web API project scaffolded with a single controller. !!! note "Controller-Based vs. Minimal APIs" - APIs can be created via the traditional, expanded controller-based structure with _Controllers_ and _Models_ folders, etc. or via the newer minimal APIs approach where controller actions are written inside _Program.cs_. The latter approach is preferential in a microservices project where the endpoints are overseeable and may easily be represented by a more compact view. - - As our workshop takes advantage of microservices, the use case for minimal APIs is given. However, in order to make the workshop a bit more demonstrable, we will, for now, stick with controller-based APIs. + APIs can be created via the traditional, expanded controller-based structure with _Controllers_ and _Models_ folders, etc. or via the newer minimal APIs approach where controller actions are written inside _Program.cs_. The latter approach is preferential in a microservices project where the endpoints are overseeable and may easily be represented by a more compact view. As our workshop takes advantage of microservices, the use case for minimal APIs is given. However, in order to make the workshop a bit more demonstrable, we will - for now - stick with controller-based APIs. === ".NET 8 or above" @@ -59,7 +66,7 @@ In this module, we will accomplish three objectives: --8<-- "docs/aca/01-deploy-api-to-aca/TaskModel.cs" ``` -- In the project root create a new folder named **Services** and add the two files below. Ensure to create it as a sibling to the *Models* folder. Add the Fake Tasks Manager service. This will be the interface of Tasks Manager service. In this module we will work with data in memory. Later on, we will implement a data store. +- In the project root create a new folder named **Services** as a sibling to the *Models* folder. Add the two files below to the *Services* folder. Add the Fake Tasks Manager service as we will work with data in memory in this module. Later on, we will implement a data store. === "ITasksManager.cs" ```csharp @@ -75,10 +82,19 @@ In this module, we will accomplish three objectives: - Now we need to register `FakeTasksManager` on project startup. Open file `#!csharp Program.cs` and register the newly created service by adding the highlighted lines from below snippet. Don't forget to include the required `using` statement for the task interface and class. - === "Program.cs" - ```csharp hl_lines="1 7" - --8<-- "docs/aca/01-deploy-api-to-aca/Program.cs" - ``` + === ".NET 8" + + === "Program.cs" + ```csharp hl_lines="1 7" + --8<-- "docs/aca/01-deploy-api-to-aca/Program-dotnet8.cs" + ``` + + === ".NET 9" + + === "Program.cs" + ```csharp hl_lines="1 7" + --8<-- "docs/aca/01-deploy-api-to-aca/Program-dotnet9.cs" + ``` - Inside the **Controllers** folder create a new controller with the below filename. We need to create API endpoints to manage tasks. @@ -118,7 +134,7 @@ We will be using Azure CLI to deploy the Web API Backend to ACA as shown in the az extension add --upgrade --name application-insights # Log in to Azure - az login + az login ``` - You may be able to use the queried Azure subscription ID or you may need to set it manually depending on your setup. @@ -232,11 +248,14 @@ We will be using Azure CLI to deploy the Web API Backend to ACA as shown in the --location $LOCATION ` --app $APPINSIGHTS_NAME ` --workspace $WORKSPACE_NAME - + # Get Application Insights Instrumentation Key $APPINSIGHTS_INSTRUMENTATIONKEY=($(az monitor app-insights component show ` --resource-group $RESOURCE_GROUP ` - --app $APPINSIGHTS_NAME ) | ConvertFrom-Json).instrumentationKey + --app $APPINSIGHTS_NAME ` + --output json) | ConvertFrom-Json).instrumentationKey + + echo $APPINSIGHTS_INSTRUMENTATIONKEY ``` #### 2.4 Azure Container Infrastructure @@ -270,12 +289,12 @@ We will be using Azure CLI to deploy the Web API Backend to ACA as shown in the ``` !!! note - We are not creating an `internal-only` Azure Container App Environment. This means that the static IP will be a public IP, and container apps, by default, will be publicly available on the internet. + We are not creating an `internal-only` Azure Container App Environment. This means that the static IP will be a public IP, and container apps, by default, will be publicly available on the internet. While this is not advised in a production workload, it is suitable for the workshop to keep the architecture confined to Azure Container Apps. -??? tip "Want to learn what above command does?" +??? tip "Want to learn what the above command does?" - It creates an ACA environment and associates it with the Log Analytics workspace created in the previous step. - - We are setting the `--dapr-instrumentation-key` value to the instrumentation key of the Application Insights instance. This will come handy when we introduce Dapr in later modules and show how the distributed tracing between microservices/container apps are captured and visualized in Application Insights. + - We are setting the `--dapr-instrumentation-key` value to the instrumentation key of the Application Insights instance. This will come handy when we introduce Dapr in later modules and show how the distributed tracing between microservices/container apps are captured and visualized in Application Insights. > ***NOTE:*** You can set the `--dapr-instrumentation-key` after you create the ACA environment but this is not possible via the AZ CLI right now. There is an [open issue](https://github.com/microsoft/azure-container-apps/issues/293){target=_blank} which is being tracked by the product group. @@ -316,7 +335,7 @@ We will be using Azure CLI to deploy the Web API Backend to ACA as shown in the echo "https://$fqdn/api/tasks/?createdby=tjoudeh@bitoftech.net" ``` -??? tip "Want to learn what above command does?" +??? tip "Want to learn what the above command does?" - Ingress param is set to `external` which means that this container app (Web API) project will be accessible from the public internet. When Ingress is set to `Internal` or `External` it will be assigned a fully qualified domain name (FQDN). Important notes about IP addresses and domain names can be found [here](https://learn.microsoft.com/en-us/azure/container-apps/ingress?tabs=bash#ip-addresses-and-domain-names){target=_blank}. - The target port param is set to 80, this is the port our Web API container listens to for incoming requests. - We didn't specify the ACR registry username and password, `az containerapp create` command was able to look up ACR username and password and add them as a secret under the created Azure container app for future container updates. @@ -332,11 +351,11 @@ We will be using Azure CLI to deploy the Web API Backend to ACA as shown in the !!! success To test the backend api service, either click on the URL output by the last command or copy the FQDN (Application URL) of the Azure container app named `tasksmanager-backend-api`, then issue a `GET` request similar to this one: `https://tasksmanager-backend-api..eastus.azurecontainerapps.io/api/tasks/?createdby=tjoudeh@bitoftech.net` and you should receive an array of the 10 tasks similar to the below image. - Note that the specific query string matters as you may otherwise get an empty result back. + Note that the specific query string matters as you may otherwise get an empty result back. !!! tip You can find your Azure container app application url on the [Azure portal](https://portal.azure.com){target=_blank} overview tab. - + ![Web API Response](../../assets/images/01-deploy-api-to-aca/Response.jpg) --8<-- "snippets/update-variables.md" diff --git a/docs/aca/02-aca-comm/Program.cs b/docs/aca/02-aca-comm/Program-dotnet8.cs similarity index 100% rename from docs/aca/02-aca-comm/Program.cs rename to docs/aca/02-aca-comm/Program-dotnet8.cs diff --git a/docs/aca/02-aca-comm/Program-dotnet9.cs b/docs/aca/02-aca-comm/Program-dotnet9.cs new file mode 100644 index 00000000..8c436871 --- /dev/null +++ b/docs/aca/02-aca-comm/Program-dotnet9.cs @@ -0,0 +1,37 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddRazorPages(); + +builder.Services.AddHttpClient("BackEndApiExternal", httpClient => +{ + var backendApiBaseUrlExternalHttp = builder.Configuration.GetValue("BackendApiConfig:BaseUrlExternalHttp"); + + if (!string.IsNullOrEmpty(backendApiBaseUrlExternalHttp)) { + httpClient.BaseAddress = new Uri(backendApiBaseUrlExternalHttp); + } else { + throw new("BackendApiConfig:BaseUrlExternalHttp is not defined in App Settings."); + } +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); + +app.UseRouting(); + +app.UseAuthorization(); + +app.MapStaticAssets(); +app.MapRazorPages() + .WithStaticAssets(); + +app.Run(); diff --git a/docs/aca/02-aca-comm/index.md b/docs/aca/02-aca-comm/index.md index 46c866e7..7184d9ad 100644 --- a/docs/aca/02-aca-comm/index.md +++ b/docs/aca/02-aca-comm/index.md @@ -82,12 +82,21 @@ By looking at the cshtml content notice that the page is expecting a query strin !!! tip "What does this code do?" The code added is similar to the create operation. The Edit page accepts the TaskId as a Guid, loads the task, and then updates the task by sending an HTTP PUT operation. -- Now we will inject an HTTP client factory and define environment variables. To do so we will register the HttpClientFactory named `BackEndApiExternal` to make it available for injection in controllers. Open the `Program.cs` file and update it with highlighted code below. Your file may be flattened rather than indented and not contain some of the below elements. Don't worry. Just place the highlighted lines in the right spot: +- Now we will inject an HTTP client factory and define environment variables. To do so, we will register the HttpClientFactory named `BackEndApiExternal` to make it available for injection in controllers. Open the `Program.cs` file and update it with highlighted code below. Your file may be flattened rather than indented and not contain some of the below elements. Don't worry. Just place the highlighted lines in the right spot: - === "Program.cs" - ```csharp hl_lines="6-15" - --8<-- "docs/aca/02-aca-comm/Program.cs" - ``` + === ".NET 8" + + === "Program.cs" + ```csharp hl_lines="6-15" + --8<-- "docs/aca/02-aca-comm/Program-dotnet8.cs" + ``` + + === ".NET 9" + + === "Program.cs" + ```csharp hl_lines="6-15" + --8<-- "docs/aca/02-aca-comm/Program-dotnet9.cs" + ``` - Next, we will add a new environment variable named `BackendApiConfig:BaseUrlExternalHttp` into `appsettings.json` file. This variable will contain the Base URL for the backend API deployed in the previous module to ACA. Later on in the workshop, we will see how we can set the environment variable once we deploy it to ACA. Use the output from this script as the `BaseUrlExternalHttp` value. @@ -165,7 +174,7 @@ By looking at the cshtml content notice that the page is expecting a query strin --output tsv) $FRONTEND_UI_BASE_URL="https://$fqdn" - + echo "See the frontend web app at this URL:" echo $FRONTEND_UI_BASE_URL ``` @@ -174,7 +183,7 @@ By looking at the cshtml content notice that the page is expecting a query strin Notice how we used the property `env-vars` to set the value of the environment variable named `BackendApiConfig_BaseUrlExternalHttp` which we added in the AppSettings.json file. You can set multiple environment variables at the same time by using a space between each variable. The `ingress` property is set to `external` as the Web frontend App will be exposed to the public internet for users. -After your run the command, copy the FQDN (Application URL) of the Azure container app named `tasksmanager-frontend-webapp` and open it in your browser, and you should be able to browse the frontend web app and manage your tasks. +After you run the command, copy the FQDN (Application URL) of the Azure container app named `tasksmanager-frontend-webapp` and open it in your browser, and you should be able to browse the frontend web app and manage your tasks. ### 3. Update Backend Web API Container App Ingress property @@ -201,7 +210,7 @@ So far the Frontend App is sending HTTP requests to the publicly exposed Web API When you do this change, the FQDN (Application URL) will change, and it will be similar to the one shown below. Notice how there is an `Internal` part of the URL. `https://tasksmanager-backend-api.internal.[Environment unique identifier].eastus.azurecontainerapps.io/api/tasks/` If you try to invoke the URL directly it will return *403 - Forbidden* as this internal Url can only be accessed successfully from container apps within the container environment. This means that while the API is not accessible, it still provides a clue that something exists at that URL. Ideally, we would want to see a *404 - Not Found*. However, recall from module 1 that we did *not* set `internal-only` for simplicity's sake of the workshop. In a production scenario, this should be done with completely private networking to not reveal anything. - + The FQDN consists of multiple parts. For example, all our Container Apps will be under a specific Environment unique identifier (e.g. `agreeablestone-8c14c04c`) and the Container App will vary based on the name provided, check the image below for a better explanation. ![Container Apps FQDN](../../assets/images/02-aca-comm/container-apps-fqdn.jpg) diff --git a/docs/aca/03-aca-dapr-integration/Frontend.Ui-dotnet8.csproj b/docs/aca/03-aca-dapr-integration/Frontend.Ui-dotnet8.csproj index e5f16afe..105c5d9e 100644 --- a/docs/aca/03-aca-dapr-integration/Frontend.Ui-dotnet8.csproj +++ b/docs/aca/03-aca-dapr-integration/Frontend.Ui-dotnet8.csproj @@ -7,7 +7,7 @@ - + \ No newline at end of file diff --git a/docs/aca/03-aca-dapr-integration/Frontend.Ui-dotnet7.csproj b/docs/aca/03-aca-dapr-integration/Frontend.Ui-dotnet9.csproj similarity index 64% rename from docs/aca/03-aca-dapr-integration/Frontend.Ui-dotnet7.csproj rename to docs/aca/03-aca-dapr-integration/Frontend.Ui-dotnet9.csproj index 6ada4e2a..2ef6a4ff 100644 --- a/docs/aca/03-aca-dapr-integration/Frontend.Ui-dotnet7.csproj +++ b/docs/aca/03-aca-dapr-integration/Frontend.Ui-dotnet9.csproj @@ -1,13 +1,13 @@ - net7.0 + net9.0 enable enable - + \ No newline at end of file diff --git a/docs/aca/03-aca-dapr-integration/Program.cs b/docs/aca/03-aca-dapr-integration/Program-dotnet8.cs similarity index 100% rename from docs/aca/03-aca-dapr-integration/Program.cs rename to docs/aca/03-aca-dapr-integration/Program-dotnet8.cs diff --git a/docs/aca/03-aca-dapr-integration/Program-dotnet9.cs b/docs/aca/03-aca-dapr-integration/Program-dotnet9.cs new file mode 100644 index 00000000..9f69986d --- /dev/null +++ b/docs/aca/03-aca-dapr-integration/Program-dotnet9.cs @@ -0,0 +1,39 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddRazorPages(); + +builder.Services.AddDaprClient(); + +builder.Services.AddHttpClient("BackEndApiExternal", httpClient => +{ + var backendApiBaseUrlExternalHttp = builder.Configuration.GetValue("BackendApiConfig:BaseUrlExternalHttp"); + + if (!string.IsNullOrEmpty(backendApiBaseUrlExternalHttp)) { + httpClient.BaseAddress = new Uri(backendApiBaseUrlExternalHttp); + } else { + throw new("BackendApiConfig:BaseUrlExternalHttp is not defined in App Settings."); + } +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); + +app.UseRouting(); + +app.UseAuthorization(); + +app.MapStaticAssets(); +app.MapRazorPages() + .WithStaticAssets(); + +app.Run(); diff --git a/docs/aca/03-aca-dapr-integration/index.md b/docs/aca/03-aca-dapr-integration/index.md index 6fcf48c3..f87eb8a6 100644 --- a/docs/aca/03-aca-dapr-integration/index.md +++ b/docs/aca/03-aca-dapr-integration/index.md @@ -127,27 +127,45 @@ You are now ready to run the applications locally using the Dapr sidecar in a se - Install Dapr SDK for .NET Core in the Frontend Web APP, so we can use the service discovery and service invocation offered by Dapr Sidecar. To do so, add below nuget package to the project. - === ".NET 8 or above" + === ".NET 8" + === "TasksTracker.WebPortal.Frontend.Ui.csproj" ```xml hl_lines="9-11" --8<-- "docs/aca/03-aca-dapr-integration/Frontend.Ui-dotnet8.csproj" ``` + === ".NET 9" + + === "TasksTracker.WebPortal.Frontend.Ui.csproj" + + ```xml hl_lines="9-11" + --8<-- "docs/aca/03-aca-dapr-integration/Frontend.Ui-dotnet9.csproj" + ``` + - Next, open the file `Programs.cs` of the Frontend Web App and register the DaprClient as the highlighted below. - === ".NET 8 or above" + === ".NET 8" + === "Program.cs" ```csharp hl_lines="6" - --8<-- "docs/aca/03-aca-dapr-integration/Program.cs" + --8<-- "docs/aca/03-aca-dapr-integration/Program-dotnet8.cs" + ``` + + === ".NET 9" + + === "Program.cs" + + ```csharp hl_lines="6" + --8<-- "docs/aca/03-aca-dapr-integration/Program-dotnet9.cs" ``` - Now, we will inject the DaprClient into the `.cshtml` pages to use the method `InvokeMethodAsync` (second approach). Update files under folder **Pages\Tasks** and use the code below for different files. === "Index.cshtml.cs" - ```csharp + ```csharp --8<-- "docs/aca/03-aca-dapr-integration/Tasks.Index.cshtml.cs" ``` @@ -161,7 +179,7 @@ You are now ready to run the applications locally using the Dapr sidecar in a se ```csharp --8<-- "docs/aca/03-aca-dapr-integration/Edit.cshtml.cs" - ``` + ``` ???+ tip Notice how we are not using the `HttpClientFactory` anymore and how we were able from the Frontend Dapr Sidecar to invoke backend API Sidecar using the method `InvokeMethodAsync` which accepts the Dapr **remote App ID** for the Backend API `tasksmanager-backend-api` and it will be able to discover the URL and invoke the method based on the specified input params. diff --git a/docs/aca/04-aca-dapr-stateapi/Backend.Api-dotnet8.csproj b/docs/aca/04-aca-dapr-stateapi/Backend.Api-dotnet8.csproj index 36139a1c..d02cf285 100644 --- a/docs/aca/04-aca-dapr-stateapi/Backend.Api-dotnet8.csproj +++ b/docs/aca/04-aca-dapr-stateapi/Backend.Api-dotnet8.csproj @@ -8,7 +8,7 @@ - + diff --git a/docs/aca/04-aca-dapr-stateapi/Backend.Api-dotnet9.csproj b/docs/aca/04-aca-dapr-stateapi/Backend.Api-dotnet9.csproj new file mode 100644 index 00000000..225a896b --- /dev/null +++ b/docs/aca/04-aca-dapr-stateapi/Backend.Api-dotnet9.csproj @@ -0,0 +1,14 @@ + + + + net9.0 + enable + enable + + + + + + + + diff --git a/docs/aca/04-aca-dapr-stateapi/Program.cs b/docs/aca/04-aca-dapr-stateapi/Program-dotnet8.cs similarity index 100% rename from docs/aca/04-aca-dapr-stateapi/Program.cs rename to docs/aca/04-aca-dapr-stateapi/Program-dotnet8.cs diff --git a/docs/aca/04-aca-dapr-stateapi/Program-dotnet9.cs b/docs/aca/04-aca-dapr-stateapi/Program-dotnet9.cs new file mode 100644 index 00000000..c6e12103 --- /dev/null +++ b/docs/aca/04-aca-dapr-stateapi/Program-dotnet9.cs @@ -0,0 +1,28 @@ +using TasksTracker.TasksManager.Backend.Api.Services; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddDaprClient(); +builder.Services.AddSingleton(); +//builder.Services.AddSingleton(); +builder.Services.AddControllers(); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/docs/aca/04-aca-dapr-stateapi/TasksStoreManager.cs b/docs/aca/04-aca-dapr-stateapi/TasksStoreManager.cs index cc85bf19..8f467a43 100644 --- a/docs/aca/04-aca-dapr-stateapi/TasksStoreManager.cs +++ b/docs/aca/04-aca-dapr-stateapi/TasksStoreManager.cs @@ -56,7 +56,11 @@ public async Task> GetTasksByCreator(string createdBy) var queryResponse = await _daprClient.QueryStateAsync(STORE_NAME, query); - var tasksList = queryResponse.Results.Select(q => q.Data).OrderByDescending(o=>o.TaskCreatedOn); + var tasksList = queryResponse.Results + .Where(q => q.Data != null) // filter null data + .Select(q => q.Data!) + .OrderByDescending(o=>o.TaskCreatedOn); + return tasksList.ToList(); } diff --git a/docs/aca/04-aca-dapr-stateapi/index.md b/docs/aca/04-aca-dapr-stateapi/index.md index 62b6232c..750c66b6 100644 --- a/docs/aca/04-aca-dapr-stateapi/index.md +++ b/docs/aca/04-aca-dapr-stateapi/index.md @@ -122,12 +122,21 @@ Whereas in the previous section we demonstrated using Dapr State Store without c Similar to what we have done in the Frontend Web App, we need to use Dapr Client SDK to manage the state store. Update the below file with the added Dapr package reference: === ".NET 8" + === "TasksTracker.TasksManager.Backend.Api.csproj" ```xml hl_lines="11" --8<-- "docs/aca/04-aca-dapr-stateapi/Backend.Api-dotnet8.csproj" ``` +=== ".NET 9" + + === "TasksTracker.TasksManager.Backend.Api.csproj" + + ```xml hl_lines="10" + --8<-- "docs/aca/04-aca-dapr-stateapi/Backend.Api-dotnet9.csproj" + ``` + #### 2.2 Create a New Concrete Implementation to Manage Tasks Persistence As you recall from the previous module, we were storing the tasks in memory. Now we need to store them in Redis and, later on, Azure Cosmos DB. The key thing to keep in mind here is that switching from Redis to Azure Cosmos DB won't require changing the code below which is a huge advantage of using Dapr. @@ -155,11 +164,21 @@ Now we need to register the new service named `TasksStoreManager` and `DaprClien !!! note Do not forget to comment out the registration of the `FakeTasksManager` service as we don't want to store tasks in memory anymore. -=== "Program.cs" +=== ".NET 8" + + === "Program.cs" - ```csharp hl_lines="7-9" - --8<-- "docs/aca/04-aca-dapr-stateapi/Program.cs" - ``` + ```csharp hl_lines="7-9" + --8<-- "docs/aca/04-aca-dapr-stateapi/Program-dotnet8.cs" + ``` + +=== ".NET 9" + + === "Program.cs" + + ```csharp hl_lines="7-9" + --8<-- "docs/aca/04-aca-dapr-stateapi/Program-dotnet9.cs" + ``` - Let's verify that the Dapr dependency is restored properly and that the project compiles. From VS Code Terminal tab, open developer command prompt or PowerShell terminal and navigate to the parent directory which hosts the `.csproj` project folder and build the project. @@ -185,7 +204,7 @@ You need to set the variable name of the `$COSMOS_DB_ACCOUNT` to a unique name a ```shell $COSMOS_DB_ACCOUNT="cosmos-tasks-tracker-state-store-$RANDOM_STRING" $COSMOS_DB_DBNAME="tasksmanagerdb" -$COSMOS_DB_CONTAINER="taskscollection" +$COSMOS_DB_CONTAINER="taskscollection" # Check if Cosmos account name already exists globally $result = az cosmosdb check-name-exists ` @@ -199,13 +218,13 @@ if ($result -eq "false") { az cosmosdb create ` --name $COSMOS_DB_ACCOUNT ` --resource-group $RESOURCE_GROUP - + # Create a SQL API database az cosmosdb sql database create ` --name $COSMOS_DB_DBNAME ` --resource-group $RESOURCE_GROUP ` --account-name $COSMOS_DB_ACCOUNT - + # Create a SQL API container az cosmosdb sql container create ` --name $COSMOS_DB_CONTAINER ` @@ -214,13 +233,13 @@ if ($result -eq "false") { --database-name $COSMOS_DB_DBNAME ` --partition-key-path "/id" ` --throughput 400 - + $COSMOS_DB_ENDPOINT=(az cosmosdb show ` --name $COSMOS_DB_ACCOUNT ` --resource-group $RESOURCE_GROUP ` --query documentEndpoint ` --output tsv) - + echo "Cosmos DB Endpoint: " echo $COSMOS_DB_ENDPOINT } @@ -264,8 +283,8 @@ To add the component file state store, add a new folder named **components** und !!! info You need to replace the **masterKey** value with your Cosmos Account key. Remember this is only needed for local development debugging, we will not be using the masterKey when we deploy to ACA. - Replace the **url** value with the URI value of your Azure Cosmos DB account. You can get that from the [Azure portal](https://portal.azure.com){target=_blank} by navigating to the Azure Cosmos DB account overview page and get the uri value from there. - Basically the uri should have the following structure. [https://COSMOS_DB_ACCOUNT.documents.azure.com:443/](https://COSMOS_DB_ACCOUNT.documents.azure.com:443/). + Replace the **url** value with the URI value of your Azure Cosmos DB account. You can get that from the [Azure portal](https://portal.azure.com){target=_blank} by navigating to the Azure Cosmos DB account overview page and get the uri value from there. + Basically, the uri should have the following structure: [https://COSMOS_DB_ACCOUNT.documents.azure.com:443/](https://COSMOS_DB_ACCOUNT.documents.azure.com:443/). === "dapr-statestore-cosmos.yaml" @@ -283,6 +302,7 @@ To add the component file state store, add a new folder named **components** und Dapr component scopes correspond to the Dapr application ID of a container app, not the container app name. Now you should be ready to launch both applications and start doing CRUD operations from the Frontend Web App including querying the store. All your data will be stored in Cosmos DB Database you just provisioned. +**TODO: Add instructions to run both again.** If you have been running the different microservices using the [debug and launch Dapr applications in VSCode](../30-appendix/01-run-debug-dapr-app-vscode.md) then remember to uncomment the following line inside tasks.json file. This will instruct dapr to load the local projects components located at **./components** instead of the global components' folder. @@ -373,7 +393,7 @@ Run the command below to associate the container app `system-assigned` identity Remember to replace the placeholders with your own values: ```shell -$ROLE_ID = "00000000-0000-0000-0000-000000000002" #"Cosmos DB Built-in Data Contributor" +$ROLE_ID = "00000000-0000-0000-0000-000000000002" #"Cosmos DB Built-in Data Contributor" az cosmosdb sql role assignment create ` --resource-group $RESOURCE_GROUP ` @@ -469,19 +489,19 @@ az containerapp dapr enable ` The last thing we need to do here is to update both container apps and deploy the new images from ACR. To do so we need to run the commands found below. ```shell -# Update Frontend web app container app and create a new revision +# Update Frontend web app container app and create a new revision az containerapp update ` --name $FRONTEND_WEBAPP_NAME ` --resource-group $RESOURCE_GROUP ` --revision-suffix v$TODAY -# Update Backend API App container app and create a new revision +# Update Backend API App container app and create a new revision az containerapp update ` --name $BACKEND_API_NAME ` --resource-group $RESOURCE_GROUP ` --revision-suffix v$TODAY-1 -echo "Azure Frontend UI URL:" +echo "Azure Frontend UI URL:" echo $FRONTEND_UI_BASE_URL ``` diff --git a/docs/aca/05-aca-dapr-pubsubapi/Backend.Svc-dotnet8.csproj b/docs/aca/05-aca-dapr-pubsubapi/Backend.Svc-dotnet8.csproj index 529c9b38..29092f18 100644 --- a/docs/aca/05-aca-dapr-pubsubapi/Backend.Svc-dotnet8.csproj +++ b/docs/aca/05-aca-dapr-pubsubapi/Backend.Svc-dotnet8.csproj @@ -1,5 +1,5 @@ - + net8.0 enable @@ -8,7 +8,7 @@ - + diff --git a/docs/aca/05-aca-dapr-pubsubapi/Backend.Svc-dotnet9.csproj b/docs/aca/05-aca-dapr-pubsubapi/Backend.Svc-dotnet9.csproj new file mode 100644 index 00000000..225a896b --- /dev/null +++ b/docs/aca/05-aca-dapr-pubsubapi/Backend.Svc-dotnet9.csproj @@ -0,0 +1,14 @@ + + + + net9.0 + enable + enable + + + + + + + + diff --git a/docs/aca/05-aca-dapr-pubsubapi/Program.cs b/docs/aca/05-aca-dapr-pubsubapi/Program-dotnet8.cs similarity index 100% rename from docs/aca/05-aca-dapr-pubsubapi/Program.cs rename to docs/aca/05-aca-dapr-pubsubapi/Program-dotnet8.cs diff --git a/docs/aca/05-aca-dapr-pubsubapi/Program-dotnet9.cs b/docs/aca/05-aca-dapr-pubsubapi/Program-dotnet9.cs new file mode 100644 index 00000000..f06ded28 --- /dev/null +++ b/docs/aca/05-aca-dapr-pubsubapi/Program-dotnet9.cs @@ -0,0 +1,28 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers().AddDapr(); +builder.Services.AddControllers(); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.UseCloudEvents(); + +app.MapControllers(); + +app.MapSubscribeHandler(); + +app.Run(); diff --git a/docs/aca/05-aca-dapr-pubsubapi/TasksStoreManager.cs b/docs/aca/05-aca-dapr-pubsubapi/TasksStoreManager.cs index eeeeb353..c429dace 100644 --- a/docs/aca/05-aca-dapr-pubsubapi/TasksStoreManager.cs +++ b/docs/aca/05-aca-dapr-pubsubapi/TasksStoreManager.cs @@ -27,7 +27,7 @@ public async Task CreateNewTask(string taskName, string createdBy, string TaskDueDate = dueDate, TaskAssignedTo = assignedTo, }; - + _logger.LogInformation("Save a new task with name: '{0}' to state store", taskModel.TaskName); await _daprClient.SaveStateAsync(STORE_NAME, taskModel.TaskId.ToString(), taskModel); await PublishTaskSavedEvent(taskModel); @@ -57,7 +57,11 @@ public async Task> GetTasksByCreator(string createdBy) var queryResponse = await _daprClient.QueryStateAsync(STORE_NAME, query); - var tasksList = queryResponse.Results.Select(q => q.Data).OrderByDescending(o=>o.TaskCreatedOn); + var tasksList = queryResponse.Results + .Where(q => q.Data != null) // filter null data + .Select(q => q.Data!) + .OrderByDescending(o=>o.TaskCreatedOn); + return tasksList.ToList(); } @@ -101,4 +105,4 @@ private async Task PublishTaskSavedEvent(TaskModel taskModel) await _daprClient.PublishEventAsync("dapr-pubsub-servicebus", "tasksavedtopic", taskModel); } } -} +} \ No newline at end of file diff --git a/docs/aca/05-aca-dapr-pubsubapi/index.md b/docs/aca/05-aca-dapr-pubsubapi/index.md index 033eaca0..d60b33eb 100644 --- a/docs/aca/05-aca-dapr-pubsubapi/index.md +++ b/docs/aca/05-aca-dapr-pubsubapi/index.md @@ -94,8 +94,8 @@ Now we will add a new ASP.NET Core Web API project named **TasksTracker.Processo !!! note "Controller-Based vs. Minimal APIs" - APIs can be created via the traditional, expanded controller-based structure with _Controllers_ and _Models_ folders, etc. or via the newer minimal APIs approach where controller actions are written inside _Program.cs_. The latter approach is preferential in a microservices project where the endpoints are overseeable and may easily be represented by a more compact view. - + APIs can be created via the traditional, expanded controller-based structure with _Controllers_ and _Models_ folders, etc. or via the newer minimal APIs approach where controller actions are written inside _Program.cs_. The latter approach is preferential in a microservices project where the endpoints are overseeable and may easily be represented by a more compact view. + As our workshop takes advantage of microservices, the use case for minimal APIs is given. However, in order to make the workshop a bit more demonstrable, we will, for now, stick with controller-based APIs. === ".NET 8 or above" @@ -125,7 +125,7 @@ Now we will add the model which will be used to deserialize the published messag Now we will install Dapr SDK to be able to subscribe to the service broker topic in a programmatic way. Add the highlighted NuGet package to the file shown below: -=== ".NET 8 or above" +=== ".NET 8" === "TasksTracker.Processor.Backend.Svc.csproj" @@ -133,6 +133,14 @@ Now we will install Dapr SDK to be able to subscribe to the service broker topic --8<-- "docs/aca/05-aca-dapr-pubsubapi/Backend.Svc-dotnet8.csproj" ``` +=== ".NET 9" + + === "TasksTracker.Processor.Backend.Svc.csproj" + + ```xml hl_lines="10" + --8<-- "docs/aca/05-aca-dapr-pubsubapi/Backend.Svc-dotnet9.csproj" + ``` + #### 2.4 Create an API Endpoint for the Consumer to Subscribe to the Topic Now we will add an endpoint that will be responsible to subscribe to the topic in the message broker we are interested in. This endpoint will start receiving the message published from the Backend API producer. Add a new controller under **Controllers** folder. @@ -146,14 +154,14 @@ Now we will add an endpoint that will be responsible to subscribe to the topic i ??? tip "Curious about what we have done so far?" - We have added an action method named `TaskSaved` which can be accessed on the route `api/tasksnotifier/tasksaved` - - We have attributed this action method with the attribute `Dapr.Topic` which accepts the Dapr Pub/Sub component to target as the first argument, + - We have attributed this action method with the attribute `Dapr.Topic` which accepts the Dapr Pub/Sub component to target as the first argument, and the second argument is the topic to subscribe to, which in our case is `tasksavedtopic`. - The action method expects to receive a `TaskModel` object. - - Now once the message is received by this endpoint, we can start out the business logic to trigger sending an email (more about this next) and then return `200 OK` response to indicate that the consumer + - Now once the message is received by this endpoint, we can start out the business logic to trigger sending an email (more about this next) and then return `200 OK` response to indicate that the consumer processed the message successfully and the broker can delete this message. - - If anything went wrong during sending the email (i.e. Email service not responding) and we want to retry processing this message at a later time, we return `400 Bad Request`, + - If anything went wrong during sending the email (i.e. Email service not responding) and we want to retry processing this message at a later time, we return `400 Bad Request`, which will inform the message broker that the message needs to be retired based on the configuration in the message broker. - - If we need to drop the message as we are aware it will not be processed even after retries (i.e Email to is not formatted correctly) we return a `404 Not Found` response. + - If we need to drop the message as we are aware it will not be processed even after retries (i.e Email to is not formatted correctly) we return a `404 Not Found` response. This will tell the message broker to drop the message and move it to dead-letter or poison queue. You may be wondering how the consumer was able to identify what are the subscriptions available and on which route they can be found at. @@ -183,12 +191,20 @@ In our case, a sample response will be as follows: Update below file in **TasksTracker.Processor.Backend.Svc** project. -=== ".NET 8 or above" +=== ".NET 8" === "Program.cs" ```csharp hl_lines="5 23 27" - --8<-- "docs/aca/05-aca-dapr-pubsubapi/Program.cs" + --8<-- "docs/aca/05-aca-dapr-pubsubapi/Program-dotnet8.cs" + ``` + +=== ".NET 9" + + === "Program.cs" + + ```csharp hl_lines="5 22 26" + --8<-- "docs/aca/05-aca-dapr-pubsubapi/Program-dotnet9.cs" ``` - Let's verify that the Dapr dependency is restored properly and that the project compiles. From VS Code Terminal tab, open developer command prompt or PowerShell terminal and navigate to the parent directory which hosts the `.csproj` project folder and build the project. @@ -200,13 +216,13 @@ Update below file in **TasksTracker.Processor.Backend.Svc** project. ??? tip "Curious about the code above?" - - On line `builder.Services.AddControllers().AddDapr();`, the extension method `AddDapr` registers the necessary services to integrate Dapr into the MVC pipeline. + - On line `builder.Services.AddControllers().AddDapr();`, the extension method `AddDapr` registers the necessary services to integrate Dapr into the MVC pipeline. It also registers a `DaprClient` instance into the dependency injection container, which then can be injected anywhere into your service. We will see how we are injecting DaprClient in the controller constructor later on. - - On line `app.UseCloudEvents();`, the extension method `UseCloudEvents` adds CloudEvents middleware into the ASP.NET Core middleware pipeline. - This middleware will unwrap requests that use the CloudEvents structured format, so the receiving method can read the event payload directly. + - On line `app.UseCloudEvents();`, the extension method `UseCloudEvents` adds CloudEvents middleware into the ASP.NET Core middleware pipeline. + This middleware will unwrap requests that use the CloudEvents structured format, so the receiving method can read the event payload directly. You can read more about [CloudEvents](https://cloudevents.io/){target=_blank} here which includes specs for describing event data in a common and standard way. - - On line `app.MapSubscribeHandler();`, we make the endpoint `http://localhost:/dapr/subscribe` available for the consumer so it responds and returns available subscriptions. + - On line `app.MapSubscribeHandler();`, we make the endpoint `http://localhost:/dapr/subscribe` available for the consumer so it responds and returns available subscriptions. When this endpoint is called, it will automatically find all WebAPI action methods decorated with the `Dapr.Topic` attribute and instruct Dapr to create subscriptions for them. With all those bits in place, we are ready to run the publisher service `Backend API` and the consumer service `Backend Background Service` and test Pub/Sub pattern end to end. @@ -235,7 +251,7 @@ Now let's try to publish a message by sending a **POST** request to [http://loca POST /v1.0/publish/taskspubsub/tasksavedtopic HTTP/1.1 Host: localhost:3500 Content-Type: application/json - + { "taskId": "fbc55b2c-d9fa-405e-aec8-22e53f4306dd", "taskName": "Testing Pub Sub Publisher", @@ -262,15 +278,15 @@ If you have followed the steps in the [appendix](../30-appendix/01-run-debug-dap ??? example "Click to expand the files to update" You can use the below files to update the existing ones. - + === "tasks.json" - + ```json --8<-- "docs/aca/05-aca-dapr-pubsubapi/tasks.json" ``` === "launch.json" - + ```json --8<-- "docs/aca/05-aca-dapr-pubsubapi/launch.json" ``` @@ -283,7 +299,7 @@ To do this, update below file under the project **TasksTracker.TasksManager.Back === "TasksStoreManager.cs" - ```csharp hl_lines="33 88-91 97-102" + ```csharp hl_lines="33 92-95 101-106" --8<-- "docs/aca/05-aca-dapr-pubsubapi/TasksStoreManager.cs" ``` @@ -349,9 +365,9 @@ We need to add a new [Dapr Azure Service Bus Topic component](https://docs.dapr. !!! note We used the name `dapr-pubsub-servicebus` which should match the name of Pub/Sub component we've used earlier in the `TasksNotifierController.cs` controller on the action method with the attribute `Topic`. - We set the metadata (key/value) to allow us to connect to Azure Service Bus topic. The metadata `consumerID` value should match the topic subscription name `sbts-tasks-processor`. + We set the metadata (key/value) to allow us to connect to Azure Service Bus topic. The metadata `consumerID` value should match the topic subscription name `sbts-tasks-processor`. - We have set the scopes section to include the `tasksmanager-backend-api` and `tasksmanager-backend-processor` app ids, as those will be the Dapr apps that need access to Azure Service Bus for publishing and + We have set the scopes section to include the `tasksmanager-backend-api` and `tasksmanager-backend-processor` app ids, as those will be the Dapr apps that need access to Azure Service Bus for publishing and consuming the messages. #### 3.3 Create an ACA Dapr Component file for Pub/Sub API Using Azure Service Bus @@ -419,7 +435,7 @@ $BACKEND_SERVICE_NAME="tasksmanager-backend-processor" az acr build ` --registry $AZURE_CONTAINER_REGISTRY_NAME ` --image "tasksmanager/$BACKEND_API_NAME" ` ---file 'TasksTracker.TasksManager.Backend.Api/Dockerfile' . +--file 'TasksTracker.TasksManager.Backend.Api/Dockerfile' . az acr build ` --registry $AZURE_CONTAINER_REGISTRY_NAME ` @@ -460,7 +476,7 @@ az containerapp create ` We need to update the Azure Container App hosting the Backend API with a new revision so our code changes for publishing messages after a task is saved is available for users. ```shell -# Update Backend API App container app and create a new revision +# Update Backend API App container app and create a new revision az containerapp update ` --name $BACKEND_API_NAME ` --resource-group $RESOURCE_GROUP ` @@ -553,27 +569,29 @@ Lastly, we need to restart both container apps revisions to pick up the role ass ```shell # Get revision name and assign it to a variable -$REVISION_NAME = (az containerapp revision list ` - --name $BACKEND_SERVICE_NAME ` +$BACKEND_SERVICE_REVISION_NAME = (az containerapp revision list ` + --name $BACKEND_SERVICE_NAME ` --resource-group $RESOURCE_GROUP ` - --query [0].name) + --query [0].name ` + --output tsv) # Restart revision by name az containerapp revision restart ` --resource-group $RESOURCE_GROUP ` --name $BACKEND_SERVICE_NAME ` ---revision $REVISION_NAME +--revision $BACKEND_SERVICE_REVISION_NAME -$REVISION_NAME = (az containerapp revision list ` - --name $BACKEND_API_NAME ` +$BACKEND_API_REVISION_NAME = (az containerapp revision list ` + --name $BACKEND_API_NAME ` --resource-group $RESOURCE_GROUP ` - --query [0].name) + --query [0].name ` + --output tsv) # Restart revision by name az containerapp revision restart ` --resource-group $RESOURCE_GROUP ` --name $BACKEND_API_NAME ` ---revision $REVISION_NAME +--revision $BACKEND_API_REVISION_NAME ``` !!! Success diff --git a/docs/aca/06-aca-dapr-bindingsapi/ExternalTasksProcessorController.cs b/docs/aca/06-aca-dapr-bindingsapi/ExternalTasksProcessorController.cs index 511a236a..0ba30961 100644 --- a/docs/aca/06-aca-dapr-bindingsapi/ExternalTasksProcessorController.cs +++ b/docs/aca/06-aca-dapr-bindingsapi/ExternalTasksProcessorController.cs @@ -32,7 +32,7 @@ public async Task ProcessTaskAndStore([FromBody] TaskModel taskMo _logger.LogInformation("Saved external task to the state store successfully. Task name: '{0}', Task Id: '{1}'", taskModel.TaskName, taskModel.TaskId); - //ToDo: code to invoke external binding and store queue message content into blob file in Azure storage + //TODO: code to invoke external binding and store queue message content into blob file in Azure storage return Ok(); } diff --git a/docs/aca/06-aca-dapr-bindingsapi/index.md b/docs/aca/06-aca-dapr-bindingsapi/index.md index cafd1ffb..672179ba 100644 --- a/docs/aca/06-aca-dapr-bindingsapi/index.md +++ b/docs/aca/06-aca-dapr-bindingsapi/index.md @@ -80,19 +80,19 @@ Run the PowerShell script below to create Azure Storage Account and get the mast We will be retrieving the storage account key for local dev testing purposes. Note that the command below will return two keys. You will only need one of them for this exercise. When deploying the changes to ACA, we are going to store the storage key securely into Azure Key Vault using [Dapr Secrets Store Building Block with AKV](https://docs.dapr.io/reference/components-reference/supported-secret-stores/azure-keyvault/){target=_blank}. - We didn't use Azure Manged Identity here because the assumption is that those services are not part of our solution and thus they could theoretically be a non AD compliant services or hosted on another cloud. + We didn't use Azure Manged Identity here because the assumption is that those services are not part of our solution and thus they could theoretically be a non AD compliant services or hosted on another cloud. If these services where part of your application's ecosystem it is always recommended that you use Azure Managed Identity. ```shell $STORAGE_ACCOUNT_NAME = "sttaskstracker$RANDOM_STRING" - + az storage account create ` --name $STORAGE_ACCOUNT_NAME ` --resource-group $RESOURCE_GROUP ` --location $LOCATION ` --sku Standard_LRS ` --kind StorageV2 - + # List Azure storage keys az storage account keys list ` --resource-group $RESOURCE_GROUP ` @@ -101,12 +101,15 @@ az storage account keys list ` # Get the primary storage account key $STORAGE_ACCOUNT_KEY=($(az storage account keys list ` --resource-group $RESOURCE_GROUP ` ---account-name $STORAGE_ACCOUNT_NAME ) | ConvertFrom-Json)[0].value +--account-name $STORAGE_ACCOUNT_NAME ` +--output json) | ConvertFrom-Json)[0].value echo "Storage Account Name : $STORAGE_ACCOUNT_NAME" echo "Storage Account Key : $STORAGE_ACCOUNT_KEY" ``` +Take note of the **Storage Account Name** and **Storage Account Key** as we will use both below. + ### 2. Updating the Backend Background Processor Project #### 2.1 Create an event handler (API endpoint) to respond to messages published to Azure Storage Queue @@ -123,11 +126,11 @@ Start by adding a new controller **Controllers** folder under the **TasksTracker ??? tip "Curious to know more about the code?" - - We defined an action method named `ProcessTaskAndStore` which can be accessed by sending HTTP POST operation on the - endpoint `ExternalTasksProcessor/Process`. - - - This action method accepts the TaskModel in the request body as JSON payload.This is what will be received from the external service (Azure Storage Queue). - + - We defined an action method named `ProcessTaskAndStore` which can be accessed by sending HTTP POST operation on the + endpoint `ExternalTasksProcessor/Process`. + + - This action method accepts the TaskModel in the request body as JSON payload.This is what will be received from the external service (Azure Storage Queue). + - Within this action method, we are going to store the received task by sending a POST request to `/api/tasks` which is part of the backend api named `tasksmanager-backend-api`. - Then we return `200 OK` to acknowledge that message received is processed successfully and should be removed from the external service queue. @@ -142,6 +145,8 @@ Now we need to create the component configuration file which will describe the c --8<-- "docs/aca/06-aca-dapr-bindingsapi/dapr-bindings-in-storagequeue.yaml" ``` +Ensure that you replace `` and `` with the values from step 1.1. + ??? tip "Curious to learn more about the specification of yaml file?" The full specifications of yaml file with Azure Storage Queues can be found on [this link](https://docs.dapr.io/reference/components-reference/supported-bindings/storagequeues/){target=_blank}, but let's go over the configuration we have added here: @@ -168,16 +173,18 @@ To do so, add a new file folder **components**. --8<-- "docs/aca/06-aca-dapr-bindingsapi/dapr-bindings-out-blobstorage.yaml" ``` +Ensure that you replace `` and `` with the values from step 1.1. + ??? tip "Curious to learn more about the specification of yaml file?" - The full specifications of yaml file with Azure blob storage can be found on [this link](https://docs.dapr.io/reference/components-reference/supported-bindings/blobstorage/){target=_blank}, + The full specifications of yaml file with Azure blob storage can be found on [this link](https://docs.dapr.io/reference/components-reference/supported-bindings/blobstorage/){target=_blank}, but let's go over the configuration we have added here: - The type of binding is `bindings.azure.blobstorage`. - The name of this output binding is `externaltasksblobstore`. We will use this name when we use the Dapr SDK to trigger the output binding. - We are setting the `storageAccount` name, `storageAccessKey` value, and the `container` name. Those properties will describe how our backend background service will be able to connect to the external service and create a blob file. We will assume that there is a container already created on the external service and named `externaltaskscontainer` as shown in the image below - + ![Storage-Account-Container](../../assets/images/06-aca-dapr-bindingsapi/StorageAccountContainer.png) - We are setting the property `decodeBase64` to `false` as we don't want to encode file content to base64 images, we need to store the file content as is. @@ -196,12 +203,12 @@ Update and replace the code in the file with the code below. Pay close attention ??? tip "Curious to know more about the code?" - Looking at the `ProcessTaskAndStore` action method above, you will see that we are calling the method `InvokeBindingAsync` and we are passing the binding name `externaltasksblobstore` - defined in the configuration file, as well the second parameter `create` which is the action we need to carry against the external blob storage. + Looking at the `ProcessTaskAndStore` action method above, you will see that we are calling the method `InvokeBindingAsync` and we are passing the binding name `externaltasksblobstore` + defined in the configuration file, as well the second parameter `create` which is the action we need to carry against the external blob storage. You can for example delete or get a content of a certain file. For a full list of supported actions on Azure Blob Storage, [visit this link](https://docs.dapr.io/reference/components-reference/supported-bindings/blobstorage/#binding-support){target=_blank}. - Notice how are setting the file name we are storing at the external service. We need the file names to be created using the same Task Identifier, so we will pass the key `blobName` with the file name values + Notice how are setting the file name we are storing at the external service. We need the file names to be created using the same Task Identifier, so we will pass the key `blobName` with the file name values into the `metaData` dictionary. #### 2.5 Test Dapr Bindings Locally @@ -282,7 +289,8 @@ $KEYVAULT_SECRETS_USER_ROLE_ID = "4633458b-17de-408a-b874-0445c86b69e6" # ID for $BACKEND_SERVICE_PRINCIPAL_ID = az containerapp show ` --name $BACKEND_SERVICE_NAME ` --resource-group $RESOURCE_GROUP ` ---query identity.principalId +--query identity.principalId ` +--output tsv az role assignment create ` --role $KEYVAULT_SECRETS_USER_ROLE_ID ` @@ -296,8 +304,8 @@ To create a secret in Azure Key Vault you need to have a role which allows you t be able to create secrets. To do so use the script below: ```shell -$SIGNEDIN_USERID = az ad signed-in-user show --query id -$KEYVAULT_SECRETS_OFFICER_ROLE_ID = "b86a8fe4-44ce-4948-aee5-eccb2c155cd7" # ID for 'Key Vault Secrets Office' Role +$SIGNEDIN_USERID = az ad signed-in-user show --query id --output tsv +$KEYVAULT_SECRETS_OFFICER_ROLE_ID = "b86a8fe4-44ce-4948-aee5-eccb2c155cd7" # ID for 'Key Vault Secrets Office' Role az role assignment create ` --role $KEYVAULT_SECRETS_OFFICER_ROLE_ID ` @@ -333,10 +341,10 @@ Create a new yaml file under the **aca-components** folder. ??? tip "Curious to learn more about the yaml file?" - - We didn't specify the component name `secretstoreakv` in the metadata of the this component yaml file. We are going to specify it once we add this dapr component to Azure Container Apps Environment + - We didn't specify the component name `secretstoreakv` in the metadata of the this component yaml file. We are going to specify it once we add this dapr component to Azure Container Apps Environment via CLI similar to what we did in earlier modules. - - We are not referencing any service bus connection strings as the authentication between Dapr and Azure Service Bus will be configured using Managed Identities. - - The metadata `vaultName` value is set to the name of the Azure Key Vault we've just created. + - We are not referencing any service bus connection strings as the authentication between Dapr and Azure Service Bus will be configured using Managed Identities. + - The metadata `vaultName` value is set to the name of the Azure Key Vault we've just created. - We are allowing this component only to be accessed by the dapr with application id `tasksmanager-backend-processor`. This means that our Backend API or Frontend Web App services will not be able to access the Dapr secret store. If we want to allow them to access the secrets we need to update this component file and grant the system-identity of those services a `Key Vault Secrets User` role. @@ -350,10 +358,10 @@ Add new files under the **aca-components** use the yaml below: --8<-- "docs/aca/06-aca-dapr-bindingsapi/containerapps-bindings-in-storagequeue.yaml" ``` ??? tip "Curious to learn more about the yaml file?" - - The properties of this file are matching the ones used in Dapr component-specific file. It is a component of type `bindings.azure.storagequeues`. - The only differences are the following: - + + The properties of this file are matching the ones used in Dapr component-specific file. It is a component of type `bindings.azure.storagequeues`. + The only differences are the following: + - We are setting the property `secretStoreComponent` value to `secretstoreakv` which is the name of Dapr secret store component. - We are using `secretRef` when setting the metadata `storageAccessKey`. The value `external-azure-storage-key` represents the AKV secret created earlier. @@ -364,9 +372,9 @@ Add new files under the **aca-components** use the yaml below: ``` ??? tip "Curious to learn more about the yaml file?" - The properties of this file are matching the ones used in Dapr component-specific file. It is a component of type `bindings.azure.blobstorage`. + The properties of this file are matching the ones used in Dapr component-specific file. It is a component of type `bindings.azure.blobstorage`. The only differences are the following: - + - We are setting the property `secretStoreComponent` value to `secretstoreakv` which is the name of Dapr secret store component. - We are using `secretRef` when setting the metadata `storageAccessKey`. The value `external-azure-storage-key` represents the AKV secret created earlier @@ -409,7 +417,7 @@ az containerapp env dapr-component set ` --resource-group $RESOURCE_GROUP ` --dapr-component-name externaltasksmanager ` --yaml '.\aca-components\containerapps-bindings-in-storagequeue.yaml' - + # Output binding component for Azure Blob Storage az containerapp env dapr-component set ` --name $ENVIRONMENT ` @@ -423,11 +431,11 @@ az containerapp env dapr-component set ` Update the Azure Container App hosting the Backend Background Processor with a new revision so our code changes are available for end users. ```shell -# Update Backend Background Processor container app and create a new revision +# Update Backend Background Processor container app and create a new revision az containerapp update ` --name $BACKEND_SERVICE_NAME ` --resource-group $RESOURCE_GROUP ` ---revision-suffix v$TODAY-3 ` +--revision-suffix v$TODAY-3 ``` !!! success diff --git a/docs/aca/07-aca-cron-bindings/TasksStoreManager.cs b/docs/aca/07-aca-cron-bindings/TasksStoreManager.cs index 9b84f509..303e94ee 100644 --- a/docs/aca/07-aca-cron-bindings/TasksStoreManager.cs +++ b/docs/aca/07-aca-cron-bindings/TasksStoreManager.cs @@ -59,7 +59,11 @@ public async Task> GetTasksByCreator(string createdBy) var queryResponse = await _daprClient.QueryStateAsync(STORE_NAME, query); - var tasksList = queryResponse.Results.Select(q => q.Data).OrderByDescending(o=>o.TaskCreatedOn); + var tasksList = queryResponse.Results + .Where(q => q.Data != null) // filter null data + .Select(q => q.Data!) + .OrderByDescending(o=>o.TaskCreatedOn); + return tasksList.ToList(); } @@ -108,21 +112,27 @@ public async Task> GetYesterdaysDueTasks() Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; var yesterday = DateTime.Today.AddDays(-1); - + var jsonDate = JsonSerializer.Serialize(yesterday, options); - + _logger.LogInformation("Getting overdue tasks for yesterday date: '{0}'", jsonDate); - + var query = "{" + "\"filter\": {" + "\"EQ\": { \"taskDueDate\": " + jsonDate + " }" + "}}"; - + var queryResponse = await _daprClient.QueryStateAsync(STORE_NAME, query); - var tasksList = queryResponse.Results.Select(q => q.Data).Where(q=>q.IsCompleted==false && q.IsOverDue==false).OrderBy(o=>o.TaskCreatedOn); - return tasksList.ToList(); + + var tasksList = queryResponse.Results + .Where(q => q.Data != null) // filter null data + .Select(q => q.Data) + .Where(q => q!.IsCompleted == false && q.IsOverDue == false) + .OrderBy(o => o!.TaskCreatedOn); + + return tasksList.ToList()!; } - + public async Task MarkOverdueTasks(List overDueTasksList) { foreach (var taskModel in overDueTasksList) diff --git a/docs/aca/07-aca-cron-bindings/index.md b/docs/aca/07-aca-cron-bindings/index.md index 8a47a0b5..3b9796a2 100644 --- a/docs/aca/07-aca-cron-bindings/index.md +++ b/docs/aca/07-aca-cron-bindings/index.md @@ -85,7 +85,7 @@ Update these files under the **Services** folder in the project **TasksTracker.T === "TasksStoreManager.cs" - ```csharp hl_lines="2-4 95-134" + ```csharp hl_lines="2-4 99-144" --8<-- "docs/aca/07-aca-cron-bindings/TasksStoreManager.cs" ``` diff --git a/docs/aca/08-aca-monitoring/Backend.Api-dotnet8.csproj b/docs/aca/08-aca-monitoring/Backend.Api-dotnet8.csproj index 6eb607ee..f565d88f 100644 --- a/docs/aca/08-aca-monitoring/Backend.Api-dotnet8.csproj +++ b/docs/aca/08-aca-monitoring/Backend.Api-dotnet8.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/docs/aca/08-aca-monitoring/Backend.Api-dotnet9.csproj b/docs/aca/08-aca-monitoring/Backend.Api-dotnet9.csproj new file mode 100644 index 00000000..a21e5c87 --- /dev/null +++ b/docs/aca/08-aca-monitoring/Backend.Api-dotnet9.csproj @@ -0,0 +1,16 @@ + + + + net9.0 + enable + enable + + + + + + + + + + diff --git a/docs/aca/08-aca-monitoring/Backend.Svc-dotnet8.csproj b/docs/aca/08-aca-monitoring/Backend.Svc-dotnet8.csproj index 2af2159a..f5955364 100644 --- a/docs/aca/08-aca-monitoring/Backend.Svc-dotnet8.csproj +++ b/docs/aca/08-aca-monitoring/Backend.Svc-dotnet8.csproj @@ -1,5 +1,5 @@ - + net8.0 enable @@ -8,8 +8,8 @@ - - + + diff --git a/docs/aca/08-aca-monitoring/Backend.Svc-dotnet9.csproj b/docs/aca/08-aca-monitoring/Backend.Svc-dotnet9.csproj new file mode 100644 index 00000000..98c784bf --- /dev/null +++ b/docs/aca/08-aca-monitoring/Backend.Svc-dotnet9.csproj @@ -0,0 +1,15 @@ + + + + net9.0 + enable + enable + + + + + + + + + diff --git a/docs/aca/08-aca-monitoring/Frontend.Ui-dotnet8.csproj b/docs/aca/08-aca-monitoring/Frontend.Ui-dotnet8.csproj index 52b90f3f..f0e78e86 100644 --- a/docs/aca/08-aca-monitoring/Frontend.Ui-dotnet8.csproj +++ b/docs/aca/08-aca-monitoring/Frontend.Ui-dotnet8.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/docs/aca/08-aca-monitoring/Frontend.Ui-dotnet9.csproj b/docs/aca/08-aca-monitoring/Frontend.Ui-dotnet9.csproj new file mode 100644 index 00000000..b57e9991 --- /dev/null +++ b/docs/aca/08-aca-monitoring/Frontend.Ui-dotnet9.csproj @@ -0,0 +1,14 @@ + + + + net9.0 + enable + enable + + + + + + + + diff --git a/docs/aca/08-aca-monitoring/Program-Backend.API.cs b/docs/aca/08-aca-monitoring/Program-Backend.API-dotnet8.cs similarity index 100% rename from docs/aca/08-aca-monitoring/Program-Backend.API.cs rename to docs/aca/08-aca-monitoring/Program-Backend.API-dotnet8.cs diff --git a/docs/aca/08-aca-monitoring/Program-Backend.API-dotnet9.cs b/docs/aca/08-aca-monitoring/Program-Backend.API-dotnet9.cs new file mode 100644 index 00000000..71cb7e6c --- /dev/null +++ b/docs/aca/08-aca-monitoring/Program-Backend.API-dotnet9.cs @@ -0,0 +1,32 @@ +using Microsoft.ApplicationInsights.Extensibility; +using TasksTracker.TasksManager.Backend.Api.Services; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddApplicationInsightsTelemetry(); +builder.Services.Configure((o) => { + o.TelemetryInitializers.Add(new TasksTracker.TasksManager.Backend.Api.AppInsightsTelemetryInitializer()); +}); + +builder.Services.AddSingleton(); +builder.Services.AddControllers(); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/docs/aca/08-aca-monitoring/Program-Backend.Svc.cs b/docs/aca/08-aca-monitoring/Program-Backend.Svc-dotnet8.cs similarity index 100% rename from docs/aca/08-aca-monitoring/Program-Backend.Svc.cs rename to docs/aca/08-aca-monitoring/Program-Backend.Svc-dotnet8.cs diff --git a/docs/aca/08-aca-monitoring/Program-Backend.Svc-dotnet9.cs b/docs/aca/08-aca-monitoring/Program-Backend.Svc-dotnet9.cs new file mode 100644 index 00000000..d3137fe7 --- /dev/null +++ b/docs/aca/08-aca-monitoring/Program-Backend.Svc-dotnet9.cs @@ -0,0 +1,35 @@ +using Microsoft.ApplicationInsights.Extensibility; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddApplicationInsightsTelemetry(); +builder.Services.Configure((o) => { + o.TelemetryInitializers.Add(new TasksTracker.TasksManager.Backend.Svc.AppInsightsTelemetryInitializer()); +}); + +builder.Services.AddControllers().AddDapr(); +builder.Services.AddControllers(); +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.UseCloudEvents(); + +app.MapControllers(); + +app.MapSubscribeHandler(); + +app.Run(); \ No newline at end of file diff --git a/docs/aca/08-aca-monitoring/Program-Frontend.UI.cs b/docs/aca/08-aca-monitoring/Program-Frontend.UI-dotnet8.cs similarity index 100% rename from docs/aca/08-aca-monitoring/Program-Frontend.UI.cs rename to docs/aca/08-aca-monitoring/Program-Frontend.UI-dotnet8.cs diff --git a/docs/aca/08-aca-monitoring/Program-Frontend.UI-dotnet9.cs b/docs/aca/08-aca-monitoring/Program-Frontend.UI-dotnet9.cs new file mode 100644 index 00000000..70783743 --- /dev/null +++ b/docs/aca/08-aca-monitoring/Program-Frontend.UI-dotnet9.cs @@ -0,0 +1,46 @@ +using Microsoft.ApplicationInsights.Extensibility; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddApplicationInsightsTelemetry(); +builder.Services.Configure((o) => { + o.TelemetryInitializers.Add(new TasksTracker.TasksManager.Frontend.Ui.AppInsightsTelemetryInitializer()); +}); + +builder.Services.AddRazorPages(); + +builder.Services.AddDaprClient(); + +builder.Services.AddHttpClient("BackEndApiExternal", httpClient => +{ + var backendApiBaseUrlExternalHttp = builder.Configuration.GetValue("BackendApiConfig:BaseUrlExternalHttp"); + + if (!string.IsNullOrEmpty(backendApiBaseUrlExternalHttp)) { + httpClient.BaseAddress = new Uri(backendApiBaseUrlExternalHttp); + } else { + throw new("BackendApiConfig:BaseUrlExternalHttp is not defined in App Settings."); + } +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} + +app.UseHttpsRedirection(); + +app.UseRouting(); + +app.UseAuthorization(); + +app.MapStaticAssets(); +app.MapRazorPages() + .WithStaticAssets(); + +app.Run(); \ No newline at end of file diff --git a/docs/aca/08-aca-monitoring/index.md b/docs/aca/08-aca-monitoring/index.md index f31b9e42..bdaa1db9 100644 --- a/docs/aca/08-aca-monitoring/index.md +++ b/docs/aca/08-aca-monitoring/index.md @@ -51,7 +51,8 @@ Our next step is to incorporate the Application Insights SDK into the **three mi To incorporate the SDK, use the NuGet reference below in the `csproj` file of the Backend API project. You may locate the csproj file in the project directory **TasksTracker.TasksManager.Backend.Api**: -=== ".NET 8 or above" +=== ".NET 8" + === "TasksTracker.TasksManager.Backend.Api.csproj" ```xml hl_lines="12" @@ -70,6 +71,26 @@ To incorporate the SDK, use the NuGet reference below in the `csproj` file of th --8<-- "docs/aca/08-aca-monitoring/Frontend.Ui-dotnet8.csproj" ``` +=== ".NET 9" + + === "TasksTracker.TasksManager.Backend.Api.csproj" + + ```xml hl_lines="12" + --8<-- "docs/aca/08-aca-monitoring/Backend.Api-dotnet9.csproj" + ``` + + === "TasksTracker.TasksManager.Backend.Svc.csproj" + + ```xml hl_lines="12" + --8<-- "docs/aca/08-aca-monitoring/Backend.Svc-dotnet9.csproj" + ``` + + === "TasksTracker.TasksManager.Frontend.Ui.csproj" + + ```xml hl_lines="11" + --8<-- "docs/aca/08-aca-monitoring/Frontend.Ui-dotnet9.csproj" + ``` + #### 2.2 Set RoleName Property in All the Services For each of the three projects, we will add a new file to each project's root directory. @@ -106,35 +127,67 @@ For each of the three projects, we will add a new file to each project's root di Next, we need to register this `AppInsightsTelemetryInitializer` class in **Program.cs** in each of the three projects. -=== "Backend.Api" +=== ".NET 8" - === "Program.cs" + === "Backend.Api" - ```csharp hl_lines="1 9-12" - --8<-- "docs/aca/08-aca-monitoring/Program-Backend.API.cs" - ``` + === "Program.cs" -=== "Backend.Svc" + ```csharp hl_lines="1 9-12" + --8<-- "docs/aca/08-aca-monitoring/Program-Backend.API-dotnet8.cs" + ``` - === "Program.cs" + === "Backend.Svc" - ```csharp hl_lines="1 8-11" - --8<-- "docs/aca/08-aca-monitoring/Program-Backend.Svc.cs" - ``` + === "Program.cs" -=== "Frontend.Ui" + ```csharp hl_lines="1 8-11" + --8<-- "docs/aca/08-aca-monitoring/Program-Backend.Svc-dotnet8.cs" + ``` - === "Program.cs" + === "Frontend.Ui" - ```csharp hl_lines="1 8-11" - --8<-- "docs/aca/08-aca-monitoring/Program-Frontend.UI.cs" - ``` + === "Program.cs" + + ```csharp hl_lines="1 8-11" + --8<-- "docs/aca/08-aca-monitoring/Program-Frontend.UI-dotnet8.cs" + ``` + +=== ".NET 9" + + === "Backend.Api" + + === "Program.cs" + + ```csharp hl_lines="1 8-11" + --8<-- "docs/aca/08-aca-monitoring/Program-Backend.API-dotnet9.cs" + ``` + + === "Backend.Svc" + + === "Program.cs" + + ```csharp hl_lines="1 7-10" + --8<-- "docs/aca/08-aca-monitoring/Program-Backend.Svc-dotnet9.cs" + ``` + + === "Frontend.Ui" + + === "Program.cs" + + ```csharp hl_lines="1 6-9" + --8<-- "docs/aca/08-aca-monitoring/Program-Frontend.UI-dotnet9.cs" + ``` #### 2.3 Set the Application Insights Instrumentation Key In the previous module, we've used Dapr Secret Store to store connection strings and keys. In this module we will demonstrate how we can use another approach to secrets in Container Apps. -We need to set the Application Insights Instrumentation Key so that the projects are able to send telemetry data to the Application Insights instance. We are going to set this via secrets and environment variables once we redeploy the Container Apps and create new revisions. +We need to set the Application Insights Instrumentation Key so that the projects are able to send telemetry data to the Application Insights instance. We are going to set this via secrets and environment variables once we redeploy the Container Apps and create new revisions. Locally, we can set it in each appsettings.json file. Obtain the key from the variable: + +```shell +$APPINSIGHTS_INSTRUMENTATIONKEY +``` === "appsettings.json" diff --git a/docs/aca/12-optimize-containers/Backend.Api.Dockerfile b/docs/aca/12-optimize-containers/Backend.Api.Dockerfile index 396a1ddd..0868716d 100644 --- a/docs/aca/12-optimize-containers/Backend.Api.Dockerfile +++ b/docs/aca/12-optimize-containers/Backend.Api.Dockerfile @@ -1,8 +1,8 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app -EXPOSE 5000 +EXPOSE 8080 -ENV ASPNETCORE_URLS=http://+:5000 +ENV ASPNETCORE_URLS=http://+:8080 USER app FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build diff --git a/docs/aca/12-optimize-containers/Backend.Api.Dockerfile.chiseled b/docs/aca/12-optimize-containers/Backend.Api.Dockerfile.chiseled index 2a1dfc35..86d1e5f1 100644 --- a/docs/aca/12-optimize-containers/Backend.Api.Dockerfile.chiseled +++ b/docs/aca/12-optimize-containers/Backend.Api.Dockerfile.chiseled @@ -1,8 +1,8 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled AS base WORKDIR /app -EXPOSE 5000 +EXPOSE 8080 -ENV ASPNETCORE_URLS=http://+:5000 +ENV ASPNETCORE_URLS=http://+:8080 USER app FROM mcr.microsoft.com/dotnet/sdk:8.0-jammy AS build diff --git a/docs/aca/12-optimize-containers/Backend.Api.Dockerfile.chiseled.aot b/docs/aca/12-optimize-containers/Backend.Api.Dockerfile.chiseled.aot index 862cdb9a..33737fb0 100644 --- a/docs/aca/12-optimize-containers/Backend.Api.Dockerfile.chiseled.aot +++ b/docs/aca/12-optimize-containers/Backend.Api.Dockerfile.chiseled.aot @@ -1,8 +1,8 @@ FROM mcr.microsoft.com/dotnet/nightly/runtime-deps:8.0-jammy-chiseled-aot AS base WORKDIR /app -EXPOSE 5000 +EXPOSE 8080 -ENV ASPNETCORE_URLS=http://+:5000 +ENV ASPNETCORE_URLS=http://+:8080 USER app FROM mcr.microsoft.com/dotnet/nightly/sdk:8.0-jammy-aot AS build diff --git a/docs/aca/12-optimize-containers/Backend.Svc.Dockerfile b/docs/aca/12-optimize-containers/Backend.Svc.Dockerfile index b60a9b20..c57c14ee 100644 --- a/docs/aca/12-optimize-containers/Backend.Svc.Dockerfile +++ b/docs/aca/12-optimize-containers/Backend.Svc.Dockerfile @@ -1,8 +1,8 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app -EXPOSE 5000 +EXPOSE 8080 -ENV ASPNETCORE_URLS=http://+:5000 +ENV ASPNETCORE_URLS=http://+:8080 USER app FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build diff --git a/docs/aca/12-optimize-containers/Backend.Svc.Dockerfile.chiseled.aot b/docs/aca/12-optimize-containers/Backend.Svc.Dockerfile.chiseled.aot index 8db548e0..893be3c7 100644 --- a/docs/aca/12-optimize-containers/Backend.Svc.Dockerfile.chiseled.aot +++ b/docs/aca/12-optimize-containers/Backend.Svc.Dockerfile.chiseled.aot @@ -1,8 +1,8 @@ FROM mcr.microsoft.com/dotnet/nightly/runtime-deps:8.0-jammy-chiseled-aot AS base WORKDIR /app -EXPOSE 5000 +EXPOSE 8080 -ENV ASPNETCORE_URLS=http://+:5000 +ENV ASPNETCORE_URLS=http://+:8080 USER app FROM mcr.microsoft.com/dotnet/nightly/sdk:8.0-jammy-aot AS build diff --git a/docs/aca/12-optimize-containers/Frontend.Ui.Dockerfile b/docs/aca/12-optimize-containers/Frontend.Ui.Dockerfile index d59ec115..1d55596a 100644 --- a/docs/aca/12-optimize-containers/Frontend.Ui.Dockerfile +++ b/docs/aca/12-optimize-containers/Frontend.Ui.Dockerfile @@ -1,8 +1,8 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app -EXPOSE 5000 +EXPOSE 8080 -ENV ASPNETCORE_URLS=http://+:5000 +ENV ASPNETCORE_URLS=http://+:8080 USER app FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build diff --git a/docs/aca/12-optimize-containers/Frontend.Ui.Dockerfile.chiseled.aot b/docs/aca/12-optimize-containers/Frontend.Ui.Dockerfile.chiseled.aot index f0d24b3d..9af28f90 100644 --- a/docs/aca/12-optimize-containers/Frontend.Ui.Dockerfile.chiseled.aot +++ b/docs/aca/12-optimize-containers/Frontend.Ui.Dockerfile.chiseled.aot @@ -1,8 +1,8 @@ FROM mcr.microsoft.com/dotnet/nightly/runtime-deps:8.0-jammy-chiseled-aot AS base WORKDIR /app -EXPOSE 5000 +EXPOSE 8080 -ENV ASPNETCORE_URLS=http://+:5000 +ENV ASPNETCORE_URLS=http://+:8080 USER app FROM mcr.microsoft.com/dotnet/nightly/sdk:8.0-jammy-aot AS build diff --git a/docs/aca/12-optimize-containers/index.md b/docs/aca/12-optimize-containers/index.md index 82d76bd7..0edaadeb 100644 --- a/docs/aca/12-optimize-containers/index.md +++ b/docs/aca/12-optimize-containers/index.md @@ -28,7 +28,7 @@ In this module, we will look into the benefits of optimized containers such as: - Potentially less *Common Vulnerabilities and Exposures (CVEs)*. - No bloat and unnecessary components such as shells, package managers, etc. -While available prior to .NET 8, the general availability introduction of .NET 8 in November 2023 came with an expanded focus on container optimization and a move away from general-purpose containers. +While available prior to .NET 8, the general availability introduction of .NET 8 in November 2023 came with an expanded focus on container optimization and a move away from general-purpose containers. You can apply similar steps for .NET versions newer than 8, but we omit them here for brevity. Please ensure you have the Docker daemon ready. Running *Docker Desktop* does it. diff --git a/docs/aca/30-appendix/Set-Variables.ps1 b/docs/aca/30-appendix/Set-Variables.ps1 index 76bffc72..7a4d53bd 100644 --- a/docs/aca/30-appendix/Set-Variables.ps1 +++ b/docs/aca/30-appendix/Set-Variables.ps1 @@ -13,9 +13,11 @@ $vars = @( "BACKEND_API_INTERNAL_BASE_URL", "BACKEND_API_NAME", "BACKEND_API_PRINCIPAL_ID", + "BACKEND_API_REVISION_NAME", "BACKEND_SERVICE_APP_PORT", "BACKEND_SERVICE_NAME", "BACKEND_SERVICE_PRINCIPAL_ID", + "BACKEND_SERVICE_REVISION_NAME", "COSMOS_DB_ACCOUNT", "COSMOS_DB_CONTAINER", "COSMOS_DB_DBNAME", @@ -31,7 +33,6 @@ $vars = @( "LOCATION", "RANDOM_STRING", "RESOURCE_GROUP", - "REVISION_NAME", "ROLE_ID", "SERVICE_BUS_CONNECTION_STRING", "SERVICE_BUS_NAMESPACE_NAME", diff --git a/mkdocs.yml b/mkdocs.yml index a40ac298..3633571b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -30,7 +30,7 @@ nav: - Debug and Launch Dapr Applications in VSCode: aca/30-appendix/01-run-debug-dapr-app-vscode.md - Testing Changes Locally or with GitHub Codespaces: aca/30-appendix/02-github-local-codespaces.md - Variables: aca/30-appendix/03-variables.md - + theme: name: material custom_dir: docs/overrides @@ -74,6 +74,7 @@ markdown_extensions: - pymdownx.inlinehilite - pymdownx.highlight: anchor_linenums: true + linenums: true line_spans: __span pygments_lang_class: true - pymdownx.tabbed: @@ -109,9 +110,9 @@ extra: backendsvc: "ACA Processor - Backend" frontend: "ACA Web - Frontend" dapr: - version: 1.12.0 + version: 1.14.0 docker: - targetport: 5000 + targetport: 8080 social: - icon: fontawesome/brands/github link: https://github.com/Azure/aca-dotnet-workshop diff --git a/snippets/containerize-app.md b/snippets/containerize-app.md index dac3e796..3c55251f 100644 --- a/snippets/containerize-app.md +++ b/snippets/containerize-app.md @@ -4,14 +4,11 @@ - Use `.NET: ASP.NET Core` when prompted for the application platform. - Choose the newly-created project, if prompted. - Choose `Linux` when prompted to choose the operating system. - - Set the **application port** to `5000`. This is arbitrary but memorable for this workshop. + - Set the **application port** to `8080`, which is the default non-privileged port since .NET 8. - You will be asked if you want to add Docker Compose files. Select `No`. - - `Dockerfile` and `.dockerignore` files are added to the workspace. - - Open `Dockerfile` and replace - `FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build` with - `FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build` + - `Dockerfile` and `.dockerignore` files are added to the project workspace. + - Open `Dockerfile` and remove `--platform=$BUILDPLATFORM` from the `FROM` instruction. !!! bug "Dockerfile Build Platform" Azure Container Registry does not set `$BUILDPLATFORM` presently when building containers. This consequently causes the build to fail. See [this issue](https://github.com/microsoft/vscode-docker/issues/4149){target=_blank} for details. Therefore, we remove it from the file for the time being. We expect this to be corrected in the future. - \ No newline at end of file