3 minutes
[ASP.NET Core] TypeConverter zamiast IModelBinder
W ramach nauki .NET Core piszę sobie aplikację webową. Na jednej podstronie mam wybór tygodnia, aby pokazywać dane z wybranego tygodnia.

Użyłem do wyboru tygodnia standardowego znacznika HTML:
<input type="week">
Opis problemu
Ten znacznik przesyła dane w postaci 2020-W24, czyli rok, a następnie numer tygodnia. W C# nie ma żadnej klasy czy struktury temu odpowiadającej. Dlatego postanowiłem napisać sobie własną klasę Week.
Zawiera ona jeden konstruktor:
public Week(int year, int week)
{
    Year = year;
    WeekNr = week;
}
Przeciążyłem metodę ToString, aby zwracało informacje w formacie z kontrolki <input type="week">
public override string ToString()
{
    return $"{Year}-W{WeekNr}";
}
Zawiera jeszcze kilka metod pomocniczych:
public DateTime GetFirstDayOfWeek()
public static Week GetCurrentWeek()
public Week GetNextWeek()
public Week GetPreviousWeek()
public string GetDaysInWeekPeriod()
Wszystko spoko działa :-) Tylko za każdym razem jak przesyłam dane z formularza, to muszę stringa z informacją o tygodniu i roku przekształcać na obiekt Week. Chciałbym aby automatycznie to się robiło.
Ucząc się podstaw ASP.NET Core czytałem oczywiście o czymś taki jak bindowanie danych. Framework sam konwertuje teksty na odpowiedni typ, dzięki temu programista już nie musi się o to martwić. Skoro takie coś istnieje, to pomyślałem, że pewnie da się jakąś własną klasę napisać, która zamieni przychodzący tekst na obiekt Week.
Zacząłem googlować custom model binding i trafiłem na artykuł Custom Model Binding in ASP.NET Core z oficjalnej dokumentacji. Jest tam napisane jak zrobić własne bindowanie do własnego modelu.
Już zacząłem to robić. Dodałem klasę, która implementowała interfejs IModelBinder, ale na szczęście doczytałem artykuł do końca :-)
Disclaimer: Czasami zdarza mi się, że szybko chce coś zrobić i przeglądam tylko kod kopiuje go do siebie, a potem się denerwuje, że coś nie działa. Okazuje się, że coś pominę i nie doczytam… i nie działa.
Na końcu jest napisane:
Typically shouldn’t be used to convert a string into a custom type, a TypeConverter is usually a better option.
Użycie TypeConverter zamiast IModelBinder
I tak zrobiłem :-) I wszystko działa i wydaje się nawet, że jest to prostsze rozwiązanie. Co musiałem zrobić?
Dodać klasę WeekConverter, która dziedziczy po klasie TypeConverter:
public class WeekConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        if (sourceType == typeof(string))
            return true;
        return base.CanConvertFrom(context, sourceType);
    }
    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string isoWeek)
        {
            var result = isoWeek.Split("-W");
            return new Week(Convert.ToInt32(result[0]), Convert.ToInt32(result[1]));
        }
        return base.ConvertFrom(context, culture, value);
    }
}
Zaimplementowałem tylko 2 metody, których potrzebuję:
- CanConvertFrom
 - ConvertFrom
 
TypeConverter konweruje w 2 strony ale mi tylko była potrzebna konwersja z typu string do Week.
Dodatkowo klasę Week musiałem oznaczyć atrybutem:
[TypeConverter(typeof(WeekConverter))]
public class Week
W metodzie oczywiście zmieniłem typ na Week + parę drobnych poprawek i już :-) I tylko tyle :-) Wszystko już działa.
Z formularza na stronie dostaję od razu obiekt Week, a nie string.
Podsumowanie
Jeśli możesz używaj TypeConverter zamiast własnego bindowania modelu. Czytaj dokumentację i artykuły do końca, mogą się tam znajdować ważne informacje.