My blog is using Azure Blob Storage to store images for posts, however, it went wrong these days when I am trying out Azure CDN, the cause is all my images requests are returning incorrect content-type. Let's see how to reset content type for files in Azure Blob Storage via .NET Core.
When I upload images using the Azure Storage API for .NET, by default, without setting ContentType, which is also used as "mime-type" on a web server, Azure Blob Storage will treat it as "application/octet-stream". Browsers handle this content-type differently, most browsers will start to download the file instead of showing it in the browser window. This makes my blog images not able to show. I didn't run into this problem before is because I used to fetch images in Azure Blob Storage in my backend service, which will set the correct content-type before responding to the client.
Get Content-Type for Files
.NET Core doesn't have MimeMapping.GetMimeMapping() API. So, we need a workaround. Thanks to two members @刘命汉 and @周杰 in local .NET community, they found FileExtensionContentTypeProvider is a nice option. The source code is here. It basically hard-coded a complete extension mapping table inside the class. We can use TryGetContentType() to get the content type of a known type.
var pvd = new FileExtensionContentTypeProvider();
bool isKnownType = pvd.TryGetContentType("test.png", out string mimeType);
// mimeType: "image/png"
For unknown types, it will return false | null, but it's OK for Azure Blob Storage because not setting ContentType on CloudBlockBlob object will result in "application/octet-stream" which is desired.
Update Content-Type for Files in Azure Blob Storage
For files that already uploaded to Azure Blob Storage. We can update the content type programmatically. Just set CloudBlockBlob.Properties.ContentType and save it by SetPropertiesAsync()
For files that are not uploaded to Azure Blob Storage, after setting CloudBlockBlob.Properties.ContentType we don't need to call SetPropertiesAsync() because UploadFromStreamAsync() contains that information. Refer to this commit in my blog system.
Made an Open-Source Tool
I wrote an open-source tool https://github.com/EdiWang/AzureBlobMimeTypeReset for reset content type (mime type) for any Azure Blob Storage. Key methods are:
Getting CloudBlobContainer
private static CloudBlobContainer GetBlobContainer()
{
CloudStorageAccount storageAccount = new CloudStorageAccount(new StorageCredentials(Options.AccountName, Options.AccountKey), true);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(Options.ContainerName);
return container;
}
Modify ContentType
Update only if ContentType is incorrect.
private static CloudBlockBlob TrySetContentType(CloudBlockBlob blob, string contentType)
{
if (blob.Properties.ContentType.ToLower() != contentType)
{
blob.Properties.ContentType = contentType;
return blob;
}
return null;
}
Iterate and update every file
var pvd = new FileExtensionContentTypeProvider();
foreach (var blob in BlobContainer.ListBlobs().OfType<CloudBlockBlob>())
{
string extension = Path.GetExtension(blob.Uri.AbsoluteUri).ToLower();
bool isKnownType = pvd.TryGetContentType(extension, out string mimeType);
if (isKnownType)
{
if (TrySetContentType(blob, mimeType) != null)
{
await blob.SetPropertiesAsync();
}
}
}
Reference: http://www.thepatrickdavis.com/blob-storage-dont-forget-the-mime/
Update 12/18/2019
In the latest (12.x+) New Azure SDK for .NET, setting content type needs to be done via BlobHttpHeaders
object, example code:
BlobClient blob = _container.GetBlobClient(fileName);
// Why .NET Core doesn't have MimeMapping.GetMimeMapping()
var blobHttpHeader = new BlobHttpHeaders();
string extension = Path.GetExtension(blob.Uri.AbsoluteUri);
switch (extension.ToLower())
{
case ".jpg":
case ".jpeg":
blobHttpHeader.ContentType = "image/jpeg";
break;
case ".png":
blobHttpHeader.ContentType = "image/png";
break;
case ".gif":
blobHttpHeader.ContentType = "image/gif";
break;
default:
break;
}
await using (var fileStream = new MemoryStream(imageBytes))
{
var uploadedBlob = await blob.UploadAsync(fileStream, blobHttpHeader);
}
Mark
Thanks Edi, Nice work, this was super helpful.
Matt Drouillard
I think I am having the same issues with my recent upgrade to .NET Core 3.1. I am using the CloudFile class though and for some reason the content types are not being set on upload, so processes that require the content type when using the generated URL to get the file break.
Raf
Thanks so much for doing this! Very helpful.