Reducing the length of GUIDs in URLs or Json payloads

  • Gérald Barré

GUIDs are commonly used as a key in a database. If you want to create a URL to a specific record, you may want to use the GUID in the URL. Now you have a problem: how to display the GUID in the URL? The easy solution is to use ToString("N"), but this will return the GUID in the format 00000000000000000000000000000000. However, this format is inefficient. You can use ToBase64String to get the GUID in a more efficient format. Base 64 encoding is not safe for URLs as it may contains / character. This means you need to encode the value using Uri.EscapeDataString. Also, there are padding characters at the end of the string.

A shorter way to encode the GUID is to use the WebEncoders.Base64UrlEncode method. This method converts a byte array to a base 64 string that can safely be used in a URL as describe in the Base64Url Encoding specification. This encoding uses the URL safe characters - and _ instead of + and /. Also, it removes the padding characters (=) at the end of the string.

Here are some examples to show the difference between the formatting methods:

FormatLengthSample data
Guid.ToString()3600000000-0000-0000-0000-000000000000
Guid.ToString("N")3200000000000000000000000000000000
Convert.ToBase64String240000000000000000000000==
WebEncoders.Base64UrlEncode220000000000000000000000

Here's the code to add a encode and decode a GUID as a base 64 URL safe string:

static string EncodeGuid(Guid guid)
{
    Span<byte> bytes = stackalloc byte[16];
    guid.TryWriteBytes(bytes);
    return Microsoft.AspNetCore.WebUtilities.WebEncoders.Base64UrlEncode(bytes);
}

static Guid DecodeGuid(string guid)
{
    return new Guid(WebEncoders.Base64UrlDecode(str));
}

#ASP.NET Core model binding

To easily use the shorter GUIDs in an ASP.NET Core application, you can create a custom struct to wrap the GUID and define custom converters that will be used by ASP.NET Core model binder and json serializer.

[ApiController]
[Route("[controller]")]
public class SampleController : ControllerBase
{
    [HttpGet("{id}")]
    public ShortGuid Get(ShortGuid id) => id;

    [HttpGet]
    public IEnumerable<ShortGuid> Get()
    {
        for (int i = 0; i < 10; i++)
            yield return Guid.NewGuid();
    }
}
[TypeConverter(typeof(ShortGuidTypeConverter))]
[JsonConverter(typeof(ShortGuidJsonConverter))]
public readonly struct ShortGuid
{
    private readonly Guid _value;

    public ShortGuid(Guid value) => _value = value;

    public static implicit operator Guid(ShortGuid shortGuid) => shortGuid._value;
    public static implicit operator ShortGuid(Guid guid) => new(guid);

    public static ShortGuid Parse(string input) => new Guid(WebEncoders.Base64UrlDecode(input));

    public override string ToString()
    {
        Span<byte> bytes = stackalloc byte[16];
        _value.TryWriteBytes(bytes);
        return WebEncoders.Base64UrlEncode(bytes);
    }

    private sealed class ShortGuidTypeConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
            => sourceType == typeof(string);

        public override object? ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
            => value is string str ? Parse(str) : null;

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
            => destinationType == typeof(string);

        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
            => ((ShortGuid)value).ToString();
    }

    private sealed class ShortGuidJsonConverter : JsonConverter<ShortGuid>
    {
        public override ShortGuid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            var str = reader.GetString();
            if (str != null)
                return Parse(str);

            return default;
        }

        public override void Write(Utf8JsonWriter writer, ShortGuid value, JsonSerializerOptions options)
            => writer.WriteStringValue(value.ToString());
    }
}

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee