[BUG] ObjectId is flattened into 5 query string arguments instead of a single string value
Describe the bug
MongoDB uses ObjectId as the default primary key. When ObjectId is used on the query string argument, the value is flattened in accordance with the Refit docs:
If you specify an object as a query parameter, all public properties which are not null are used as query parameters.
ObjectId has five public properties:
-
int Timestamp -
int Machine -
int Pid -
int Increment -
DateTime CreationTime
These properties are rarely used within most applications. CreationTime is probably used the most - it represents the date/time when the ID was generated.
An ObjectId are typically represented as a string e.g. 63028b8339684a16b0ef8b4e and this is what I'd expect to appear on the query string.
[Get("/group")]
Task GroupList([Query] ObjectId id);
>>> /group?Timestamp=1661094220&Machine=15077794&Pid=13168&Increment=16127951&CreationTime=08%2F21%2F2022%2015%3A03%3A40
Steps To Reproduce
See above code sample ☝️.
Expected behavior
>>> /group?id=63028b8339684a16b0ef8b4e
Additional information
The only workaround I can think of is to replace the ObjectId with string. This obviously works but who likes magic strings?
It's also worth noting that this is also the default behaviour when serializing an ObjectId although it's possible to override it using a JsonConverter:
public class ObjectIdConverter : JsonConverter<ObjectId>
{
public override ObjectId Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
var value = reader.GetString();
return ObjectId.TryParse(value, out var result) ? result : ObjectId.Empty;
}
public override void Write(
Utf8JsonWriter writer,
ObjectId value,
JsonSerializerOptions options)
{
var json = value == default
? string.Empty
: value.ToString();
writer.WriteStringValue(json);
}
}
I have basically this exact same issue except using StronglyTypedId.
@andrewgunn the approach I'm taking is to use a Default Interface Method to wrap the actual Refit definition so I can expose using the more appropriate type, in your case ObjectId, and internally pass through the underlying string.
Something like this should work for you.
// Default interface method
Task GroupList(ObjectId id) => GroupListInternal(id.ToString());
// Actual Refit definition
[Get("/group")]
internal Task GroupListInternal([Query] string id);
I have basically this exact same issue except using StronglyTypedId.
@andrewgunn the approach I'm taking is to use a Default Interface Method to wrap the actual Refit definition so I can expose using the more appropriate type, in your case
ObjectId, and internally pass through the underlyingstring.Something like this should work for you.
// Default interface method Task GroupList(ObjectId id) => GroupListInternal(id.ToString()); // Actual Refit definition [Get("/group")] internal Task GroupListInternal([Query] string id);
Good idea 👍
I also stumbled into this issue with https://github.com/ultimicro/netulid.
GitHub
High quality/performance ULID implementation for .NET - GitHub - ultimicro/netulid: High quality/performance ULID implementation for .NET