Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DateOnly and TimeOnly to MVC Model Binding #45243

Closed

Conversation

Nick-Stanton
Copy link
Member

Add DateOnly and TimeOnly to MVC Model Binding

Adds nullable DateOnly and TimeOnly to list of model binding simple types.

Description

Part of the effort tracked in #34591 to add DateOnly and TimeOnly support to Model binding as well as Blazor/regular routing.

The logic almost exactly matches the implementation for DateTime with the exception of DateTimeStyles.AdjustToUniversal being included as a supported style as this style is not supported by the DateOnly and TimeOnly types.

On the completion and merge of this PR, I will create an additional PR to add DateOnly and TimeOnly to our list of supported types.

@Nick-Stanton Nick-Stanton requested review from a team as code owners November 22, 2022 22:39
@ghost ghost added the area-web-frameworks *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels label Nov 22, 2022
Copy link
Contributor

@TanayParikh TanayParikh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Congrats on your first PR in aspnetcore! 🎉

@Nick-Stanton Nick-Stanton linked an issue Nov 30, 2022 that may be closed by this pull request
@Nick-Stanton Nick-Stanton added the blocked The work on this issue is blocked due to some dependency label Nov 30, 2022
@Nick-Stanton
Copy link
Member Author

Adding the blocked label pending the approval and resolution of #45376.

@@ -74,6 +74,8 @@ public void Configure(MvcOptions options)
options.ModelBinderProviders.Add(new ArrayModelBinderProvider());
options.ModelBinderProviders.Add(new CollectionModelBinderProvider());
options.ModelBinderProviders.Add(new ComplexObjectModelBinderProvider());
options.ModelBinderProviders.Add(new DateOnlyModelBinderProvider());
options.ModelBinderProviders.Add(new TimeOnlyModelBinderProvider());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these new IModelBinderProviders ever get hit outside of unit tests? I cloned this PR and added the following lines to MvcSandbox's HomeController.

public class HomeController : Controller
{
[ModelBinder]
public string Id { get; set; }
public IActionResult Index()
{
return View();
}
}

    [ModelBinder]
    public TimeOnly Time { get; set; }

And then made a request to /?Id=3&Time=12:34:56%20PM and added breakpoints. The Time got populated by the TryParseModelBinderProvider added by @brunolins16 in #40233. Since that's added before these providers, it gets first crack at binding DateOnly and TimeOnly.

For this reason and to maintain back compat, we put TryParseModelBinderProvider after DateTimeModelBinderProvider. Even if we remove the TryParseModelBinderProvider, the SimpleTypeModelBinderProvider would handle this first as well using TypeConverter.

options.ModelBinderProviders.Add(new DateTimeModelBinderProvider());
options.ModelBinderProviders.Add(new TryParseModelBinderProvider());
options.ModelBinderProviders.Add(new SimpleTypeModelBinderProvider());

Since we don't have any back compat concerns for DateOnly and TimeOnly, I'm not sure we need these. It does allow customizing DateTimeStyles if you construct the providers yourself and add them manually, but I don't like having to maintain providers for each possible type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@halter73 Are there any differences in behavior between the TryParse implementation and the concrete modelbinders? Or any other concern like perf? If the TryParse implementation supports all of them, should we get rid of the DateTime and DateTimeOffset modelbinders?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to get rid of the DateTime and DateTimeOffset model binders, but I don't think it's worth potentially breaking working code. @brunolins16 What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@halter73 I do not think most people will change those binders directly. We can remove them from 8.0, make an announcement in preview1 and see if someone is broken by it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As reported here , it could potentially be break and was decided (I don't have the historical reason) to create specific binders for them even though the simpletypebinder supported them a long ago. That said I believe this is a worthy investigation and we don't need to delete them and instead just remove the default registration and let the binder available for manual registration.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a potential reason why the specific binders were created #34591 (comment)

Great point, this was an essential part of getting DateTime binding correct. That being said, DateTimeStyles.AdjustToUniversal is not supported for DateOnly and TimeOnly so the same requirement is not there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the only benefit I see in have those two new binders, is allow users to customize the DateTimeStyles creating their own ModelBinderProviders.

Copy link
Member

@brunolins16 brunolins16 Dec 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion 💡: What if we add those two new Binder to allow users customization, without the Providers (no registration), and let the TryParse/Converter binder do the work by default.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the DatetimeStyles when using the TryParse binder:

var dateTimeStyles = type switch
{
Type t when t == typeof(DateTime) => DateTimeStyles.AdjustToUniversal | DateTimeStyles.AllowWhiteSpaces,
Type t when t == typeof(DateTimeOffset) => DateTimeStyles.AssumeUniversal | DateTimeStyles.AllowWhiteSpaces,
_ => DateTimeStyles.AllowWhiteSpaces
};

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, DateTimeStyles.AllowWhiteSpaces is what I currently have for these proposed providers. While writing them I found that the only supported styles for TimeOnly and DateOnly were the ones pertaining to white space, but I can't seem to find a doc page to back that up.

Also, do you think there's any gain/loss when TryParse is used over any of the other providers/binders? I see that TryParse goes before SimpleType.

Copy link
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks great, I think we are missing some E2E test coverage, as @halter73 pointed out.

@brunolins16
Copy link
Member

brunolins16 commented Dec 2, 2022

On the completion and merge of this PR, I will create an additional PR to add DateOnly and TimeOnly to our list of supported types.

Thanks for that but those types are already simple types since this definition is not based on the Converter or TryParse methods .

@brunolins16 brunolins16 closed this Dec 2, 2022
@ghost
Copy link

ghost commented Dec 2, 2022

Hi @brunolins16. It looks like you just commented on a closed PR. The team will most probably miss it. If you'd like to bring something important up to their attention, consider filing a new issue and add enough details to build context.

@brunolins16 brunolins16 reopened this Dec 2, 2022
@brunolins16
Copy link
Member

Sorry closed by mistake.

@Nick-Stanton
Copy link
Member Author

On the completion and merge of this PR, I will create an additional PR to add DateOnly and TimeOnly to our list of supported types.

Thanks for that but those types are already simple types since this definition is not based on the Converter or TryParse methods .

@brunolins16 Yep, sounds like I need to make that docs PR regardless of the outcome of this one.

}
else if (type == typeof(DateOnly))
{
model = DateOnly.Parse(value, valueProviderResult.Culture, _supportedStyles);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend the TryParse here to avoid the exception

}
else if (type == typeof(TimeOnly))
{
model = TimeOnly.Parse(value, valueProviderResult.Culture, _supportedStyles);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend the TryParse here to avoid the exception

@Nick-Stanton
Copy link
Member Author

Closing this PR since TryParseModelBinder already covers this use case with no additional API required

@Nick-Stanton Nick-Stanton deleted the DateOnlyTimeOnlyBinding branch January 9, 2023 22:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-web-frameworks *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels blocked The work on this issue is blocked due to some dependency
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Model Binding Support for TimeOnly and DateOnly
8 participants