Reputation: 4318
I have an WebAPI project that takes in ISO date strings inside the input model. I have been parsing these using DateTimeOffset?
. I want to banish BCL datetimes from my project so I want to find a way to directly bind these strings to Instant
.
public class MyInputModel
{
public DateTimeOffset? OldTime { get; set; }
public Instant NewTime { get; set; }
}
An example JSON input model looks like this:
{
"oldtime":"2016-01-01T12:00:00Z",
"newtime":"2016-01-01T12:00:00Z"
}
And my controller code is:
[HttpPost]
public async Task<IActionResult> PostTimesAsync([FromBody]MyInputModel input)
{
Instant myOldTime = Instant.FromDateTimeUtc(input.oldTime.Value.UtcDateTime);
Instant myNewTime = input.newTime; // This cannot be used with ISO date strings.
}
I attempted to build a custom model binder as follows. This works for models in the query string but not for those in the body of a POST request. How can I bind date input in an ISO 8601 string format to a NodaTime Instant?
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (!String.IsNullOrWhiteSpace(bindingContext.ModelName) &&
bindingContext.ModelType == typeof(Instant?) &&
bindingContext.ValueProvider.GetValue(bindingContext.ModelName) != null)
{
Instant? value;
var val = bindingContext.ValueProvider
.GetValue(bindingContext.ModelName).FirstValue as string;
if (String.IsNullOrWhiteSpace(val))
{
bindingContext.Result = ModelBindingResult.Failed();
return Task.FromResult(0);
}
else if (InstantExtensions.TryParse(val, out value))
{
bindingContext.Result = ModelBindingResult.Success(value);
return Task.FromResult(0);
}
else
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName,
"The date is invalid.");
}
}
bindingContext.Result = ModelBindingResult.Failed();
return Task.FromResult(0);
}
public static bool TryParse(string value, out Instant? result)
{
result = null;
// If this is date-only, add Utc Midnight to the value.
if (value.Length.Equals(10))
{
value += "T00:00:00Z";
}
// Trim milliseconds if present
var decimalPointIndex = value.IndexOf('.');
if (decimalPointIndex > 0)
{
value = value.Substring(0, decimalPointIndex) + "Z";
}
// Attempt to parse
var parseResult = InstantPattern.GeneralPattern.Parse(value);
if (parseResult.Success)
{
result = parseResult.Value;
return true;
}
return false;
}
Upvotes: 2
Views: 676
Reputation: 2604
You should add your model binder like this: ( in WebApiConfig
Register
method )
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.BindParameter(typeof(Instant), new InstantModelBinder())
...
}
}
WebApiConfig.Register is called in Configuration
function in Startup.cs
file. In most cases like this:
var config = new HttpConfiguration();
WebApiConfig.Register(config);
If it isn't called, you can just add this line:
config.BindParameter(typeof(Instant), new InstantModelBinder())
where HttpConfiguration
object is being created in Startup.cs
.
Upvotes: 1