Allow specific datetime formats in asp.net core app on json deserialization - asp.net-core

I want to allow specific datetime formats in my asp.net core app.
I have this code in Startup.cs on ConfigureServices method:
...
services.AddMvc()
.AddJsonOptions(options =>
{
...
options.SerializerSettings.DateFormatString = "dd/MM/yyyy HH:mm";
})
...
This property allow only one datetime format.
I'm needing something like this (with many datetime formats permitted):
...
services.AddMvc()
.AddJsonOptions(options =>
{
...
options.SerializerSettings.DateFormatString = { "dd/MM/yyyy HH:mm", "dd/MM/yyyy HH:mm:ss", "dd/MM/yyyy", ... };
});
...
Thanks.

This is not possible. Logically, how would the serializer know which format to actually apply? There's no Date type, so a DateTime, even if it no time component is set is still a DateTime and will simply return the time as midnight (00:00:00).
You're struggling here with a basic flaw in API design. An API should not return different types for the same member(s). If time is a component ever, then time should always be present, even if it's zeroed out. Returning different responses puts additional and sometimes impossible burdens on the client.

From https://learn.microsoft.com/en-us/dotnet/standard/datetime/system-text-json-support:
public class DateTimeConverterUsingDateTimeParse : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Debug.Assert(typeToConvert == typeof(DateTime));
return DateTime.Parse(reader.GetString());
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
class Program
{
private static void ProcessDateTimeWithCustomConverter()
{
JsonSerializerOptions options = new JsonSerializerOptions();
options.Converters.Add(new DateTimeConverterUsingDateTimeParse());
string testDateTimeStr = "04-10-2008 6:30 AM";
string testDateTimeJson = #"""" + testDateTimeStr + #"""";
DateTime resultDateTime = JsonSerializer.Deserialize<DateTime>(testDateTimeJson, options);
Console.WriteLine(resultDateTime);
string resultDateTimeJson = JsonSerializer.Serialize(DateTime.Parse(testDateTimeStr), options);
Console.WriteLine(Regex.Unescape(resultDateTimeJson));
}
}
This should handle any unambiguous date/time.

Related

How to handle unknown parameters in ASP.NET Core Actions

How to handle unknown parameters in ASP.NET Core? When I use [FromQuery] it just ignores the unknown parameters, but ideally it should return 400 if the parameter is unknown so the caller knows it needs to fix the parameters?
Example: GetRecords tries to use any StartDate or EndDate from query string, use default value if they are not specified.
But if a query like ?StartTime=2021/2/15&EndTime=2021/2/16, the code actually will return all records from DB as it treats like no parameters passed. Ideally it should throw an error to let caller know the parameter names are invalid.
class RecordQuery
{
public RecordQuery()
{
StartDate = DateTime.MinValue;
EndDateTime = DateTime.Now;
}
//...
}
class Controller
{
public async Task<ActionResult<RecordsResult>> GetRecords([FromQuery] RecordQuery query)
{
// query db where date < query.EndDateTime && date > query.StartDateTime;
}
}
When I use [FromQuery] it just ignores the unknown parameters
Actually, this is the default behavior of the querystring parameters. But you could return an Invalid Request status, so that the client knows that what it's trying to do isn't valid.
To implement it, you can use the ActionFilter, get both the action parameters and request query string queryParameters and make a judgement. Codes like below:
public class QueryActionFilter<T> : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
var model = context.ActionArguments.Values.OfType<T>().Single();
var modelProperties = model.GetType().GetProperties();
var queryParameters = context.HttpContext.Request.Query;
if (!queryParameters.Select(q => q.Key).All(queryParameter => modelProperties.Any(p => p.Name == queryParameter)))
{
context.Result = new BadRequestObjectResult("Querystring does not match");
}
}
}
Then in controller
[TypeFilter(typeof(QueryActionFilter<RecordQuery>))]
public async Task<ActionResult<RecordsResult>> GetRecords([FromQuery] RecordQuery query)
{
// query db where date < query.EndDateTime && date > query.StartDateTime;
}
You can see example https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-5.0&tabs=visual-studio#the-puttodoitem-method
if(StartDate == null){
return BadRequest();
}
Let's do the same thing with another input parameter(s) (query conditions)
If you want validate input parameter(s), use [Required] for model of [FromQuery], see https://stackoverflow.com/a/19279419/3728901 . In your case, it is model RecordQuery .

Date binding dd-mm-yyyy in ASP NET Core 3.1

Im trying send date in format dd/mm/yyyy as query string parameter but binding for date property not working for me, only if I send date US format mm/dd/yyyy , any idea how to solve?
Generally, we solve this situation by using Custom model binding.
You can see my code example below.
Action:
[HttpGet]
public object Demo([ModelBinder(BinderType = typeof(DateTimeModelBinder))] DateTime MyTime)
{
return Ok();
}
DateTimeModelBinder:
public class DateTimeModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var valueProviderResult = bindingContext.ValueProvider.GetValue("MyTime");
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
var value = valueProviderResult.FirstValue;
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
var TestTime = DateTime.ParseExact(value, "dd/MM/yyyy", CultureInfo.InvariantCulture);
bindingContext.Result = ModelBindingResult.Success(TestTime);
return Task.CompletedTask;
}
}
Url:https://localhost:xxxx/api/xxx/?mytime=19/05/2020
Result:
Another solution would be to send your date in UTC format. For example:
"2020-11-19T10:21:05Z"
Then ASP.Net Core will bind it automatically. Using UTC format is also considered a good practice. You easily cast your date object to UTC string using
string foo = yourDateTime.ToUniversalTime()
.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffK");
Source
Or in JavaScript
new Date('05 October 2011 14:48 UTC').toISOString();
Source

.NET Core Web API with ODATA serializing DateTime

I have a .NET Core Web API using ODATA. To support a legacy requirement, I'd like to change the default format of DateTime members to something like "yyyy-MM-dd HH:mm:ss", then be able to override the format on individual members. I understand this is different from JsonConverter and may require a custom ODATA serializer, but I am not sure how to do so.
Since .NET 5.0 this has moved.
You now add the ODataPayloadValueConverter on the options.AddRouteComponent
.AddOData(options =>
{
options.AddRouteComponents("odata", GetEdmModel(), action =>
{
action.AddSingleton(typeof(ODataPayloadValueConverter), new ODataByteArrayAsHexJsonConverter());
})
.Filter().Select().Expand().Count().OrderBy().SetMaxTop(50).SkipToken();
});
I've used it to replace the standard byte[] serializer (which uses base64 encoding), with an implementation that uses hex encoding instead.
public class ODataByteArrayAsHexJsonConverter : ODataPayloadValueConverter
{
public override object ConvertToPayloadValue(object value, IEdmTypeReference edmTypeReference)
{
if (value is byte[] byteValue)
{
return Helper.ConvertByteToHexString(byteValue);
}
else
{
return base.ConvertToPayloadValue(value, edmTypeReference);
}
}
public override object ConvertFromPayloadValue(object value, IEdmTypeReference edmTypeReference)
{
if (edmTypeReference.IsByte() && value is string stringValue)
{
return Helper.ConvertHexStringToByte(stringValue);
}
else
{
return base.ConvertFromPayloadValue(value, edmTypeReference);
}
}
}
I found a good solution using ODataPayloadValueConverter. Below is the implementation for DateTime, but you could easily add other types as needed.
class MyPayloadValueConverter : ODataPayloadValueConverter
{
public override object ConvertToPayloadValue(object value, IEdmTypeReference edmTypeReference)
{
if (value is DateTime)
{
return String.Format("{0:yyyy-MM-dd HH:mm:ss}", value, CultureInfo.InvariantCulture);
}
else
{
return base.ConvertToPayloadValue(value, edmTypeReference);
}
}
public override object ConvertFromPayloadValue(object value, IEdmTypeReference edmTypeReference)
{
if (edmTypeReference.IsDate() && value is string)
{
return DateTime.Parse((string)value, CultureInfo.InvariantCulture);
}
else
{
return base.ConvertFromPayloadValue(value, edmTypeReference);
}
}
}
Wire up your converter in Startup.cs like this:
app.UseEndpoints(endpoints =>
{
...
endpoints.MapODataRoute("odata", "odata", action =>
{
action.AddService(Microsoft.OData.ServiceLifetime.Singleton, typeof(ODataPayloadValueConverter), pvc => new MyPayloadValueConverter());
});
});

Converting String to Date when using functional WebFlux

When we send a URL with request parameters that needs to be converted to date, in SpringMVC we can do something like the code below in the controller and the fasterxml json library does the automatic conversion!
public String getFare(##RequestParam(value = "flightDate") #DateTimeFormat(iso = ISO.DATE) LocalDate date)
But how to achieve the same when we use the HandlerFunction (Spring webflux)? For example, in my HandlerFunction
public HandlerFunction<ServerResponse> getFare = serverRequest ->
{
Optional<String> flightDate = serverRequest.queryParam("flightDate");
}
The code serverRequest.queryParam("flightDate") gives a String. Is it possible to get the same automatic conversion here?
No. (you can look at Spring's source code and see that no other way to get the queryParams other than getting it as Optional<String>)
You must convert the field to Date yourself
Date flightDate = request.queryParam("flightDate ")
.map(date -> {
try {
return new SimpleDateFormat("dd-MMM-yyyy").parse(date);
} catch (ParseException e) {
return null;
}
}).orElse(null);

get Max method for DateTime type

I am looking for an Enumerable.Max method that returns a DateTime, but it seems this method does not exist.
I tried using reflection to find it, but none of the results returned a DateTime:
Dim test = GetType(Enumerable).GetMethods(BindingFlags.Public Or BindingFlags.Static).
Where(Function(m) m.Name = "Max")
Is there such a method?
Enumerable has no Max method specified for DateTime type, but it has generic Max(IEnumerable<TSource>) method that can process objects of classes that implements IComparable<T> or IComparable interfaces, including DateTime.
So you can use this method:
class Program
{
delegate DateTime MaxDelegate(IEnumerable<DateTime> values);
static void Main(string[] args)
{
MaxDelegate d = Enumerable.Max<DateTime>;
var values = new DateTime[] { DateTime.MinValue, DateTime.Now, DateTime.MaxValue };
var result = d(values);
Console.WriteLine(result);
}
}