Передача DateTimeOffset в качестве атрибута маршрута - PullRequest
0 голосов
/ 03 октября 2019

Я рассмотрел этот вопрос о параметрах запроса DateTimeOffset , но я явно хочу иметь возможность передавать DateTimeOffset в качестве атрибута маршрута, а не параметра запроса (вероятно, потребуется дополнительная маршрутизация после даты). Кроме того, я хочу маршрут, который не включает дату, например так:

[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
}

Эти маршруты оба возвращают InvalidOperationException: The constraint reference 'DateTimeOffset' could not be resolved to a type. Register the constraint type with 'Microsoft.AspNetCore.Routing.RouteOptions.ConstraintMap'.

Чтобы попытаться это исправить, я добавил и зарегистрировал ограничение:

//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
}

После этого изменения вызов первого маршрута возвращает InvalidOperationException: Unable to resolve service for type 'System.DateTimeOffset', в то время как второй с DateTimeOffset (в надлежащем формате jul времени Зулу, например, 2019-10-02T05: 04: 18.070Z) возвращает404.

Следующие вопросы @Kirk Larkin ... Контроллер берет IDatePeriodRepository, это определено в другом проекте. Это в какой-то момент DateTimeOffset передается в конструктор

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);
    }
}

Это построено с расширением коллекции сервисов

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"));

Как я могу иметь DateTimeOffset в качестве атрибута маршрута?

Smudge202'a ответ сработал, но мне также пришлось изменить метод сопоставления constrinat следующим образом:

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;
}

1 Ответ

2 голосов
/ 03 октября 2019

Вы вводите DateTimeOffset в DatePeriodRepository, однако ваша настройка DI настраивает класс Options.

Измените DatePeriodRepository, чтобы ожидать настроенный класс Options:

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);
    }
}
...