Search code examples
c#servicestacknodatimeautoquery-servicestack

Using ServiceStack Autoquery With Value Types that don't implement IConvertible


I was trying to use AutoQuery with a NodaTime.LocalDate on a query parameter and I get the following exception when I try to filter using that date field, specifically >MyDate=2020-01-01 (ordering is unaffected):

[MyEndpoint: 5/23/2016 4:19:51 PM]: [REQUEST: {}] System.InvalidCastException: Invalid cast from 'System.String' to 'NodaTime.LocalDate'. at System.Convert.DefaultToType(IConvertible value, Type targetType, IFormatProvider provider) at ServiceStack.TypedQuery`2.AppendUntypedQueries(SqlExpression`1 q, Dictionary`2 dynamicParams, String defaultTerm, IAutoQueryOptions options, Dictionary`2 aliases) at ServiceStack.TypedQuery`2.CreateQuery(IDbConnection db, IQueryDb dto, Dictionary`2 dynamicParams, IAutoQueryOptions options) at ServiceStack.AutoQuery.CreateQuery[From](IQueryDb`1 dto, Dictionary`2 dynamicParams, IRequest req) at ServiceStack.AutoQueryServiceBase.Exec[From](IQueryDb`1 dto) at ServiceStack.Host.ServiceRunner`1.Execute(IRequest request, Object instance, TRequest requestDto)

I tracked it down to this line of code that uses Convert.ChangeType(...) because NodaTime.LocalDate is a struct and not an enum:

var value = strValue == null ? 
      null 
    : isMultiple ? 
      TypeSerializer.DeserializeFromString(strValue, Array.CreateInstance(fieldType, 0).GetType())
    : fieldType == typeof(string) ? 
      strValue
    : fieldType.IsValueType && !fieldType.IsEnum ? //This is true for NodaTime.LocalDate
      Convert.ChangeType(strValue, fieldType) :    //NodaTime.LocalDate does not implement IConvertible, so this throws
      TypeSerializer.DeserializeFromString(strValue, fieldType);

I'm using my NodaTime ServiceStack serialization library so the behavior of TypeSerializer.DeserializeFromString(strValue, fieldType) is what I actually want in this case.

Workarounds I see are:

  • Use MyDateDateBetween=2020-01-01,9999-12-31 in the query string as that code path uses the custom serialization I have specified (cumbersome)
  • Use DateTime instead of NodaTime.LocalDate (I want to use NodaTime.LocalDate)
  • Not use AutoQuery (I want to)
  • NodaTime.LocalDate implements IConvertible (not likely)

Is there another way to get auto query filters to work with value types that don't implement IConvertible?


Solution

  • I've just added wrapped these lines in a new ChangeTo() extension method with an extra check to check for implementing IConvertible in this commit:

    public static object ChangeTo(this string strValue, Type type)
    {
        if (type.IsValueType && !type.IsEnum
            && type.HasInterface(typeof(IConvertible)))
        {
            try
            {
                return Convert.ChangeType(strValue, type);
            }
            catch (Exception ex)
            {
                Tracer.Instance.WriteError(ex);
            }
        }
        return TypeSerializer.DeserializeFromString(strValue, type);
    }
    

    And changed AutoQuery to use it so NodaTime's LocalDate should now fall through to the TypeSerializer.

    This change is available from v4.0.57 that's now available on MyGet.