Request binding with IParseable

Using IParseable to bind primitive types to more complex entities

Home DailyDrop

Daily Knowledge Drop

In a previous post we had a look at the IParseable interface which allows for a string to be parsed into a type.

This functionality can be leveraged with minimal API's to automatically parse a query string to a complex type.


IParseable request

In this example, the Song entity implements the IParsable<Song> interface (more details in the previous post about IParseable):

public class Song : IParsable<Song>
{
    public string Name { get; set; }
    public string Artist { get; set; }
    public int LengthInSeconds { get; set; }

    private Song(string name, string artist, int lengthInSeconds)
    {
        Name = name;
        Artist = artist;
        LengthInSeconds = lengthInSeconds;
    }

    public static Song Parse(string s, IFormatProvider? provider)
    {
        string[] songPortions = s.Split(new[] { '|' });

        if (songPortions.Length != 3) 
        { 
            throw new OverflowException("Expect format: Name|Artist|LengthInSeconds"); 
        }

        return new Song(songPortions[0], songPortions[1], Int32.Parse(songPortions[2]));
    }

    public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Song result)
    {
        result = null;
        if (s == null) 
        { 
            return false; 
        }

        try
        {
            result = Parse(s, provider);
            return true;
        }
        catch { return false; }
    }
}

This interface implementation allows for a string value (in a specific format), to be converted to a Song instance:

Song song = "Everlong|Foo Fighters|326".Parse<Song>();

Minimal endpoint

Manual parsing

If we want an endpoint which accepts a Song as a parameter, one option is to have the request entity as a string and perform the conversion to Song manually:

app.MapGet("/song", ([FromQuery]string song) =>
{
    // perform the conversion from string
    // to Song manually, using the IParsable
    // interface
    return details.Parse<Song>();
});

Calling the endpoint with a song query string /song?song=Everlong|Foo Fighters|326, results in the following response:

{"name":"Everlong","artist":"Foo Fighters","lengthInSeconds":326}

This is a valid approach, but because the Song class implements IParseable, the conversion can be done automatically!


Automatic parsing

A slightly easier approach that manually doing the conversions, is allowing it to happen automatically. Changing the parameter type from string to Song:

// parameter is Song instead of string
app.MapGet("/song", ([FromQuery] Song song) =>
{
    return song;
});

Calling the same endpoint with a song query string /song?song=Everlong|Foo Fighters|326, results in the same response:

{"name":"Everlong","artist":"Foo Fighters","lengthInSeconds":326}

As Song implement IParsable, the string parameter is automatically parsed to a Song instance.


Notes

This is a small quality of life feature which makes working with the IParsable interface and minimal endpoints easier and more streamline.


References

5 new MVC features in .NET 7

Daily Drop 233: 12-01-2023

At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.

The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.
c# .net endpoint iparseable bind