0

I'm writing an API in dotnet core. I would like my controller action to take a date (eg. '2019-11-12') as a parameter, so the user doesn't have to provide a full datetime.

I have the following:

[HttpGet]
public async Task<ActionResult<IEnumerable<DogDto>>> GetDogsForKennelOnDate([FromQuery]string kennelName, [FromQuery]DateTime birthDate)
{...}

It seems to me that this is probably a common use case, but I haven't been able to find a solution on SO. Can you help?

EDIT:

Should I take a string as parameter and then create a new DateTime from this string? Is this the right way to do it?

  • `I cannot write [FromQuery]DateTime.Date birthDate to bind it` - [why not](https://meta.stackexchange.com/q/66377/147640)? – GSerg Nov 13 '19 at 17:25
  • @GSerg My IDE says `Cannot resolve symbol 'Date'` if I put that in the signature. –  Nov 13 '19 at 17:27
  • https://stackoverflow.com/q/15713167/11683? – GSerg Nov 13 '19 at 17:29

1 Answers1

2

The reason why you get a compilation error for this:

[HttpGet]
public async Task<ActionResult<IEnumerable<DogDto>>> GetDogsForKennelOnDate([FromQuery]string kennelName, [FromQuery]DateTime.Date birthDate)
{...}

is because the compiler is expecting you to define the data type for the birthDate parameter, but DateTime.Date is not a data type, but a property of the DateTime data type.

Now consider this, tested on .NET Core 2.2:

[HttpGet]
public async Task<ActionResult<IEnumerable<DogDto>>> GetDogsForKennelOnDate([FromQuery]string kennelName, [FromQuery]DateTime birthDate)
{...}

The query string parameter:

birthDate=2019-11-12

binds successfully to the birthDate parameter in the action (with 0 for the hours, minutes and seconds). So I think your assumption that the user has to provide the full date and time is wrong.

Note: The DateTime.Date property is itself a DateTime with the time value set to 12:00:00 midnight (00:00:00); see https://learn.microsoft.com/en-us/dotnet/api/system.datetime.date?view=netcore-2.2.

Edit: rather than making it impossible for the user to include the time in the query string parameter, you can instead ignore it (my preference as it is user-friendly):

[HttpGet]
public async Task<ActionResult<IEnumerable<DogDto>>> GetDogsForKennelOnDate([FromQuery]string kennelName, [FromQuery]DateTime birthDate)
{
    DateTime birthDateDate = birthDate.Date;
}

Or you could validate that the hours, minutes and seconds are 0, and create a model state error if they are not (not so user friendly). You can do this using a custom validation attribute:

public sealed class DateAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (validationContext == null)
        {
            throw new ArgumentNullException(nameof(validationContext));
        }

        if (!(value is DateTime))
        {
            return new ValidationResult($"Not a DateTime object ({value}).");
        }

        DateTime date = (DateTime)value;

        if (date.Date != date)
        {
            return new ValidationResult($"Do not specify the time ({value}).");
        }

        return ValidationResult.Success;
    }
}

Usage:

[HttpGet]
public async Task<ActionResult<IEnumerable<DogDto>>> GetDogsForKennelOnDate([FromQuery]string kennelName, [FromQuery] [Date] DateTime birthDate)
{
    ...
}

This will now succeed validation:

birthDate=2019-11-12

But this will fail:

birthDate=2019-11-12 11:22:33

However, this will still succeed:

birthDate=2019-11-12 00:00:00
Polyfun
  • 9,479
  • 4
  • 31
  • 39
  • I feel like an idiot. Should I not then make it impossible to add hours, minutes, etc.? –  Nov 13 '19 at 17:34
  • 1
    @elliott the problem you are most likely to encounter is consumers passing invalid datestrings based off of regional settings. So in whatever spec either account for it, or enforce UTC 8601 (Which you appear to use yourself at least) – Austin T French Nov 13 '19 at 17:39