Developer Guide

Media storage

Keep media on local disk while you develop and in Azure Blob Storage when hosted, switched by configuration alone.

Uploaded images, videos and files, and the files visitors attach to forms, are stored by a media storage provider. Out of the box that is Local, which writes to App_Data/media on the server's own disk. That needs no setup, and it's the right choice while you develop.

When hosted, you'll usually want Azure Blob Storage instead. Files then live in a storage account rather than on the web server, so they survive redeploys and are shared by every server in a farm. The same build does both: configuration decides which provider is used.

1. Add the package and register it

Reference the package from your site's project:

dotnet add package Cendia.Storage.AzureBlob

Then register it in Program.cs, after AddCendia:

using Cendia.Storage.AzureBlob;

builder.Services.AddAzureBlobMediaStorage(
    builder.Configuration["AzureStorageConnectionString"]);

This makes Azure Blob available. It doesn't switch the site over. Until step 4, the site keeps using local disk.

2. Create a storage account

Give each site its own storage account. The account is what separates one site's media from another's, so the container names inside it stay the same everywhere.

Copy the account's connection string from Security + networking › Access keys in the Azure portal. It carries the account key, so treat it like a password.

3. Create the two containers

Container Access level Holds
media Blob (anonymous read access for blobs only) The media library. Visitors' browsers fetch these files directly from storage.
forms Private Files visitors attach to forms. Only the CMS can read them, through its own download route.
  • Create media yourself. The site doesn't create it, and uploads fail until it exists.
  • Never give media container-level access. That would let anyone list every file in it. Blob access serves a file to someone who already has its URL, and nothing more.
  • forms creates itself as private on the first visitor upload if it doesn't exist. Creating it yourself is fine too.
  • Your storage account must allow anonymous access for the media container's Blob setting to take effect: Settings › Configuration › Allow Blob anonymous access must be Enabled.

4. Configure the hosted site

Set these as environment variables on the host, or in your vault. Neither belongs in appsettings.json.

Variable Value
Cendia__MediaStorage__Provider AzureBlob
AzureStorageConnectionString The connection string from step 2

Leave appsettings.json on Local, so a fresh clone and your own machine keep working with no storage account:

{
  "Cendia": {
    "MediaStorage": {
      "Provider": "Local"
    }
  }
}

To try Azure Blob on your own machine, put the connection string in user-secrets and set the provider there too:

dotnet user-secrets set "AzureStorageConnectionString" "<connection string>"
dotnet user-secrets set "Cendia:MediaStorage:Provider" "AzureBlob"

Restart the site after changing either setting.

5. Check it

  1. In the CMS, upload an image to Shared Media.
  2. In the Azure portal, open the media container. The file is under images/.
  3. Open the image on a published page. Its URL points at https://<account>.blob.core.windows.net/media/images/….

Using a CDN or custom domain

Media URLs are built from the storage account's own hostname, so you don't need to set anything to serve files directly from storage. If a CDN or custom domain sits in front of the account, give its origin only:

{
  "Cendia": {
    "MediaStorage": {
      "PublicBaseUrl": "https://cdn.example.com"
    }
  }
}

Cendia appends /{container}/{path} itself, so this example produces https://cdn.example.com/media/images/<id>.png. Don't include the container or a path: setting it to …/media/images/ doubles them up and breaks every media URL.

Moving an existing site

Switching the provider doesn't copy anything. Files uploaded while the site was on Local stay in App_Data/media and stop showing. Before you switch, copy them into the containers, keeping their paths: App_Data/media/images/… goes to media/images/…, and App_Data/forms/… goes to forms/….

Troubleshooting

What you see Why, and what to do
The site fails with "Media storage is set to AzureBlob but no storage connection string was supplied" AzureStorageConnectionString isn't set for this environment. Set it, then restart.
Uploads fail with a ContainerNotFound error The media container doesn't exist in this storage account. Create it (step 3).
Uploads succeed but images don't load on the site The media container is private, or the account doesn't allow anonymous access. Check both settings in step 3.
Images that loaded before the switch are now missing They're still on local disk. See Moving an existing site.

Configuration reference

{
  "Cendia": {
    "MediaStorage": {
      "Provider": "Local",
      "LocalRootPath": "App_Data/media",
      "ContainerName": "media",
      "FormUploadsContainerName": "forms",
      "PublicBaseUrl": null
    }
  }
}

These are the defaults, so you only need to set the ones you change. LocalRootPath is used only by Local, and is relative to the site's content root unless it's a full path.