HTTP Redirect with non-ASCII characters in the URL

Another day, another small learning.

SITUATION

I have been extending ASP.NET Core REST API service that returns redirect (HTTP 301) results for a query. I would submit something like /redirect/A10293 and it would look up the associated SharePoint library URL for that entity.

/redirect/A10293 would then be redirected to https://sharepoint/site/library.

The customer wanted to add another piece of input, to be passed as a parameter of type string: the description of the entity. In SharePoint I had made a SPFx application customizer extension that showed this information in the header.

The new flow would be:

/redirect/A10293?name=Hello%20World should return a redirect to https://sharepoint/site/library?name=Hello%20World.

SYMPTOMS

In testing, we found out that for some entity names the service would return a Server error (HTTP 500). In particular, for the names with strange characters in the name, such as German umlauts like Müller.

/redirect/A10293?name=Hello%20Herr%20M%C3%BCller would fail with a 500.

CAUSE

Thanks to my colleague Christoph König (yes, with umlaut in the surname).

The cause of the error is that HTTP redirects are restricted to ASCII characters in the redirect result, as per the RFC.

I (non readable character) non-ascii characters
I do, really. Especially my surname last character: ć. Thanks Unicode!

In the code I was constructing a string and adding the name parameter as a string. It was being automatically URL-decoded by ASP.NET, and the redirection URL was then something like https://sharepoint/site/library?name=Hello Herr Müller. This tripped the redirect ActionResult parser to raise an exception.

SOLUTION

Use the System.Uri class to parse the string and return its AbsoluteUri property. A string, with the canonical, encoded version safe to be used in redirect result.

string redirectUrl = " https://sharepoint/site/library?name=Hello Herr Müller ";
Uri redirectURI = new Uri(redirectUrl);
return RedirectPermanent(redirectURI.AbsoluteUri);

Static Initializers in C#: A Cautionary Tale

I have been chasing a weird bug in a solution I’ve been working on. The insight on how certain C# feature work by design, which I got after catching of the culprit, made me write this post in the hope that somebody can save hours or days of debugging.

My solution uses SPMeta2 library to define a structure of a SharePoint site to be provisioned. You specify a collection of “nodes” that represent your lists, content types, site columns and so on. It can be then “provisioned” on the target side using CSOM or SSOM.

Static Function (yes, it’s PHP but it gets the point, right?)

The structure was defined using static properties on static classes such as FieldDefinitions.cs or ContentTypeDefinitions.cs. A new content type, for instance, would be defined as a ContentTypeDefinition property initialized inline with a static initializer.

        public static ContentTypeDefinition BaseContentType = new ContentTypeDefinition()
        {
            Name = "BaseContentType",
            Id = new Guid("EC0463EA-2AA1-4461-8407-2A4D0FD58B8B"),
            ParentContentTypeId = BuiltInContentTypeId.Document,
            Group = GroupNames.MyContentTypeGroupName
        };

However, when I retrieved a template (a sum of all ModelNodes in the definition) I would get a weird System.TypeInitializationException in the nodes collection.

The source of my headache for a couple of days (simplified)

I spent a couple of days trying to find the root of the issue. As I couldn’t debug static initializers, I had to rely on removing and adding nodes to see what triggers the error. Finally I got it.

The offending code was like this:

public static class ContentTypeDefinitions 
{
        public static ContentTypeDefinition ContentType1 = new ContentTypeDefinition()
        {
            Name = "ContentType1",
            ...
            ParentContentTypeId = ContentTypeDefinitions.ContentType2.Id
        };

        public static ContentTypeDefinition ContentType2 = new ContentTypeDefinition()
        {
            Name = "ContentType2",
            ...
            ParentContentTypeId = BuiltInContentTypeId.Document
        };
...
}

Do you see the error?

ContentType1 (a static field) relies on the ContentType2 definition (another static field) to be initialized. However, ContentType2 is defined AFTER the ContentType1 in the source code. But, since when the order of definitions in C# is important? Shouldn’t the strongly-typed nature of C# make it irrelevant?

As per C# specifications, section 10.11 Static Constructors says:

If a class contains any static fields with initializers, those initializers are executed in textual order immediately prior to executing the static constructor.

C# 1.2 Language Specification, section 10.11 paragraph 7.

I fixed the code just by reordering the static fields:

public static class ContentTypeDefinitions 
{
        public static ContentTypeDefinition ContentType2 = new ContentTypeDefinition()
        {
            Name = "ContentType2",
            ...
            ParentContentTypeId = BuiltInContentTypeId.Document
        };

        public static ContentTypeDefinition ContentType1 = new ContentTypeDefinition()
        {
            Name = "ContentType1",
            ...
            ParentContentTypeId = ContentTypeDefinitions.ContentType2.Id
        };

...
}

And it just works.

If you want to see it by yourself, this is a minimal demo that makes the error. You can run it inside DotNetFiddle.

As soon as the order of static fields is reversed, it works.

Materials from my Azure AD Session at NetCoreConf Barcelona 2019

Last Saturday I attended the NetCoreConf Barcelona, a free conference dedicated to .NET Core and related technologies.

I spoke about the key concepts and authentication flows in Azure Active Directory from the perspective of an average developer. I had a great time and I heard some good feedback about the session, so it might just have helped someone. There is no better reward.

Me speaking at NetCoreConf 2019 Barcelona

I used demos from the following sources on GitHub:

The slides are available on my OneDrive: