# Data Standard: Azure Blob Storage

## Overview
Object storage for files, images, documents, and large binary data.

## Setup
```xml
<PackageReference Include="Azure.Storage.Blobs" Version="12.*" />
```

```csharp
// Program.cs
builder.Services.AddSingleton(new BlobServiceClient(
    new Uri(config["Storage:AccountUrl"]),
    new DefaultAzureCredential())); // Managed Identity — no connection string
```

## Service Pattern
```csharp
public class BlobStorageService : IFileStorageService
{
    private readonly BlobContainerClient _container;

    public BlobStorageService(BlobServiceClient client, IConfiguration config)
    {
        _container = client.GetBlobContainerClient(config["Storage:ContainerName"]);
    }

    public async Task<string> UploadAsync(Stream content, string fileName, string contentType)
    {
        var blobName = $"{DateTime.UtcNow:yyyy/MM/dd}/{Guid.NewGuid()}/{fileName}";
        var blob = _container.GetBlobClient(blobName);

        await blob.UploadAsync(content, new BlobHttpHeaders { ContentType = contentType });
        return blobName; // Return blob path, not URL
    }

    public async Task<Stream> DownloadAsync(string blobName)
    {
        var blob = _container.GetBlobClient(blobName);
        var response = await blob.DownloadAsync();
        return response.Value.Content;
    }

    public async Task DeleteAsync(string blobName)
    {
        var blob = _container.GetBlobClient(blobName);
        await blob.DeleteIfExistsAsync();
    }

    public Uri GenerateSasUrl(string blobName, TimeSpan expiry)
    {
        var blob = _container.GetBlobClient(blobName);
        return blob.GenerateSasUri(BlobSasPermissions.Read, DateTimeOffset.UtcNow.Add(expiry));
    }
}
```

## Security Requirements

### Always Use Managed Identity
```csharp
// ✅ Managed Identity — no secrets
new BlobServiceClient(storageUri, new DefaultAzureCredential())

// ❌ Connection string — avoid
new BlobServiceClient(connectionString)
```

### SAS Tokens for Client Access
```csharp
// Generate short-lived SAS for client-side uploads
var sasUri = blob.GenerateSasUri(BlobSasPermissions.Write, DateTimeOffset.UtcNow.AddMinutes(15));
// Send SAS URL to client — they upload directly
```

### Container Access Levels
- `Private`: No public access (default for user documents)
- `Blob`: Public read for specific blobs (CDN-served images)
- `Container`: Public read for all blobs (avoid in most cases)

## Content Types + Validation
```csharp
private static readonly HashSet<string> AllowedTypes = ["image/jpeg", "image/png", "application/pdf"];
private const long MaxFileSizeBytes = 10 * 1024 * 1024; // 10 MB

public async Task<string> UploadValidatedAsync(IFormFile file)
{
    if (!AllowedTypes.Contains(file.ContentType))
        throw new ValidationException($"File type {file.ContentType} not allowed");
    if (file.Length > MaxFileSizeBytes)
        throw new ValidationException("File exceeds 10 MB limit");

    return await UploadAsync(file.OpenReadStream(), file.FileName, file.ContentType);
}
```

## Blob Lifecycle (Cost Management)
- Move to Cool tier after 30 days
- Move to Archive tier after 90 days
- Delete after 365 days (unless legal hold)
- Configure via Storage Account Lifecycle Management rules (Bicep)
