Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Custom DateTime Model Binding in ASP.NET Core Web API
public class DateTimeModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
if (bindingContext.ModelType != typeof(DateTime?))
{
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 = ParseDate(bindingContext, dateToParse);
bindingContext.Result = ModelBindingResult.Success(dateTime);
return Task.CompletedTask;
}
private DateTime? ParseDate(ModelBindingContext bindingContext, string dateToParse)
{
var attribute = GetDateTimeModelBinderAttribute(bindingContext);
var dateFormat = attribute?.DateFormat;
if (string.IsNullOrEmpty(dateFormat))
{
return ParseDateTime(dateToParse);
}
return ParseDateTime(dateToParse, new string[] { dateFormat });
}
private DateTimeModelBinderAttribute GetDateTimeModelBinderAttribute(ModelBindingContext bindingContext)
{
var modelName = GetModelName(bindingContext);
var paramDescriptor = bindingContext.ActionContext.ActionDescriptor.Parameters
.Where(x => x.ParameterType == typeof(DateTime?))
.Where((x) =>
{
// See comment in GetModelName() on why we do this.
var paramModelName = x.BindingInfo?.BinderModelName ?? x.Name;
return paramModelName.Equals(modelName);
})
.FirstOrDefault();
var ctrlParamDescriptor = paramDescriptor as ControllerParameterDescriptor;
if (ctrlParamDescriptor == null)
{
return null;
}
var attribute = ctrlParamDescriptor.ParameterInfo
.GetCustomAttributes(typeof(DateTimeModelBinderAttribute), false)
.FirstOrDefault();
return (DateTimeModelBinderAttribute)attribute;
}
private string GetModelName(ModelBindingContext bindingContext)
{
//The "Name" property of the ModelBinder attribute can be used to specify the
//route parameter name when the action parameter name is different from the route parameter name.
//For instance, when the route is /api/{birthDate} and the action parameter name is "date"
//we need to say [DateTimeModelBinder(Name ="birthDate")]
//The bindingContext.BinderModelName contains the "Name" property of the ModelBinder attribute
if (string.IsNullOrEmpty(bindingContext.BinderModelName))
{
return bindingContext.ModelName;
}
return bindingContext.BinderModelName;
}
public DateTime? ParseDateTime(
string dateToParse,
string[] formats = null,
IFormatProvider provider = null,
DateTimeStyles styles = DateTimeStyles.AssumeLocal)
{
var CUSTOM_DATE_FORMATS = new string[]
{
"yyyyMMddTHHmmssZ",
"yyyyMMddTHHmmZ",
"yyyyMMddTHHmmss",
"yyyyMMddTHHmm",
"yyyyMMddHHmmss",
"yyyyMMddHHmm",
"yyyyMMdd",
"yyyy-MM-ddTHH-mm-ss",
"yyyy-MM-dd-HH-mm-ss",
"yyyy-MM-dd-HH-mm",
"yyyy-MM-dd",
"MM-dd-yyyy"
};
if (formats == null)
{
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;
}
}
public class DateTimeModelBinderAttribute : ModelBinderAttribute
{
public string DateFormat { get; set; }
public DateTimeModelBinderAttribute()
: base(typeof(DateTimeModelBinder))
{
}
}
public class DateTimeModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(DateTime?))
{
return new DateTimeModelBinder();
}
return null;
}
}
[Route("api/[controller]")]
public class MainController : Controller
{
[Route("EchoDate/{date}")]
[HttpGet]
public DateTime? EchoDate(
[DateTimeModelBinder]
DateTime? date)
{
return date;
}
[Route("EchoCustomDate/{date}")]
[HttpGet]
public DateTime? EchoCustomDateFormat(
[DateTimeModelBinder(DateFormat = "yyyyMMdd")]
DateTime? date)
{
return date;
}
}
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(option =>
{
// add our custom binder to beginning of collection
option.ModelBinderProviders.Insert(0, new DateTimeModelBinderProvider());
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment