The remote server returned an error: (400) Bad Request – when configuring Azure Storage for an older Optimizely CMS site
I recently updated about eight Optimizely sites still running Optimizely CMS 11 on various flavours of the .NET Framework, so that they (amongst other things) could store editor uploaded files as blobs in an Azure Storage account rather than on a local fileshare.
What I tried to do
The job was pretty straightforward.
- Create an Azure Storage resource with a blob container
- Generate SAS token for the container
- Transfer the blobs using azcopy copy and the SAS token
- Install the NuGet package EPiServer.Azure
- Configure the blob provider in episerver.framework.config using the connection string and the name of the container
The problem
For four of the sites this worked perfectly, as expected. For the four remaining sites, I was faced with the exception below.
Initial thought
I didn't know where to start, as the error message wasn't very helpful. The four solutions with this error message didn't seem to have much in common.
- They all used different versions of CMS 11
- They used three different versions of the .NET Framework ranging from 4.6.2 to 4.8
- Some used the old Episerver.Search, some used Search & Navigation
- Some used Episerver.Forms, others did not
First attempt, and a bright idea
My guess was that the server probably returned a message in addition to «The remote server returned an error: (400) Bad Request.» stating what the problem was. At first I had no luck debugging my way to such a message, so I fired up Fiddler as a system proxy, thinking I could inspect the response there.
When Fiddler fired up, everything magically worked! The images were loaded from Azure Storage, and there was no exception to inspect!
My first idea was that this could have something with TLS to do, as Fiddler might make some adaptations there. Some of the solutions already had something like this in Global.asax.cs, but I tested the remaining. It did not help.
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
Another attempt
I then tested to run the same call (that failed above) from code I had more control over, so I added this to a controller.
using Microsoft.WindowsAzure.Storage;
var cloudBlobContainer = CloudStorageAccount.Parse(connectionString)
.CreateCloudBlobClient()
.GetContainerReference(containerName);
if (cloudBlobContainer.Exists())
{
Console.WriteLine("Hello Blobs!");
}
It worked! Then I removed the code adding TLS 1.2 from Global.asax.cs, and it failed! Now I added a breakpoint to the line with the call to Exists()
, and I was able to find the response using the debugger too! It said what I expected from the start:
The TLS version of the connection is not permitted on this storage account.
The StatusDescription from the response says that wrong TLS version is the issue:
Time to rethink
So when I write the code communicating with Azure Storage, my TLS-settings from Global.asax are respected.
When Optimizely wrote code that sits in EPiServer.Azure
, that calls code in Microsoft.WindowsAzure.Storage
, my TLS-settings are not respected.
Also, according to the documentation, it shouldn't be necessary to move to TLS 1.2 until November 2025.
The solution
The explanation is, of course, that Optimizely's code runs in an initialization module that executes before Application_Start in Global.asax.cs. So how do I make sure my code runs before the initialization module? Add another initialization module?
It turns out you can perform some «black magic» in web.config
!
By adding the targetFramework
attribute to the <httpRuntime>
element, you can influence the default TLS settings used by the application.
This resolved the issue for all four of my affected solutions, as they had been missing the targetFramework
attribute, unlike the ones that worked from the start.
Simply match the framework version used in your .csproj file, like this:
<httpRuntime requestValidationMode="2.0" targetFramework="4.6.2" />
That's it!