Skip to content

Develop Solutions that Use Azure Blob Storage

This guide covers the AZ-204 exam topics for developing solutions with Azure Blob Storage:

  • Set and retrieve properties and metadata
  • Perform operations on data by using the appropriate SDK
  • Implement storage policies and data lifecycle management

Prerequisites

  • .NET SDK 8.0 (Install via winget: `winget install Microsoft.DotNet.SDK.8))
  • Azure CLI (Install)
  • Azure Functions Core Tools (Install)
  • Azure Subscription (Free tier or $200 credit recommended)
  • Existing Resource Group (az204exam)

Set Up Blob Storage

Note: We set up taskmanagerstoreyourname in Functions for task JSONs (tasks container). Here, we’ll add attachments for files to keep them separate.

Follow these steps to set up Blob Storage:

  1. Verify the Storage Account.

  2. Create an attachments container.

  3. Confirm the connection string in the Function App.

Step 1: Verify Storage Account

In the Azure Portal, go to Storage accounts. Find taskmanagerstoreyourname. If missing, create:

  • Name: taskmanagerstoreyourname.
  • Resource group: az204.
  • Region: West US3.
  • Primary Service: Azure Blob Storage or Azure Data Lake Storage Gen 2.
  • Redundancy: LRS.

Step 2: Create Attachments Container

In taskmanagerstoreyourname, go to Containers > + Container. Name: attachments. Click Create.

Step 3: Confirm Connection String

Go to Access keys > Copy Connection string for key1.

In your Function App (taskmanagerfunc-yourname), go to Configuration > Application settings. Verify StorageConnection (from Functions). If missing, add:

  • Name: StorageConnection, Value: [your-connection-string].
  • Save and restart.

Perform Operations with the SDK

Note: We’ll add Functions to upload and download files to attachments using the Blob SDK, prepping for web app file uploads.

Follow these steps to perform operations with the Blob Storage SDK:

  1. Add the Blob Storage SDK.

  2. Create an Upload Function.

  3. Test locally (optional).

  4. Deploy and test in Azure.

Step 1: Add Blob SDK

In TaskManagerFunctions, run (if not added):

dotnet add package Azure.Storage.Blobs

Step 2: Create Upload Function

Create a new HTTP-triggered Function.

cd TaskManagerFunctions
func new --template "HttpTrigger" --name UploadAttachment

Edit UploadAttachment.cs:

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;

namespace TaskManagerFunctions
{
    public class UploadAttachment
    {
        private readonly ILogger<UploadAttachment> _logger;

        public UploadAttachment(ILogger<UploadAttachment> logger)
        {
            _logger = logger;
        }

        [Function("UploadAttachment")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req)
        {
            try
            {
                _logger.LogInformation("Uploading file to attachments.");

                // Validate content type
                if (!req.HasFormContentType)
                {
                    _logger.LogWarning("Invalid content type: {ContentType}", req.ContentType);
                    return new BadRequestObjectResult("Please send a multipart form.");
                }

                // Parse form
                var form = await req.ReadFormAsync();
                var file = form.Files.FirstOrDefault();
                var taskId = form["taskId"];

                _logger.LogInformation("Form parsed. File: {FileName}, TaskId: {TaskId}", file?.FileName, taskId);

                // Validate form data
                if (file == null || string.IsNullOrEmpty(taskId))
                {
                    _logger.LogWarning("Missing file or taskId.");
                    return new BadRequestObjectResult("Please provide a file and taskId.");
                }

                // Get storage connection
                var connectionString = Environment.GetEnvironmentVariable("StorageConnection");
                if (string.IsNullOrEmpty(connectionString))
                {
                    _logger.LogError("StorageConnection environment variable is missing or empty.");
                    return new StatusCodeResult(500);
                }

                // Initialize blob client
                _logger.LogInformation("Initializing BlobServiceClient.");
                var blobServiceClient = new BlobServiceClient(connectionString);
                var containerClient = blobServiceClient.GetBlobContainerClient("attachments");

                // Create container if it doesn't exist
                _logger.LogInformation("Ensuring attachments container exists.");
                await containerClient.CreateIfNotExistsAsync();

                // Upload blob
                var blobName = $"{taskId}/{file.FileName}";
                var blobClient = containerClient.GetBlobClient(blobName);
                _logger.LogInformation("Uploading file to blob: {BlobName}", blobName);

                using (var stream = file.OpenReadStream())
                {
                    await blobClient.UploadAsync(stream, new BlobUploadOptions
                    {
                        HttpHeaders = new BlobHttpHeaders { ContentType = file.ContentType },
                        Metadata = new Dictionary<string, string> { { "TaskId", taskId } }
                    });
                }

                _logger.LogInformation("Uploaded: {BlobName}", blobName);
                return new OkObjectResult($"File uploaded: {blobName}");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to upload file. Exception: {Message}, StackTrace: {StackTrace}", ex.Message, ex.StackTrace);
                return new ObjectResult($"Error uploading file: {ex.Message}") { StatusCode = 500 };
            }
        }
    }
}

Step 3: Test Locally (Optional)

Update local.settings.json:

{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
        "StorageConnection": "[your-connection-string]"
    }
}

Run the Function locally:

func start

POST (use Postman or curl, with a file like test.txt):

curl -X POST http://localhost:7071/api/UploadAttachment -F "taskId=test123" -F "file=@test.txt"

Check the attachments container (Azurite or Azure Portal) for test123/test.txt.

Step 4: Deploy and Test

Deploy the Function to Azure:

func azure functionapp publish taskmanagerfunc-yourname

Retrieve the Function key:

az functionapp function keys list --name taskmanagerfunc-yourname --resource-group az204 --function-name UploadAttachment

Test:

curl -X POST https://taskmanagerfunc-yourname.azurewebsites.net/api/uploadattachment?code=[your-key] -F "taskId=test123" -F "file=@test.txt"

Verify in Azure Portal > taskmanagerstoreyourname > attachments.

Why

Uploads files with the SDK, showing create operations for the exam.

Set and Retrieve Properties and Metadata

Note: We’ll get the file’s content type and metadata (task ID) to show exam skills.

Follow these steps to set and retrieve properties and metadata:

  1. Create a Download Function.

  2. Test locally (optional).

  3. Deploy and test in Azure.

Step 1: Create Download Function

Create a new HTTP-triggered Function.

cd TaskManagerFunctions
func new --template "HttpTrigger" --name GetAttachment

Edit GetAttachment.cs:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Azure.Storage.Blobs;
using Azure;

namespace TaskManagerFunctions
{
    public class GetAttachment
    {
        private readonly ILogger<GetAttachment> _logger;

        public GetAttachment(ILogger<GetAttachment> logger)
        {
            _logger = logger;
        }

        [Function("GetAttachment")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req)
        {
            try
            {
                _logger.LogInformation("Retrieving file metadata.");

                // Validate query parameters
                string? taskId = req.Query["taskId"];
                string? fileName = req.Query["fileName"];
                _logger.LogInformation("Query parameters: taskId={TaskId}, fileName={FileName}", taskId, fileName);

                if (string.IsNullOrEmpty(taskId) || string.IsNullOrEmpty(fileName))
                {
                    _logger.LogWarning("Missing taskId or fileName in query parameters.");
                    return new BadRequestObjectResult("Please provide taskId and fileName.");
                }

                // Get storage connection
                var connectionString = Environment.GetEnvironmentVariable("StorageConnection");
                if (string.IsNullOrEmpty(connectionString))
                {
                    _logger.LogError("StorageConnection environment variable is missing or empty.");
                    return new StatusCodeResult(500);
                }

                // Initialize blob client
                _logger.LogInformation("Initializing BlobServiceClient.");
                var blobServiceClient = new BlobServiceClient(connectionString);
                var containerClient = blobServiceClient.GetBlobContainerClient("attachments");
                var blobName = $"{taskId}/{fileName}";
                var blobClient = containerClient.GetBlobClient(blobName);
                _logger.LogInformation("Accessing blob: {BlobName}", blobName);

                // Get blob properties
                var properties = await blobClient.GetPropertiesAsync();
                var metadata = properties.Value.Metadata;
                var contentType = properties.Value.ContentType;

                // Safely access metadata
                string? metadataTaskId = metadata.ContainsKey("TaskId") ? metadata["TaskId"] : null;

                _logger.LogInformation("Blob properties retrieved. TaskId={TaskId}, ContentType={ContentType}, Uri={Uri}",
                    metadataTaskId, contentType, blobClient.Uri);

                return new OkObjectResult(new
                {
                    TaskId = metadataTaskId,
                    ContentType = contentType,
                    Uri = blobClient.Uri
                });
            }
            catch (RequestFailedException ex) when (ex.Status == 404)
            {
                _logger.LogWarning("Blob not found: {ErrorMessage}", ex.Message);
                return new NotFoundResult();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to retrieve file metadata. Exception: {Message}, StackTrace: {StackTrace}",
                    ex.Message, ex.StackTrace);
                return new ObjectResult($"Error retrieving file metadata: {ex.Message}") { StatusCode = 500 };
            }
        }
    }
}

Step 2: Test Locally (Optional)

Run the Function locally:

func start

GET:

curl http://localhost:7071/api/GetAttachment?taskId=test123&fileName=test.txt

Expect JSON: {"TaskId":"test123","ContentType":"text/plain","Uri":"..."}.

Step 3: Deploy and Test

Deploy the Function to Azure:

func azure functionapp publish taskmanagerfunc-yourname

Retrieve the Function key:

az functionapp function keys list --name taskmanagerfunc-yourname --resource-group az204 --function-name GetAttachment

Test:

curl https://taskmanagerfunc-yourname.azurewebsites.net/api/getattachment?code=[your-key]&taskId=test123&fileName=test.txt

Verify metadata and properties.

Why

Shows setting and getting metadata/properties, a key exam topic.

Implement Storage Policies and Data Lifecycle Management

Note: We’ll delete old files in attachments to manage costs.

Follow these steps to implement storage policies and data lifecycle management:

  1. Create a lifecycle rule.

  2. Verify the lifecycle rule.

Step 1: Create Lifecycle Rule

In the Azure Portal, go to taskmanagerstoreyourname > Data Management > Lifecycle management > Add rule.

  • Rule name: ArchiveOldFiles.
  • Scope: Filter to attachments/ (type attachments/ in Blob prefix).
  • Conditions:
  • Blob type: Block blobs.
  • Not modified for 1 days.
  • Actions:
  • Delete Blob.
  • Feel free to set up your own rules here.
  • Click Add.

Step 2: Verify

  • Upload a file via UploadAttachment.
  • Wait 1 day (or use a test account to simulate).
  • Check attachments > Confirm old files are deleted.

Why

Lifecycle rules reduce storage costs, an exam must-know.