问题
On an ASP.NET Core 2.2 API I have the following action:
public async Task<IActionResult> Create([FromBody]Model model) {
}
Where Model
is the following:
public class Model {
public DateTime? PublishedAt { get; set; }
}
Property PublishedAt
is required and needs to be in the past.
When calling the action I am able to predict 2 different scenarios:
Data sent to action doesn't include
PublishedAt
orPublishedAt
is NULL.By using
DateTime?
I am able to check if it is NULL or in the past in case it is defined.Does this make sense?
Data sent to action includes an invalid
PublishedAt
date (2019-20-40).In this case I realised that
Model
becomes null so I cannot validate it.I also cannot send back a friendly message such as:
"Publish At date format is invalid"
How can I return friendly messages when a DateTime has wrong format?
I would like to avoid using String as data type for
PublishedAt
.Maybe using a custom Model Binder?
回答1:
What you can do is to put Required
attribute on PublishedAt
public class Model
{
[Required]
public DateTime? PublishedAt { get; set; }
}
[ApiController]
public class ValuesController : ControllerBase
{
public async Task<IActionResult> Create([FromBody]Model model)
{
}
}
If you have ApiController attribute on your controller it should automatically respond with BadRequest if it's not present.
Similarly you can add your own custom validation attribute with a message. Asp.Net documentation has an example on how to do that.
回答2:
IMHO, I like the approach below and have used it extensively without any problems. The good thing about this approach is that it keeps your model clean and enables separation of concerns. Your validation logic for the
Model
is completely independent.
Try using FluentValidation
. You can read about it here in detail. It's a NuGet package that you can download via NuGet.org. Once installed you can register it in ConfigureServices
like below:
1 public void ConfigureServices(IServiceCollection services)
2 {
3 services.AddMvc(setup => {
4 //...mvc setup...
5 }).AddFluentValidation(configuration => configuration
6 .RegisterValidatorsFromAssemblyContaining<Startup>());
7 }
Line number 5 and 6 will automatically find any public, non-abstract types that inherit from AbstractValidator
and register them with the container. You then define your AbstractValidator
for Model
as below
Before you create your AbstractValidator
I know you mentioned that you would want to avoid changing the PublishedAt type to string. However, I would suggest you consider it. That will make it easy to validate the parameter, otherwise, automatic model binding may bind it in a different format, and custom model binding is little trickier than the following.
If you really want to avoid changing the
PublishedAt
tostring
, you can try the same approach by changing the rules slightly and see if that works for you
public class ModelValidator : AbstractValidator<Model>
{
public ModelValidator()
{
// add a rule that Date must be in the past, shouldn't be empty
// and in the correct format
RuleFor(model => model.PublishedAt)
.Cascade(CascadeMode.StopOnFirstFailure)
.Must(date => !string.IsNullOrWhiteSpace(date))
.WithMessage("PublishAt is a required parameter")
.Must(arg =>
{
if (DateTime.TryParseExact(arg.ToString(), new[] { "dd-MMM-yyyy" }, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime date))
{
return date < DateTime.Now;
}
return false;
})
.When(model => !string.IsNullOrWhiteSpace(model.PublishedAt))
.WithMessage("Argument PublishAt is invalid. Please specify the date in dd-MMM-yyy and should be in the past");
}
}
The above validator will be executed after the model binding process and if the validation fails, WithMessage
statements will add the error to the ModelState. As you have [ApiController]
attribute. Your model will be validated and it will return the messages you specified in the WithMessage
statements.
Or you can manually check if the ModelState.IsValid
inside the action method and return the ObjectResult
with the ModelState.
来源:https://stackoverflow.com/questions/55925199/validate-values-with-invalid-format-in-asp-net-core-api