Asp.net mvc 3 MVC 3控制器操作中的可选字典参数

Asp.net mvc 3 MVC 3控制器操作中的可选字典参数,asp.net-mvc-3,Asp.net Mvc 3,我有一个MVC 3控制器动作定义如下: public ActionResult _UpdateFilter(int a, string b, List<string> c) <input type="hidden" id="id-@(something)-Key" name="sometimesSet[@(idx)].Key" value="@(myIntValue)" /> <input type="hidden" id="id-@(something)-Val

我有一个MVC 3控制器动作定义如下:

public ActionResult _UpdateFilter(int a, string b, List<string> c)
<input type="hidden" id="id-@(something)-Key" name="sometimesSet[@(idx)].Key" value="@(myIntValue)" />
<input type="hidden" id="id-@(something)-Value" name="sometimesSet[@(idx)].Value" value="@(myBoolValue)" /> 
public ActionResult _UpdateFilter(int a, string b, List<string> c, Dictionary<int, bool> sometimesSet = null)
(问题末尾的长堆栈跟踪)

问题

我收集的路由映射无法确定
SomeTimeSet
是可选的,但我不知道如何正确配置它。我该怎么做?我没有在Global.asax中编辑路线定义

堆栈跟踪

at System.Collections.Generic.Dictionary`2.FindEntry(TKey key)
at System.Collections.Generic.Dictionary`2.ContainsKey(TKey key)
at System.Web.Mvc.ValueProviderUtil.GetKeysFromPrefix(IEnumerable`1 collection, String prefix)
at System.Web.Mvc.DictionaryValueProvider`1.GetKeysFromPrefix(String prefix)
at System.Web.Mvc.ValueProviderCollection.GetKeysFromPrefixFromProvider(IValueProvider provider, String prefix)
at System.Web.Mvc.ValueProviderCollection.<>c__DisplayClass11.<GetKeysFromPrefix>b__c(IValueProvider provider)
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)
at System.Web.Mvc.ValueProviderCollection.GetKeysFromPrefix(String prefix)
at System.Web.Mvc.DefaultModelBinder.UpdateDictionary(ControllerContext controllerContext, ModelBindingContext bindingContext, Type keyType, Type valueType)
at System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
at System.Web.Mvc.DefaultModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
at System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
at System.Web.Mvc.ControllerActionInvoker.GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass25.<BeginInvokeAction>b__1e(AsyncCallback asyncCallback, Object asyncState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state)
at System.Web.Mvc.Controller.<>c__DisplayClass1d.<BeginExecuteCore>b__17(AsyncCallback asyncCallback, Object asyncState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state)
at System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state)
at System.Web.Mvc.MvcHandler.<>c__DisplayClass6.<>c__DisplayClassb.<BeginProcessRequest>b__3(AsyncCallback asyncCallback, Object asyncState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout)
at System.Web.Mvc.MvcHandler.<>c__DisplayClass6.<BeginProcessRequest>b__2()
at System.Web.Mvc.SecurityUtil.<>c__DisplayClassb`1.<ProcessInApplicationTrust>b__a()
at System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f)
at System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action)
at System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust[TResult](Func`1 func)
at System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state)
at System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state)
at System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
at System.Collections.Generic.Dictionary`2.FindEntry(TKey)
at System.Collections.Generic.Dictionary`2.ContainsKey(TKey)
位于System.Web.Mvc.ValueProviderUtil.GetKeysFromPrefix(IEnumerable`1集合,字符串前缀)
位于System.Web.Mvc.DictionaryValueProvider`1.GetKeysFromPrefix(字符串前缀)
位于System.Web.Mvc.ValueProviderCollection.GetKeysFromPrefixFromProvider(IValueProvider provider,字符串前缀)
在System.Web.Mvc.ValueProviderCollection.c__DisplayClass11.b_;c(IValueProvider provider)
在System.Linq.Enumerable.WhereSelectEnumerableInterator`2.MoveNext()中
在System.Linq.Enumerable.WhereSelectEnumerableInterator`2.MoveNext()中
位于System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1源)
位于System.Web.Mvc.ValueProviderCollection.GetKeysFromPrefix(字符串前缀)
位于System.Web.Mvc.DefaultModelBinder.UpdateDictionary(ControllerContext ControllerContext,ModelBindingContext bindingContext,Type keyType,Type valueType)
位于System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext ControllerContext,ModelBindingContext bindingContext)
位于System.Web.Mvc.DefaultModelBinder.BindModel(ControllerContext ControllerContext,ModelBindingContext bindingContext)
位于System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ControllerContext ControllerContext,ParameterDescriptor ParameterDescriptor)
位于System.Web.Mvc.ControllerActionInvoker.GetParameterValues(ControllerContext ControllerContext,ActionDescriptor ActionDescriptor)
在System.Web.Mvc.Async.AsyncControllerActionInvoker.c_uuDisplayClass25.b_u1E(AsyncCallback AsyncCallback,Object asyncState)
位于System.Web.Mvc.Async.AsyncResultRapper.WrappedAsyncResult`1.Begin(异步回调、对象状态、Int32超时)
位于System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext ControllerContext,String actionName,AsyncCallback回调,对象状态)
在System.Web.Mvc.Controller.c__displayClassId.b_uu 17(AsyncCallback AsyncCallback,Object asyncState)
位于System.Web.Mvc.Async.AsyncResultRapper.WrappedAsyncResult`1.Begin(异步回调、对象状态、Int32超时)
位于System.Web.Mvc.Controller.BeginExecuteCore(异步回调,对象状态)
位于System.Web.Mvc.Async.AsyncResultRapper.WrappedAsyncResult`1.Begin(异步回调、对象状态、Int32超时)
位于System.Web.Mvc.Controller.BeginExecute(RequestContext RequestContext、AsyncCallback回调、对象状态)
位于System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.BeginExecute(RequestContext RequestContext、AsyncCallback回调、对象状态)
在System.Web.Mvc.MvcHandler.c__DisplayClass6.c__DisplayClassb.b__3(AsyncCallback AsyncCallback,Object asyncState)
位于System.Web.Mvc.Async.AsyncResultRapper.WrappedAsyncResult`1.Begin(异步回调、对象状态、Int32超时)
在System.Web.Mvc.MvcHandler.c_uuudisplayClass6.b_uuu2()中
在System.Web.Mvc.SecurityUtil.c__DisplayClassb`1.b__a()中
在System.Web.Mvc.SecurityUtil.b_0(操作f)
位于System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(操作)
位于System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust[TResult](Func`1 Func)
位于System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext、AsyncCallback回调、对象状态)
位于System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext HttpContext,AsyncCallback回调,对象状态)
位于System.Web.Mvc.MvcHandler.System.Web.IHTTPassynchandler.BeginProcessRequest(HttpContext上下文、AsyncCallback cb、Object extraData)
在System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()中
在System.Web.HttpApplication.ExecuteStep(IExecutionStep步骤,布尔值&同步完成)

我发现最干净的解决方案是实现我自己的ModelBinder,它了解如何填充
字典
,即使在线路上找不到相关值(它将返回一个空的
字典

这是密码

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

namespace My.ModelBinders
{
    /// <summary>
    /// Binds to a generic Dictionary using the basic format outlined at 
    /// http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
    /// with the relaxation that the indices can be arbitrary integers (need not start at 0 or be sequential).
    /// Returns an empty dictionary of no matching parameters found on the wire rather than throwing
    /// an Exception as the current default binder does.
    /// </summary>
    /// <typeparam name="K">Key type</typeparam>
    /// <typeparam name="V">Value type</typeparam>
    public class OptionalDictionaryBinder<K,V> : CustomBinderBase, IModelBinder
    {
        /// <summary>
        /// Pull key/value pairs out of request.  Modified from
        /// https://github.com/loune/MVCStuff/blob/master/Extensions/DefaultDictionaryBinder.cs
        /// Files not currently supported.
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        private IEnumerable<KeyValuePair<string, string>> GetValueProviderData(ControllerContext context)
        {
            foreach (var fk in context.HttpContext.Request.Form.Keys)
            {
                yield return new KeyValuePair<string, string>((string)fk, context.HttpContext.Request.Form[(string)fk]);
            }

            foreach (var rd in context.RouteData.Values)
            {
                yield return new KeyValuePair<string, string>(rd.Key, (string)rd.Value);
            }

            foreach (var qp in context.HttpContext.Request.QueryString)
            {
                yield return new KeyValuePair<string, string>((string)qp, context.HttpContext.Request.QueryString[(string)qp]);
            }
        }

        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (controllerContext == null) 
            {
                throw new ArgumentNullException("controllerContext");
            }
            if (bindingContext == null)
            {
                throw new ArgumentNullException("bindingContext");
            }

            Dictionary<K, V> dict = new Dictionary<K, V>();
            Dictionary<int, K> keys = new Dictionary<int, K>();
            Dictionary<int, V> values = new Dictionary<int, V>();

            foreach (KeyValuePair<string,string> formParam in GetValueProviderData(controllerContext))
            {
                if (formParam.Key.StartsWith(bindingContext.ModelName, StringComparison.InvariantCultureIgnoreCase))
                {
                    int startbracket = formParam.Key.IndexOf("[");
                    if (startbracket != bindingContext.ModelName.Length) throw new Exception("Did not find [ directly after model name");

                    int endbracket = formParam.Key.IndexOf("]", bindingContext.ModelName.Length + 1);
                    if (endbracket == -1) throw new Exception("Did not find closing bracket in " + formParam);

                    int idx;
                    string idxText = formParam.Key.Substring(bindingContext.ModelName.Length + 1, endbracket - bindingContext.ModelName.Length - 1);
                    if (!int.TryParse(idxText, out idx))
                    {
                        throw new Exception("Could not parse numeric index from " + formParam);
                    }

                    if (formParam.Key.EndsWith(".Key", StringComparison.InvariantCultureIgnoreCase))
                    {
                        if (keys.ContainsKey(idx))
                        {
                            throw new Exception("The index " + idx + " was repeated.");
                        }

                        K dictKey;
                        try
                        {
                            dictKey = (K)Convert.ChangeType(formParam.Value, typeof(K));
                            keys.Add(idx, dictKey);
                        }
                        catch (NotSupportedException)
                        {
                            throw new Exception("The given key '" + formParam.Key + "' could not be converted to type K");
                        }
                    }
                    else if (formParam.Key.EndsWith(".Value", StringComparison.InvariantCultureIgnoreCase))
                    {
                        if (values.ContainsKey(idx))
                        {
                            throw new Exception("The index " + idx + " was repeated.");
                        }

                        V dictValue;
                        try
                        {
                            dictValue = (V)Convert.ChangeType(formParam.Value, typeof(V));
                            values.Add(idx, dictValue);
                        }
                        catch (NotSupportedException)
                        {
                            throw new Exception("The given value '" + formParam.Value + "' could not be converted to type V");
                        }
                    }
                }
            }

            if (!keys.Keys.SequenceEqual(values.Keys))
            {
                throw new Exception("Keys and values do not match.");
            }

            foreach (KeyValuePair<int, K> kvp in keys)
            {
                dict.Add(kvp.Value, values[kvp.Key]);
            }

            return dict;
        }
    }
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;
使用System.Web;
使用System.Web.Mvc;
命名空间My.ModelBinders
{
/// 
///使用中概述的基本格式绑定到通用词典
/// http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
///通过放松,指数可以是任意整数(不需要从0开始,也不需要是连续的)。
///返回一个空字典,其中没有在连接上找到匹配的参数,而不是抛出
///与当前默认活页夹相同的异常。
/// 
///键类型
///值类型
公共类可选字典绑定器:CustomBinderBase、IModelBinder
{
/// 
///从请求中拉出键/值对。修改自
/// https://github.com/loune/MVCStuff/blob/master/Extensions/DefaultDictionaryBinder.cs
///当前不支持的文件。
/// 
/// 
/// 
私有IEnumerable GetValueProviderData(ControllerContext上下文)
{
foreach(context.HttpContext.Request.Form.Keys中的var fk)
{
返回新的KeyValuePair((string)fk,context.HttpContext.Request.Form[(string)fk]);
}
foreach(context.RouteData.Values中的var rd)
{
返回新的KeyValuePair(rd.Key,(string)rd.Value);
}
foreach(context.HttpContext.Request.QueryString中的var qp)
{
返回新的KeyValuePair((字符串)qp,context.HttpContext.R
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace My.ModelBinders
{
    /// <summary>
    /// Binds to a generic Dictionary using the basic format outlined at 
    /// http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
    /// with the relaxation that the indices can be arbitrary integers (need not start at 0 or be sequential).
    /// Returns an empty dictionary of no matching parameters found on the wire rather than throwing
    /// an Exception as the current default binder does.
    /// </summary>
    /// <typeparam name="K">Key type</typeparam>
    /// <typeparam name="V">Value type</typeparam>
    public class OptionalDictionaryBinder<K,V> : CustomBinderBase, IModelBinder
    {
        /// <summary>
        /// Pull key/value pairs out of request.  Modified from
        /// https://github.com/loune/MVCStuff/blob/master/Extensions/DefaultDictionaryBinder.cs
        /// Files not currently supported.
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        private IEnumerable<KeyValuePair<string, string>> GetValueProviderData(ControllerContext context)
        {
            foreach (var fk in context.HttpContext.Request.Form.Keys)
            {
                yield return new KeyValuePair<string, string>((string)fk, context.HttpContext.Request.Form[(string)fk]);
            }

            foreach (var rd in context.RouteData.Values)
            {
                yield return new KeyValuePair<string, string>(rd.Key, (string)rd.Value);
            }

            foreach (var qp in context.HttpContext.Request.QueryString)
            {
                yield return new KeyValuePair<string, string>((string)qp, context.HttpContext.Request.QueryString[(string)qp]);
            }
        }

        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (controllerContext == null) 
            {
                throw new ArgumentNullException("controllerContext");
            }
            if (bindingContext == null)
            {
                throw new ArgumentNullException("bindingContext");
            }

            Dictionary<K, V> dict = new Dictionary<K, V>();
            Dictionary<int, K> keys = new Dictionary<int, K>();
            Dictionary<int, V> values = new Dictionary<int, V>();

            foreach (KeyValuePair<string,string> formParam in GetValueProviderData(controllerContext))
            {
                if (formParam.Key.StartsWith(bindingContext.ModelName, StringComparison.InvariantCultureIgnoreCase))
                {
                    int startbracket = formParam.Key.IndexOf("[");
                    if (startbracket != bindingContext.ModelName.Length) throw new Exception("Did not find [ directly after model name");

                    int endbracket = formParam.Key.IndexOf("]", bindingContext.ModelName.Length + 1);
                    if (endbracket == -1) throw new Exception("Did not find closing bracket in " + formParam);

                    int idx;
                    string idxText = formParam.Key.Substring(bindingContext.ModelName.Length + 1, endbracket - bindingContext.ModelName.Length - 1);
                    if (!int.TryParse(idxText, out idx))
                    {
                        throw new Exception("Could not parse numeric index from " + formParam);
                    }

                    if (formParam.Key.EndsWith(".Key", StringComparison.InvariantCultureIgnoreCase))
                    {
                        if (keys.ContainsKey(idx))
                        {
                            throw new Exception("The index " + idx + " was repeated.");
                        }

                        K dictKey;
                        try
                        {
                            dictKey = (K)Convert.ChangeType(formParam.Value, typeof(K));
                            keys.Add(idx, dictKey);
                        }
                        catch (NotSupportedException)
                        {
                            throw new Exception("The given key '" + formParam.Key + "' could not be converted to type K");
                        }
                    }
                    else if (formParam.Key.EndsWith(".Value", StringComparison.InvariantCultureIgnoreCase))
                    {
                        if (values.ContainsKey(idx))
                        {
                            throw new Exception("The index " + idx + " was repeated.");
                        }

                        V dictValue;
                        try
                        {
                            dictValue = (V)Convert.ChangeType(formParam.Value, typeof(V));
                            values.Add(idx, dictValue);
                        }
                        catch (NotSupportedException)
                        {
                            throw new Exception("The given value '" + formParam.Value + "' could not be converted to type V");
                        }
                    }
                }
            }

            if (!keys.Keys.SequenceEqual(values.Keys))
            {
                throw new Exception("Keys and values do not match.");
            }

            foreach (KeyValuePair<int, K> kvp in keys)
            {
                dict.Add(kvp.Value, values[kvp.Key]);
            }

            return dict;
        }
    }
}
public ActionResult _UpdateFilter(int a, string b, List<string> c, 
         [ModelBinder(typeof(OptionalDictionaryBinder<int, bool>))] 
         Dictionary<int, bool> sometimesSet)