У меня такая модель:

public class CalendarAvailabilityRequest
{
    [Required]
    [FromQuery]        
    public DateTime StartDate { get; set; }
}

И этот контроллер / метод действия:

[ApiController]
[Route("api/[controller]")]
public class AppointmentController : ControllerBase
{        
    [Route("{providerName}/CalendarAvailability")]
    [HttpGet]
    public Task<CalendarAvailabilityResponse> GetCalendarAvailability(CalendarAvailabilityRequest request)
    {
        return null;
    }
}

Как я могу убедиться, что принимается только "yyyy-MM-dd" при достижении конечной точки?

Например. Это будет принято:

https://example.org/api?StartDate=2019-04-17

Но это будет исключение:

https://example.org/api?StartDate=2019-17-04

https://example.org/api?StartDate=17-04-2017

1
David Klempfner 17 Апр 2019 в 07:58

2 ответа

Лучший ответ

В итоге я написал Attribute, который реализует IResourceFilter:

public class DateTimeResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        const string PreferredDateTimeFormat = "yyyy-MM-dd";
        string dateTimeString = context.HttpContext.Request.Query["StartDate"].First();
        bool isPreferredDateTimeFormat = DateTime.TryParseExact(dateTimeString, PreferredDateTimeFormat, new CultureInfo("en-AU"), DateTimeStyles.None, out DateTime dateTime);
        if (!isPreferredDateTimeFormat)
        {
            context.Result = new ContentResult()
            {
                Content = $"Date must be in the following format: {PreferredDateTimeFormat}",
                StatusCode = (int)HttpStatusCode.BadRequest
            };
        }
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}

Я применил атрибут к своему методу действия:

    [DateTimeResourceFilter]
    [Route("{providerName}/CalendarAvailability")]
    [HttpGet]
    public Task<CalendarAvailabilityResponse> GetCalendarAvailability(CalendarAvailabilityRequest request)
    {
        return null;
    }
1
David Klempfner 17 Апр 2019 в 23:26

Я бы предложил использовать fluentvalidation, поскольку это позволяет разделять и повторно использовать правила проверки.

В вашем случае, предполагая, что startdate является частью CalendarAvailabilityRequest, вы бы добавили валидатор для запроса dto:

public class CalendarAvailabilityRequestValidator : 

AbstractValidator<CalendarAvailabilityRequest> 
{
  public CalendarAvailabilityRequestValidator() 
  {
    RuleFor(request => request.StartDate)
        .Must(BeAValidDateFormat).WithMessage("Date must follow the format: yyyy-mm-dd")
        .NotNull().WithMessage("A start date must be provided.");
  }

  // will only match yyyy-mm-dd
  private static bool BeAValidDateFormat(string date)
    => Regex.IsMatch(date, "2\d{3}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$", RegexOptions.Compiled);
}

В вашем контроллере вы создаете валидатор и позволяете ему валидировать:

[Route("{providerName}/CalendarAvailability")]
[HttpGet]
public Task<IActionResult> GetCalendarAvailability(CalendarAvailabilityRequest request)
{
    var validationResult = new CalendarAvailabilityRequestValidator().Validate(request);
    if (!validationResult.IsValid)
    {
        Log.Warning(validationResult.Errors.ToString());
        return BadRequest(validationResult.Errors);
    }
    var statDate = DateTime.ParseExact(request.StartDate, "yyyy-mm-dd", CultureInfo.InvariantCulture);
    //TODO: calendar availability logic
    return OK(); 
}

Конечно, вы также можете использовать регулярное выражение сверху и подтвердить запрос в вашем контроллере.

Другой вариант - попробовать catch, используя DateTime.ParseExact что-то вроде этого:

try
{
    var statDate = DateTime.ParseExact(request.StartDate, "yyyy-mm-dd", CultureInfo.InvariantCulture);
}
catch(exception ex)
{
  Log.Warning("Request for {startdate} was invalid: {message}", request.StartDate, ex.Message);
  return BadRequest(ex.message);
}

Но я бы порекомендовал избегать попытки catch, когда вы можете проверить ввод, если вам действительно не нужно.

1
Raul Sebastian 17 Апр 2019 в 06:18