0

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?

1
  • Did you try to use a custome model binder around the ContactId? Commented Jan 28, 2024 at 10:02

1 Answer 1

0

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.

Sign up to request clarification or add additional context in comments.

1 Comment

This didn't answer my issue directly but helped me realise I was missing the [FromRoute] attribute on my endpoint's parameter. The endpoint still worked without that attribute, but the swagger generation was different, because the TypeConverter was not being realised.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.