简体   繁体   中英

IDictionary Property on model throwing System.InvalidCastException

I have a database model which stores settings as bits on a 32bit integer using .HasFlag . The front-end uses AngularJs (1) which sadly does not allow bit-wise operations in expressions . As such I created an extension method to convert from an Enum to a dictionary of each flag and if it is turned on or not:

public static IDictionary<TEnum, bool> GetAllFlags<TEnum>(this TEnum obj) where TEnum: Enum
{
    return Enum.GetValues(typeof(TEnum))
        .Cast<TEnum>()
        .ToDictionary(flag => flag, flag => obj.HasFlag(flag));
}

I also created an extension method to set the values back but that relies on other extension methods and it does not get called (as explained below) so for the sake of brevity I will leave them out.

Here is an example of the enum being used (note it's type is defaulted to int and has very few options, but it does define one for 0 which is weird)

public enum Settings
{
    None = 0,
    Setting1 = 1,
    Setting2 = 2,
    Setting3 = 4,
}

All this is exposed as a Property on an object like so

// SettingsFlags comes from another partial and is a Settings enum persisted to the database as an `INT`
public parital class Options
{
    IDictionary<Settings, bool> Flags {
        get { return SettingsFlags.GetAllFlags(); }
        set { SettingsFlags.SetAllFlags(value); } // this never gets called
    }
}

This works great when being serialized to JSON for the client. However when it is received in a request it throws the following exception:

System.InvalidCastException: Specified cast is not valid.
   at System.Web.Mvc.DefaultModelBinder.CollectionHelpers.ReplaceDictionaryImpl[TKey,TValue](IDictionary`2 dictionary, IEnumerable`1 newContents)

When entering the Debugger the get block is called and returns the expected dictionary. After that the error is thrown with no additional steps in the debugger.

Here is an example of what the endpoint looks like (it is not REST compliant):

public JsonResult UpdateSingleItem(Options options, ...)
{ // breakpoint here is never called.
    ....
}`

Using ReSharper with DotPeak to inspect the code gives me the following for the method that is throwing the error:

// System.Web.Mvc.DefaultModelBuilder.CollectionHelpers
private static void ReplaceDictionaryImpl<TKey, TValue>(
  IDictionary<TKey, TValue> dictionary,
  IEnumerable<KeyValuePair<object, object>> newContents)
{
  dictionary.Clear();
  foreach (KeyValuePair<object, object> newContent in newContents)
    {
    TKey key = (TKey) newContent.Key;
    TValue obj = newContent.Value is TValue ? (TValue) newContent.Value : default (TValue);
    dictionary[key] = obj;
  }
}

I can't be certain, but I'm guessing the client is sending the enum as a string value?

{
"Setting1":true,
"Setting2":false
}

This will fail as the default binding is trying to cast that string back to an enum. If it passed back the integer value of the enum instead of the string, I think this would work. If you are getting a string value, then you might need to create a custom model binder.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM