To recap, the HMTL5 week input is supported by a number
of browsers and enables the user to select a week of the year:
It works with
values formatted according to the ISO 8601 standard, which, in the case of a
week of the year is in the format yyyy-Www, where yyyy is the full year -W
is literal, and ww represents the
ISO 8601 week of
the year. In the instance above, the value is set to 2021-W01 - the
first week of 2021. The input tag helper renders input type="week"
for properties that have a DataType attribute set to
"week".
You have a decision to make in respect of how you want to represent the
week in your server code. You could work with it as a string,
or a DateTime, or you can create your own custom type to
represent the week:
public class Week { public int Year { get; set; } public int WeekNumber { get; set; } public static Week TryParse(string input) { var result = input.Split("-W"); if (result.Length != 2) { return null; } if (int.TryParse(result[0], out int year) && int.TryParse(result[1], out int week)) { return new Week { Year = year, WeekNumber = week }; } return null; } public override string ToString() { return $"{Year}-W{WeekNumber:D2}"; } }
The TryParse method takes a string and attempts to
generate a valid instance of the Week type. The overridden ToString method generates the correctly
formatted value that will be applied to the week input when using the input
tag helper. Next, you need a way to call the TryParse method in
user input. So, based on the recommendation to use a TypeConverter,
here is the WeekConverter:
public class WeekConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); } public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string input) { return Week.TryParse(input); } return base.ConvertFrom(context, culture, value); } }
The class derives from the TypeConverter and overrides two
methods: CanConvertFrom() and ConvertFrom(). The
first method returns a bool indicating whether this converter
can manage conversions from the specified type. This converter is intended
to be used in model binding, so it is designed to convert from strings. The
ConvertFrom method contains the code that tests whether the
input is a string, and if so, uses the Week.TryParse method to
attempt to return a new Week instance.
Finally, you need to register the WeekConverter. You do this
by applying the TypeConverterAttribute to the Week
class:
[TypeConverter(typeof(WeekConverter))] public class Week { ... }
Note that this attribute is not applied to the PageModel property like
the ModelBinder attribute. It must be applied to the class
definition. There is an
open issue on
Github that will enable the declaration of the
TypeConverterAttribute on model properties and parameters, but in the
meantime, it is not supported as part of the model binding infrastructure. The PageModel property has the BindProperty and
DataType
attributes applied:
[BindProperty, DataType("week")] public Week CustomWeek { get; set; }
Now the TypeConverter takes care of assigning the posted
value from the input to the PageModel property:
Summary
As I mentioned at the beginning, the recommendation is to use a
TypeConverter when you need to map a single value in a request to a
complex object. Doing so does not have to be complex at all, as this article
shows. Since type converters are used by the model binding system anyway,
once you have implemented your own and configured it on the class, it "just
works".