Reducing the length of GUIDs in URLs or Json payloads
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:
Format | Length | Sample data |
---|---|---|
Guid.ToString() | 36 | 00000000-0000-0000-0000-000000000000 |
Guid.ToString("N") | 32 | 00000000000000000000000000000000 |
Convert.ToBase64String | 24 | 0000000000000000000000== |
WebEncoders.Base64UrlEncode | 22 | 0000000000000000000000 |
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!