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.

The problem

For four of the sites this worked perfectly, as expected. For the four remaining sites, I was faced with the exception below.

The remote server returned an error: (400) Bad Request. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.  Exception Details: System.Net.WebException: The remote server returned an error: (400) Bad Request.  Source Error:  The source code that generated this unhandled exception can only be shown when compiled in debug mode. To enable this, please follow one of the below steps, then request the URL:  1. Add a Debug=true directive at the top of the file that generated the error. Example:    <%@ Page Language=C# Debug=true %>  or:  2) Add the following section to the configuration file of your application:  <configuration>    <system.web>        <compilation debug=true/>    </system.web> </configuration>  Note that this second technique will cause all files within a given application to be compiled in debug mode. The first technique will cause only that particular file to be compiled in debug mode.  Important: Running applications in debug mode does incur a memory/performance overhead. You should make sure that an application has debugging disabled before deploying into production scenario.  Stack Trace:   [WebException: The remote server returned an error: (400) Bad Request.]    System.Net.HttpWebRequest.GetResponse() +1751    Microsoft.WindowsAzure.Storage.Core.Executor.Executor.ExecuteSync(RESTCommand`1 cmd, IRetryPolicy policy, OperationContext operationContext) in c:\Program Files (x86)\Jenkins\workspace\release_dotnet_master\Lib\ClassLibraryCommon\Core\Executor\Executor.cs:722  [StorageException: The remote server returned an error: (400) Bad Request.]    Microsoft.WindowsAzure.Storage.Core.Executor.Executor.ExecuteSync(RESTCommand`1 cmd, IRetryPolicy policy, OperationContext operationContext) in c:\Program Files (x86)\Jenkins\workspace\release_dotnet_master\Lib\ClassLibraryCommon\Core\Executor\Executor.cs:649    EPiServer.Azure.Blobs.DefaultAzureBlobContainer.CreateIfNotExist() +295    System.Threading.Tasks.Task.Execute() +71  [AggregateException: One or more errors occurred.]    System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout, CancellationToken cancellationToken) +1264    EPiServer.Framework.<>c__DisplayClass2_0.<InitializeBlobProviders>b__0(Object o, EventArgs e) +21  [TargetInvocationException: Exception has been thrown by the target of an invocation.]    System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor) +0    System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments) +216    System.Delegate.DynamicInvokeImpl(Object[] args) +160    EPiServer.Framework.Initialization.InitializationEngine.OnInitComplete() +312    EPiServer.Framework.Initialization.InitializationEngine.ExecuteTransition(Boolean continueTransitions) +208    EPiServer.Framework.Initialization.InitializationModule.EngineExecute(HostType hostType, Action`1 engineAction) +408    EPiServer.Framework.Initialization.InitializationModule.FrameworkInitialization(HostType hostType) +226    EPiServer.Global..ctor() +106    Geonorge.EPiServerApplication..ctor() +43    ASP.global_asax..ctor() +46  [TargetInvocationException: Exception has been thrown by the target of an invocation.]    System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck) +0    System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark) +142    System.Activator.CreateInstance(Type type, Boolean nonPublic) +107    System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark) +1473    System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes) +186    System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture) +28    System.Web.HttpRuntime.CreateNonPublicInstance(Type type, Object[] args) +80    System.Web.HttpApplicationFactory.GetSpecialApplicationInstance(IntPtr appContext, HttpContext context) +182    System.Web.Hosting.PipelineRuntime.InitializeApplication(IntPtr appContext) +369  [HttpException (0x80004005): Exception has been thrown by the target of an invocation.]    System.Web.HttpRuntime.FirstRequestInit(HttpContext context) +532    System.Web.HttpRuntime.EnsureFirstRequestInit(HttpContext context) +111    System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context) +731

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.

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:

Tekst

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!

Found this post helpful? Help keep this blog ad-free by buying me a coffee! ☕