Save Uploaded Files To Azure Blob Storage In ASP.NET Core
In my case, I had an ASP.NET Core MVC app where Step Two of onboarding accepted files, but only saved a database row. I needed the upload to actually land in Azure Blob Storage, under a folder named after the client uid.
This post is the exact sequence I followed.
1. Add the Azure Storage packages
Start with the SDK packages for blobs and Azure identity.
Azure.Storage.Blobs gives you BlobContainerClient and BlobClient. Azure.Identity gives you DefaultAzureCredential, which is useful when you want the same code to work locally and in Azure.
If you already have a connection string, AccountName becomes optional for local runs. I still left it there because the same options class also supports identity-based auth.
publicAzureBlobClientFileStorage(IOptions<AzureBlobStorageOptions> options) { var settings = options.Value; var containerName = settings.ContainerName?.Trim(); if (string.IsNullOrWhiteSpace(containerName)) { thrownew InvalidOperationException("Azure Blob Storage container name is not configured."); }
if (!string.IsNullOrWhiteSpace(settings.ConnectionString)) { containerClient = new BlobContainerClient(settings.ConnectionString, containerName); return; }
var accountName = settings.AccountName?.Trim(); if (string.IsNullOrWhiteSpace(accountName)) { thrownew InvalidOperationException("Azure Blob Storage account name is not configured."); }
var serviceUri = new Uri($"https://{accountName}.blob.core.windows.net"); var serviceClient = new BlobServiceClient(serviceUri, new DefaultAzureCredential()); containerClient = serviceClient.GetBlobContainerClient(containerName); }
publicasync Task UploadAsync(Guid clientUid, string fileName, Stream content, string? contentType, CancellationToken cancellationToken = default) { var sanitizedFileName = Path.GetFileName(fileName).Trim(); if (string.IsNullOrWhiteSpace(sanitizedFileName)) { thrownew InvalidOperationException("File name is required."); }
var blobClient = containerClient.GetBlobClient($"{clientUid:D}/{sanitizedFileName}");
try { await blobClient.UploadAsync( content, new BlobUploadOptions { HttpHeaders = new BlobHttpHeaders { ContentType = string.IsNullOrWhiteSpace(contentType) ? "application/octet-stream" : contentType } }, cancellationToken); } catch (RequestFailedException exception) when (exception.ErrorCode == BlobErrorCode.BlobAlreadyExists) { thrownew InvalidOperationException( "A file with the same name already exists for this client.", exception); } } }
The important bit is this line:
1
var blobClient = containerClient.GetBlobClient($"{clientUid:D}/{sanitizedFileName}");
That gives you a blob path like 1ee45be0-202d-4c2b-91ba-2eb7362dcf52/my-file.pdf. Azure Blob Storage does not have real folders, but that prefix behaves like one in the portal and SDK.
4. Upload the file before inserting the database row
My original bug was simple: the app inserted metadata, but never persisted the file itself. The fix was to make blob upload the gate before InsertAsync(...).
if (fileisnull || file.Length == 0) { TempData["OnboardingMessage"] = "Please choose a file to upload."; return RedirectToAction(nameof(StepTwo), new { uid }); }
var fileTypeDescription = ResolveFileTypeDescription(group); if (fileTypeDescription isnull) { TempData["OnboardingMessage"] = "Unsupported file group."; return RedirectToAction(nameof(StepTwo), new { uid }); }
var fileTypeId = await fileRepository.GetFileTypeIdAsync(fileTypeDescription, cancellationToken); if (!fileTypeId.HasValue) { TempData["OnboardingMessage"] = "File type could not be resolved."; return RedirectToAction(nameof(StepTwo), new { uid }); }
var fileName = Path.GetFileName(file.FileName); awaitusingvar fileStream = file.OpenReadStream();
For local development, a connection string is the fastest way to prove the upload path works.
For Azure-hosted environments, I would rather use managed identity or a service principal with the right blob permissions than ship account keys around.