Skip to content

Commit

Permalink
Make date model binding more flexible
Browse files Browse the repository at this point in the history
An IModelBinderProvider is configured that will attempt to model bind
all Date, DateTime and DateOnly instances in a way that works with the
Date input component. However, sometimes apps pass dates around without
using the Date input component (e.g. as a query parameter) and these
model binders are blocking the built-in ones from running and working.

This exposes a method on our options class to create one of our model
binders. With this, consumers mix in our model binders with their own as
they see fit.
  • Loading branch information
gunndabad committed Apr 13, 2023
1 parent f3eeeaa commit 0ab5378
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 12 deletions.
32 changes: 32 additions & 0 deletions src/GovUk.Frontend.AspNetCore/GovUkFrontendAspNetCoreOptions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using GovUk.Frontend.AspNetCore.HtmlGeneration;
using GovUk.Frontend.AspNetCore.ModelBinding;
using GovUk.Frontend.AspNetCore.TagHelpers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace GovUk.Frontend.AspNetCore
{
Expand Down Expand Up @@ -75,5 +77,35 @@ public GovUkFrontendAspNetCoreOptions()
/// The default is <c>true</c>.
/// </remarks>
public bool PrependErrorToTitle { get; set; }

/// <summary>
/// Creates an <see cref="IModelBinder"/> for Date input components using the <see cref="DateInputModelConverter"/>
/// registered in <see cref="DateInputModelConverters"/> for the specified <see cref="Type"/>.
/// </summary>
/// <param name="dateModelType">The type that the created <see cref="IModelBinder"/> should support.</param>
/// <returns>
/// A <see cref="IModelBinder"/>
/// or <see langword="null"/> if there is no <see cref="DateInputModelConverter"/> registered that supports <paramref name="dateModelType"/>.
/// </returns>
public IModelBinder? GetDateInputModelBinder(Type dateModelType)
{
foreach (var converter in DateInputModelConverters)
{
if (converter.CanConvertModelType(dateModelType))
{
return GetDateInputModelBinder(converter);
}
}

return null;
}

/// <summary>
/// Creates an <see cref="IModelBinder"/> for Date input components using the specified <see cref="DateInputModelConverter"/>.
/// </summary>
/// <param name="converter">The <see cref="DateInputModelConverter"/> to use to convert to and from a <see cref="Date"/>.</param>
/// <returns>A <see cref="IModelBinder"/>.</returns>
public IModelBinder GetDateInputModelBinder(DateInputModelConverter converter) =>
new DateInputModelBinder(converter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,21 @@ namespace GovUk.Frontend.AspNetCore.ModelBinding
{
internal class DateInputModelBinderProvider : IModelBinderProvider
{
private readonly DateInputModelConverter[] _dateInputModelConverters;
private readonly GovUkFrontendAspNetCoreOptions _options;

public DateInputModelBinderProvider(GovUkFrontendAspNetCoreOptions options)
{
Guard.ArgumentNotNull(nameof(options), options);

_dateInputModelConverters = options.DateInputModelConverters.ToArray();
_options = options;
}

public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
Guard.ArgumentNotNull(nameof(context), context);

var modelType = context.Metadata.UnderlyingOrModelType;

foreach (var converter in _dateInputModelConverters)
{
if (converter.CanConvertModelType(modelType))
{
return new DateInputModelBinder(converter);
}
}

return null;
return _options.GetDateInputModelBinder(modelType);
}
}
}

0 comments on commit 0ab5378

Please sign in to comment.