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
mediayourself. The site doesn't create it, and uploads fail until it exists. - Never give
mediacontainer-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. formscreates 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
mediacontainer'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
- In the CMS, upload an image to Shared Media.
- In the Azure portal, open the
mediacontainer. The file is underimages/. - 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.