Reputation: 522
I've looked at this question about DateTimeOffset query parameters, but I explicitly want to be able to pass a DateTimeOffset as a route attribute, not a query parameter (will likely need further routing after the date). In addition I want a route that does not include the date, like so:
[Route("api/Controller/Action/")]
[HttpGet]
public async Task<ActionResult> ControllerAction()
{
//blah
}
[Route("api/Controller/Action/{dateParam:DateTimeOffset}")]
[HttpGet]
public async Task<ActionResult> ControllerAction(DateTimeOffset dateParam)
{
//blah
}
These routes both return an InvalidOperationException: The constraint reference 'DateTimeOffset' could not be resolved to a type. Register the constraint type with 'Microsoft.AspNetCore.Routing.RouteOptions.ConstraintMap'.
To try and fix this I added and registered a constraint:
//Constraint
public class DateTimeOffsetConstraint : IRouteConstraint
{
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values,
RouteDirection routeDirection)
{
if (values.TryGetValue(routeKey, out var value) && value != null)
{
return value is DateTimeOffset;
}
return false;
}
}
//In startup configure services
services.Configure<RouteOptions>(opt => opt.ConstraintMap.Add("DateTimeOffsetConstraint", typeof(DateTimeOffsetConstraint)));
//In controller, altered second route to use constraint
[Route("api/Controller/Action/{dateParam:DateTimeOffsetConstraint}")]
[HttpGet]
public async Task<ActionResult> ControllerAction(DateTimeOffset dateParam)
{
//blah
}
After that change calling the first route returns InvalidOperationException: Unable to resolve service for type 'System.DateTimeOffset'
, while the second with the DateTimeOffset (in the proper Zulu time json format e.g. 2019-10-02T05:04:18.070Z) returns a 404.
Following questions by @Kirk Larkin... The controller takes an IDatePeriodRepository, this is defined in another project. This at one point has a DateTimeOffset passed into the constructor
public interface IDatePeriodRepository
{
Task<int> GetDatePeriod();
Task<int> GetDatePeriod(DateTimeOffset date);
}
//Defined in seperate file
internal class DatePeriodRepository: IDatePeriodRepository
{
private readonly DateTimeOffset _dateCycleStart;
public DatePeriodRepository(DateTimeOffset dateCycleStart)
{
_dateCycleStart = dateCycleStart;
}
public Task<int> GetDatePeriod()
{
return GetDatePeriod(DateTimeOffset.Now);
}
public Task<int> GetDatePeriod(DateTimeOffset date)
{
var yearDiff = (date.Year - _billingCycleStart.Year) * 12;
var monthDiff = yearDiff + date.Month - _dateCycleStart.Month;
return Task.FromResult(monthDiff);
}
}
This is constructed with a service collection extension
public static class ServiceCollectionExtension
{
public static IServiceCollection AddDatePeriodRepository(this IServiceCollection services, Action<Options> configuration)
{
var options = new Options();
configuration(options);
services.AddSingleton(options);
services.Configure(configuration);
return services.AddScoped<IDatePeriodRepository, DatePeriodRepository>();
}
}
//Used in startup ConfigureServices
services.AddBillingPeriodRepository(opt =>
opt.BillingPeriodCycleStart = Configuration.GetValue<DateTimeOffset>("BillingPeriodCycleStart"));
How can I have a DateTimeOffset as a route attribute?
Smudge202'a answer worked, but I also had to alter the constrinat match method like so:
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values,
RouteDirection routeDirection)
{
if (values.TryGetValue(routeKey, out var value) && value != null)
{
return DateTimeOffset.TryParse(value.ToString(), out _);
}
return false;
}
Upvotes: 1
Views: 1042
Reputation: 4687
You're injecting a DateTimeOffset
into DatePeriodRepository
, however your DI setup is configuring an Options
class.
Change DatePeriodRepository
to expect the configured Options
class:
internal class DatePeriodRepository: IDatePeriodRepository
{
private readonly DateTimeOffset _dateCycleStart;
public DatePeriodRepository(Options options)
{
_dateCycleStart = options.BillingPeriodCycleStart;
}
public Task<int> GetDatePeriod()
{
return GetDatePeriod(DateTimeOffset.Now);
}
public Task<int> GetDatePeriod(DateTimeOffset date)
{
var yearDiff = (date.Year - _billingCycleStart.Year) * 12;
var monthDiff = yearDiff + date.Month - _dateCycleStart.Month;
return Task.FromResult(monthDiff);
}
}
Upvotes: 2