0

I'm using ASP.NET MVC 4.0.40804. This is my Code:

public class FilesController : MyController
{
    public ActionResult List(int product, Dictionary<int, string> files)
    {
         return View();
    }
}

And this is the request:

http://localhost:7135/Files/List?product=2

I would expect that the "files" dictionary is null, however it crashes with "Specified cast is not valid". (see below). I believe the problem is related to this ASP.NET MVC bug report.

Is this actually a bug in ASP.NET MVC, or am I doing something wrong?

Specified cast is not valid.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidCastException: Specified cast is not valid. [InvalidCastException: Specified cast is not valid.]
System.Web.Mvc.CollectionHelpers.ReplaceDictionaryImpl(IDictionary2 dictionary, IEnumerable1 newContents) +131

[TargetInvocationException: Exception has been thrown by the target of an invocation.] System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor) +0
System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments) +92
System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) +108 System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters) +19
System.Web.Mvc.CollectionHelpers.ReplaceDictionary(Type keyType, Type valueType, Object dictionary, Object newContents) +178
System.Web.Mvc.DefaultModelBinder.UpdateDictionary(ControllerContext controllerContext, ModelBindingContext bindingContext, Type keyType, Type valueType) +1211 [..

1
  • 1
    Looks like this works fine in MVC5. I can repro your issue in MVC4 Commented Dec 30, 2014 at 21:10

2 Answers 2

1

It sure looks like you're running into that bug.

The bug was fixed over a year ago. Have you tried upgrading to MVC 5? That CodePlex bug report has a .cs file attached that provides a fixed dictionary binder. You can incorporate it using this in App_Start:

ModelBinder.Binders.DefaultBinder = new DefaultDictionaryBinder();

Here is the fixed binder (provided by Codeplex user richardprior):

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Web.Mvc;

/// <summary>
/// ASP.NET MVC Default Dictionary Binder, with a dictionary that doesn't bind your
/// route parameters when no values are passed
/// Adapted from: https://github.com/loune/MVCStuff/blob/master/Extensions/DefaultDictionaryBinder.cs
/// </summary>
public class FixedDictionaryBinder : DefaultModelBinder
{
    readonly IModelBinder _nextBinder;

    /// <summary>
    /// Create an instance of DefaultDictionaryBinder.
    /// </summary>
    public FixedDictionaryBinder()
        : this(null)
    {
    }

    /// <summary>
    /// Create an instance of DefaultDictionaryBinder.
    /// </summary>
    /// <param name="nextBinder">The next model binder to chain call. If null, by default, the DefaultModelBinder is called.</param>
    public FixedDictionaryBinder(IModelBinder nextBinder)
    {
        _nextBinder = nextBinder;
    }

    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        Type modelType = bindingContext.ModelType;

        if (modelType.AssemblyQualifiedName != null && modelType.AssemblyQualifiedName.StartsWith("System.Collections.Generic.IDictionary`2"))
        {
            object result = null;

            var ga = modelType.GetGenericArguments();
            var valueBinder = Binders.GetBinder(ga[1]);

            foreach (var key in GetValueProviderKeys(controllerContext))
            {
                if (!key.StartsWith(bindingContext.ModelName + "[", StringComparison.InvariantCultureIgnoreCase))
                    continue;

                var endbracket = key.IndexOf("]", bindingContext.ModelName.Length + 1, StringComparison.InvariantCultureIgnoreCase);
                if (endbracket == -1)
                    continue;

                object dictKey;
                try
                {
                    dictKey = ConvertType(key.Substring(bindingContext.ModelName.Length + 1, endbracket - bindingContext.ModelName.Length - 1), ga[0]);
                }
                catch (NotSupportedException)
                {
                    continue;
                }

                var innerBindingContext = new ModelBindingContext
                                              {
                                                  ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, ga[1]),
                                                  ModelName = key.Substring(0, endbracket + 1),
                                                  ModelState = bindingContext.ModelState,
                                                  PropertyFilter = bindingContext.PropertyFilter,
                                                  ValueProvider = bindingContext.ValueProvider
                                              };
                var newPropertyValue = valueBinder.BindModel(controllerContext, innerBindingContext);

                if (result == null)
                    result = CreateModel(controllerContext, bindingContext, modelType);

                if (!(bool)modelType.GetMethod("ContainsKey").Invoke(result, new [] { dictKey }))
                    modelType.GetProperty("Item").SetValue(result, newPropertyValue, new [] { dictKey });
            }

            return result;
        }

        return _nextBinder != null
                   ? _nextBinder.BindModel(controllerContext, bindingContext)
                   : base.BindModel(controllerContext, bindingContext);
    }

    private static IEnumerable<string> GetValueProviderKeys(ControllerContext context)
    {
        var keys = new List<string>();
        keys.AddRange(context.HttpContext.Request.Form.Keys.Cast<string>());
        keys.AddRange(((IDictionary<string, object>)context.RouteData.Values).Keys);
        keys.AddRange(context.HttpContext.Request.QueryString.Keys.Cast<string>());
        keys.AddRange(context.HttpContext.Request.Files.Keys.Cast<string>());
        return keys;
    }

    private static object ConvertType(string stringValue, Type type)
    {
        return TypeDescriptor.GetConverter(type).ConvertFrom(stringValue);
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you, this fix works. Small gotcha: Make sure you use IDictionary, and not Dictionary as your parameter type. The FixedDictionaryBinder specifically looks for IDictionary.
0

Make it an optional parameter:

public ActionResult List(int product, Dictionary<int, string> files = null)

1 Comment

This doesn't change anything, same crash.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.