I'm in the middle of converting an ASP.NET Core 6 API app to be a Minimal API app. Now that ASP.NET Core 8 supports [FromForm]
for object binding, I decided to take the plunge, but I'm running into trouble.
Specifically I'm having a hard time getting value objects bound. In my case these are strongly typed ids using Andrew Lock's StronglyTypedId. When I added a TryParse
static method to the value object, as mentioned here, it successfully bound when using [FromRoute]
, but I can't get it to bind to the property of an object, even though the TryParse
should still apply.
Here's the object I'm trying to bind using [FromForm]
:
public sealed class Command :
IRequest<IResult> {
[MaxLength(254), EmailAddress]
public string? Email { get; init; }
[Required]
public ContactId Id { get; init; }//<-- The property not binding...
[Required, MaxLength(byte.MaxValue)]
public string Name { get; init; } = null!;
[Range(1000000000, 9999999999)]
public long? Phone { get; init; }
}
And here's the value object for the ContactId
property:
[StronglyTypedId(StronglyTypedIdBackingType.Int, StronglyTypedIdConverter.EfCoreValueConverter | StronglyTypedIdConverter.SystemTextJson | StronglyTypedIdConverter.TypeConverter)]
public partial struct ContactId {
public static ContactId Parse(
int? value) => value.HasValue
? new ContactId(value.Value)
: throw new ArgumentNullException(nameof(value));
public static ContactId Parse(
ReadOnlySpan<char> value) => int.TryParse(value, out var intValue)
? new ContactId(intValue)
: throw new ArgumentNullException(nameof(value));
public static bool TryParse(
string value,
out ContactId id) {
if (!int.TryParse(value, out var intValue)) {
ArgumentNullException.ThrowIfNull(value, nameof(value));
}
id = new ContactId(intValue);
return true;
}
public sealed class EfCoreValueGenerator :
ValueGenerator<ContactId> {
public override ContactId Next(
EntityEntry entry) => new(Random.Shared.Next() * -1);
public override bool GeneratesTemporaryValues => true;
}
}
What should I do to resolve this?
After spending a day on this, I ended up resolving it by upgrading my value objects to use the StronglyTypeIds beta 7 source generator. After converting everything from beta 6 to 7, adding in my own templates for byte
and short
backed values, it all seems to be working as expected.
I can only surmise that the new interfaces and methods that were introduced in .NET 7 and .NET 8 that the upgraded value objects use are what made the model binding work.
Specifically one of the IFormattable
, IParsable<T>
, ISpanFormattable
, ISpanParsable<T>
, IUtf8SpanFormattable
or IUtf8SpanParsable<T>
interfaces and related method implementations probably did it.
So, in conclusion, if you're using the StronglyTypedIds package, and you're implementing an ASP.NET Core 8+ Minimal API that uses [FromForm]
for model binding, then make sure you're on StronglyTypedIds beta 7 or newer.