4

I am using net core 3.1 in my web API project. I have created one API which accepts the date from the user. By default MM-dd-yyyy format is accepted in the project. But I want to accept the date in dd-MM-yyyy format and validate all dates accordingly.

Below is my api :

    [HttpGet]
    public async Task<IActionResult> Get(DateTime fromDate, DateTime toDate)
    {
        return Ok();
    }

Also, I have APIs in which the date parameter is passed in the request body as JSON. I have tried following StackOverflow answers but nothing worked:

https://stackoverflow.com/a/58103218/11742476

The above solution worked when passing the date inside the request body but not when passing the date in the URL.

Is there any other way through which I can achieve this. ?

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Sunny
  • 752
  • 1
  • 11
  • 24
  • Good evening. Are there two dates coming as part of route parameter? Please advise – Viswanatha Swamy Mar 17 '20 at 12:41
  • ah yeah. In one API as route parameter and in another as JSON request. – Sunny Mar 18 '20 at 08:05
  • good afternoon. You can give these 1. Route Constraints, and 2. Data Annotations a shot. Below are the URLs for additional details. Please let me know if this works? Many thanks. 1. https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-3.1 2. https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/validation?view=aspnetcore-3.1 – Viswanatha Swamy Mar 18 '20 at 09:06
  • @ViswanathaSwamy I guess, for handling the date inside the request body, using JsonConverter will be better instead of data annotations and about route constraints, I hadn't yet explored that. It would be great if you could show how can I achieve that using route constraints. – Sunny Mar 18 '20 at 09:20
  • Did you want to pass `dd-MM-yyyy` date to action successfully?If so,url would accept all the format of datetime.From your url,the custom JsonConverter is used to modify the format of return date.What is your requirement,accept the `dd-MM-yyyy` date or show`dd-MM-yyyy` date? – Rena Mar 18 '20 at 09:30
  • @Rena. My requirement is to pass the dd-MM-yyyy format to action successfully. I have used this answer https://stackoverflow.com/a/58103218/11742476 for accepting (from request body as JSON) as well returning the date in dd-MM-yyyy format. Now I am struct in passing the date in dd-MM-yyyy format from url. – Sunny Mar 18 '20 at 10:11
  • Hi @Sunny,what's the error message?1.Is the date not correct when you pass to action?If so,please share the date you pass and the screenshot of the date you receive in your action.2.Mybe you even do not hit the action?If so,you need to change your `HttpGet` to `[HttpGet("{fromDate}/{toDate}")]` and pass the url like:`https://localhost:portNumber/api/values/8-7-2019/8-8-2019` – Rena Mar 19 '20 at 01:50
  • @Rena. API is called successfully when I pass the date in MM-dd-yyyy format. Whatever format I use, action always takes it as MM-dd-yyyy and if I pass 24-12-2020 it gives me the error something like "invalid value for fromdate". – Sunny Mar 19 '20 at 05:01
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/209899/discussion-between-sunny-and-rena). – Sunny Mar 19 '20 at 06:05
  • Please check my updated answer. – Rena Apr 01 '20 at 05:12
  • The best way would to be consistent no matter the user's locale and make sure dates are always transferred according to [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) as `yyyy-MM-dd` then there is no room for interpretation. – phuzi Oct 06 '22 at 09:16

2 Answers2

1

You could custom model binder for the DateTime format like below:

1.DateTimeModelBinderProvider:

public class DateTimeModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (DateTimeModelBinder.SUPPORTED_TYPES.Contains(context.Metadata.ModelType))
        {
            return new BinderTypeModelBinder(typeof(DateTimeModelBinder));
        }

        return null;
    }
}

2.DateTimeModelBinder:

public class DateTimeModelBinder : IModelBinder
{
    public static readonly Type[] SUPPORTED_TYPES = new Type[] { typeof(DateTime), typeof(DateTime?) };

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        if (!SUPPORTED_TYPES.Contains(bindingContext.ModelType))
        {
            return Task.CompletedTask;
        }

        var modelName = GetModelName(bindingContext);

        var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
        if (valueProviderResult == ValueProviderResult.None)
        {
            return Task.CompletedTask;
        }

        bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);

        var dateToParse = valueProviderResult.FirstValue;

        if (string.IsNullOrEmpty(dateToParse))
        {
            return Task.CompletedTask;
        }

        var dateTime = Helper.ParseDateTime(dateToParse);

        bindingContext.Result = ModelBindingResult.Success(dateTime);

        return Task.CompletedTask;
    }

    private string GetModelName(ModelBindingContext bindingContext)
    {
        if (!string.IsNullOrEmpty(bindingContext.BinderModelName))
        {
            return bindingContext.BinderModelName;
        }

        return bindingContext.ModelName;
    }
}


public class Helper
{
    public static DateTime? ParseDateTime(
        string dateToParse,
        string[] formats = null,
        IFormatProvider provider = null,
        DateTimeStyles styles = DateTimeStyles.None)
    {
        var CUSTOM_DATE_FORMATS = new string[]
            {    
            //"MM-dd-yyyy",
            "yyyy-MM-dd",
            "dd-MM-yyyy"
            };

        if (formats == null || !formats.Any())
        {
            formats = CUSTOM_DATE_FORMATS;
        }

        DateTime validDate;

        foreach (var format in formats)
        {
            if (format.EndsWith("Z"))
            {
                if (DateTime.TryParseExact(dateToParse, format,
                         provider,
                         DateTimeStyles.AssumeUniversal,
                         out validDate))
                {
                    return validDate;
                }
            }

            if (DateTime.TryParseExact(dateToParse, format,
                     provider, styles, out validDate))
            {
                return validDate;
            }
        }
        return null;
    }
}

3.Startup.cs:

services.AddControllers(option =>
{
     // add the custom binder at the top of the collection
     option.ModelBinderProviders.Insert(0, new DateTimeModelBinderProvider());
})

If you still want to display the dd-MM-yyyy format date,change your Startup.cs:

services.AddControllers(option =>
{
     option.ModelBinderProviders.Insert(0, new DateTimeModelBinderProvider());
}).AddJsonOptions(options =>
{
     options.JsonSerializerOptions.Converters.Add(new DateTimeConverter());
});

Result:

enter image description here

Reference:

http://www.vickram.me/custom-datetime-model-binding-in-asp-net-core-web-api

Update:

You could see that you can pass dd-MM-yyyy date to the action but the receive format is still as before.This is by design,refer to:

https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-3.1#globalization-behavior-of-model-binding-route-data-and-query-strings

Rena
  • 30,832
  • 6
  • 37
  • 72
0

You can add a JsonConverter in SerializerOptions

  public class DateTimeConverter : JsonConverter<DateTime>
{
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return DateTime.Parse(reader.GetString());
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm"));
    }
}


static readonly JsonSerializerOptions JsonSerOpt = new()
    {
        Converters =
        {
            new DateTimeConverter()
        }
    };
SmRiley
  • 147
  • 1
  • 7