Skip to content

Commit

Permalink
More updates, let's see where we are
Browse files Browse the repository at this point in the history
  • Loading branch information
IEvangelist committed Dec 1, 2023
1 parent 8856b67 commit d4db8b5
Show file tree
Hide file tree
Showing 14 changed files with 152 additions and 80 deletions.
Binary file modified docs/media/queue-output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/storage-project.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 43 additions & 18 deletions docs/storage/azure-storage.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Connect an ASP.NET Core app to .NET Aspire storage components
description: Learn how to connect an ASP.NET Core app to .NET Aspire storage components.
ms.date: 11/30/2023
ms.date: 12/01/2023
ms.topic: tutorial
zone_pivot_groups: azure-storage-mechanism
---
Expand Down Expand Up @@ -75,13 +75,8 @@ You also need to assign the following roles to the user account you are logged i

Visual Studio creates a new ASP.NET Core solution that is structured to use .NET Aspire. The solution consists of the following projects:

- **AspireStorage**: A Blazor project directory that contains the following projects:

- **AspireStorage**: The Blazor project.
- **AspireStorage.Client**: The Blazor WebAssembly client project.

- **AspireStorage**: A Blazor project that depends on service defaults.
- **AspireStorage.AppHost**: An orchestrator project designed to connect and configure the different projects and services of your app. The orchestrator should be set as the startup project.

- **AspireStorage.ServiceDefaults**: A shared class library to hold configurations that can be reused across the projects in your solution.

### Add the Worker Service project
Expand Down Expand Up @@ -120,12 +115,12 @@ In the _Program.cs_ file of the _AspireStorage_ project, add calls to the <xref:

:::zone pivot="azurite"

:::code source="snippets/tutorial/AspireStorage/AspireStorage/AspireStorage/Program.cs" highlight="3-4,8-9,27-33":::
:::code source="snippets/tutorial/AspireStorage/AspireStorage/Program.cs" highlight="2-3,7-8,30-39":::

:::zone-end
:::zone pivot="azure-portal,azure-cli"

:::code source="snippets/tutorial/AspireStorage/AspireStorage/AspireStorage/Program.cs" range="1-25,34-52" highlight="3-4,8-9":::
:::code source="snippets/tutorial/AspireStorage/AspireStorage/AspireStorage/Program.cs" range="1-27,41-50" highlight="2-3,7-8":::

:::zone-end

Expand Down Expand Up @@ -176,29 +171,42 @@ In the _appsettings.json_ file of the _AspireStorage.Worker_ project, add the co

## Create the form

The application requires a form for the user to be able to submit support ticket information and upload an attachment. The app uploads the attached file on the `Document` property to Azure Blob Storage using the injected <xref:Azure.Storage.Blobs.BlobServiceClient?displayProperty=fullName>. The <xref:Azure.Storage.Queues.QueueServiceClient?displayProperty=fullName> sends a message composed of the `Title` and `Description` to the Azure Storage Queue.
The app requires a form for the user to be able to submit support ticket information and upload an attachment. The app uploads the attached file on the `Document` property to Azure Blob Storage using the injected <xref:Azure.Storage.Blobs.BlobServiceClient>. The <xref:Azure.Storage.Queues.QueueServiceClient> sends a message composed of the `Title` and `Description` to the Azure Storage Queue.

Use the following Razor markup to create a basic form, replacing the contents of the _Home.razor_ file in the _AspireStorage/Components/Pages_ directory:

:::code language="razor" source="snippets/tutorial/AspireStorage/AspireStorage/AspireStorage/Components/Pages/Home.razor":::
:::code language="razor" source="snippets/tutorial/AspireStorage/AspireStorage/Components/Pages/Home.razor":::

:::zone pivot="azurite"
For more information about creating forms in Blazor, see [ASP.NET Core Blazor forms overview](/aspnet/core/blazor/forms).

## Update the AppHost

The _AspireStorage.AppHost_ project is the orchestrator for your app. It's responsible for connecting and configuring the different projects and services of your app. The orchestrator should be set as the startup project.

:::zone pivot="azurite"

Add the [Aspire.Hosting.Azure](https://www.nuget.org/packages/Aspire.Hosting.Azure) NuGet package to your _AspireStorage.AppHost_ project:

```dotnetcli
dotnet add package Aspire.Hosting.Azure --prerelease
```

Next, replace the contents of the _Program.cs_ file in the _AspireStorage.AppHost_ project with the following code:
:::zone-end

Replace the contents of the _Program.cs_ file in the _AspireStorage.AppHost_ project with the following code:

:::zone pivot="azurite"

:::code source="snippets/tutorial/AspireStorage/AspireStorage.AppHost/Program.cs":::

The preceding code adds Azure storage, blobs, and queues, and when in development mode, it uses the emulator.
The preceding code adds Azure storage, blobs, and queues, and when in development mode, it uses the emulator. Each project defines references for these resources that they depend on.

:::zone-end
:::zone pivot="azure-portal,azure-cli"

:::code source="snippets/tutorial/AspireStorage/AspireStorage.AppHost/Program.cs" range="1-5,11-23:::

The preceding code adds Azure storage, blobs, and queues, and defines references for these resources within each project that depend on them.

:::zone-end

Expand All @@ -210,30 +218,47 @@ When a new message is placed on the `tickets` queue, the worker service should r

:::code source="snippets/tutorial/AspireStorage/AspireStorage.Worker/Worker.cs":::

Before the worker service can process messages, it needs to be able to connect to the Azure Storage queue. With Azurite, you need to ensure that the queue is available before the worker service starts executing messaging queue processing.
Before the worker service can process messages, it needs to be able to connect to the Azure Storage queue. With Azurite, you need to ensure that the queue is available before the worker service starts executing message queue processing.

:::zone-end
:::zone pivot="azure-portal,azure-cli"

:::code source="snippets/tutorial/AspireStorage/AspireStorage.Worker/Worker.cs" range="1-11,15-37":::

The worker service processes messages by connecting to the Azure Storage queue, and pulling messages off the queue.

:::zone-end

The worker service processes message in the queue and deletes them when they've been processed.

## Run and test the app locally

The sample app is now ready for testing. Verify that the submitted form data is sent to Azure Blob Storage and Azure Queue Storage by completing the following steps:

1. Press the run button at the top of Visual Studio to launch your .NET Aspire app dashboard in the browser.
1. On the projects page, in the **asireweb** row, click the link in the **Endpoints** column to open the UI of your app.
1. On the projects page, in the **aspirestorage** row, click the link in the **Endpoints** column to open the UI of your app.

:::image type="content" source="../media/support-app.png" alt-text="A screenshot showing the home page of the .NET Aspire support application.":::

1. Enter sample data into the `Title` and `Description` form fields and select a simple file to upload.
1. Press submit, and the page should reload.
1. In a separate browser tab, use the Azure portal to navigate to the **Storage browser** in your Azure Storage Account.
1. Select **Containers** and then navigate into the **Documents** container to see the uploaded file.
1. You can verify the message on the queue was processed by searching for the **Message Processed** log from the the worker service in the Visual Studio output window.
1. You can verify the message on the queue was processed by looking at the **Project logs** of the [.NET Aspire dashboard](../dashboard.md).

:::image type="content" source="../media/queue-output.png" alt-text="A screenshot showing the console output of the Worker app.":::

Congratulations! You created and configured an ASP.NET Core app app that connects to Azure Storage using .NET Aspire components.
## Summary

The example app that you built demonstrates persisting blobs from an ASP.NET Core Blazor Web App and processing queues in a [.NET Worker Service](/dotnet/core/extensions/workers). Your app connects to Azure Storage using .NET Aspire components. The app sends the support tickets to a queue for processing and uploads an attachment to storage.

:::zone pivot="azurite"

Since you choose to use Azurite, there's no need to clean up these resources when you're done testing them, as you created them locally in the context of an emulator. The emulator enabled you to test your app locally without incurring any costs, as no Azure resources were provisioned or created.

:::zone-end
:::zone pivot="azure-portal,azure-cli"

Don't forget to clean up any Azure resources when you're done testing them.

:::zone-end
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@
builder.AddProject<Projects.AspireStorage_Worker>("aspirestorage.worker")
.WithReference(queues);

builder.Build().Run();
var app = builder.Build();
app.Run();
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>dotnet-AspireStorage.Worker-fd5a2620-2124-439c-93f0-ee86d7e4ca9a</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Azure.Storage.Queues" Version="8.0.0-preview.1.23557.2" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.13.1" />
<PackageReference Include="Azure.Storage.Files.Shares" Version="12.1.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
</ItemGroup>

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
{
"dependencies": {
"secrets1": {
"type": "secrets"
}
}
"dependencies": {}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
{
"dependencies": {
"secrets1": {
"type": "secrets.user"
}
}
"dependencies": {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ConnectionStrings": {
"QueueConnection": "https://{account_name}.queue.core.windows.net/"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Azure.Storage.Blobs" Version="8.0.0-preview.1.23557.2" />
<PackageReference Include="Aspire.Azure.Storage.Queues" Version="8.0.0-preview.1.23557.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\AspireStorage.ServiceDefaults\AspireStorage.ServiceDefaults.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,65 @@
@page "/"

@using System.ComponentModel.DataAnnotations
@using Azure.Storage.Blobs
@using Azure.Storage.Queues

@inject BlobServiceClient BlobClient
@inject QueueServiceClient QueueServiceClient

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>
<div class="text-center">
<h1 class="display-4">Request Support</h1>
</div>

<EditForm Model="@Ticket" FormName="Tickets" method="post"
OnValidSubmit="@HandleValidSubmit" enctype="multipart/form-data">
<DataAnnotationsValidator />
<ValidationSummary />

<div class="mb-4">
<label>Issue Title</label>
<InputText class="form-control" @bind-Value="@Ticket.Title" />
<ValidationMessage For="() => Ticket.Title" />
</div>
<div class="mb-4">
<label>Issue Description</label>
<InputText class="form-control" @bind-Value="@Ticket.Description" />
<ValidationMessage For="() => Ticket.Description" />
</div>
<div class="mb-4">
<label>Attachment</label>
<InputFile class="form-control" name="Ticket.Document" />
<ValidationMessage For="() => Ticket.Document" />
</div>
<button class="btn btn-primary" type="submit">Submit</button>
</EditForm>

@code {
[SupplyParameterFromForm(FormName = "Tickets")]
private SupportTicket Ticket { get; set; } = new();

private async Task HandleValidSubmit()
{
var docsContainer = BlobClient.GetBlobContainerClient("fileuploads");

// Upload file to blob storage
await docsContainer.UploadBlobAsync(
Ticket.Document.FileName,
Ticket.Document.OpenReadStream());

// Send message to queue
var queueClient = QueueServiceClient.GetQueueClient("tickets");

await queueClient.SendMessageAsync(
$"{Ticket.Title} - {Ticket.Description}");
}

Welcome to your new app.
private class SupportTicket()
{
[Required] public string Title { get; set; } = default!;
[Required] public string Description { get; set; } = default!;
[Required] public IFormFile Document { get; set; } = default!;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
using AspireStorage.Components;
using AspireStorage.Components;
using Azure.Storage.Blobs;
using Azure.Storage.Queues;

var builder = WebApplication.CreateBuilder(args);

builder.AddAzureBlobService("BlobConnection");
builder.AddAzureQueueService("QueueConnection");

builder.AddServiceDefaults();

// Add services to the container.
Expand All @@ -16,9 +21,23 @@
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
// The default HSTS value is 30 days. You may want to change this for production
// scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
else
{
// In development, create the blob container and queue if they don't exist.
var blobService = app.Services.GetRequiredService<BlobServiceClient>();
var docsContainer = blobService.GetBlobContainerClient("fileuploads");

await docsContainer.CreateIfNotExistsAsync();

var queueService = app.Services.GetRequiredService<QueueServiceClient>();
var queueClient = queueService.GetQueueClient("tickets");

await queueClient.CreateIfNotExistsAsync();
}

app.UseHttpsRedirection();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"BlobConnection": "https://{account_name}.blob.core.windows.net/",
"QueueConnection": "https://{account_name}.queue.core.windows.net/"
}
},
"AllowedHosts": "*"
}

0 comments on commit d4db8b5

Please sign in to comment.