Seems like a good candidate for a custom model binder:
public class FilterViewModel
{
public string Key { get; set; }
public string Value { get; set; }
}
public class FilterViewModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var filterParamRegex = new Regex(bindingContext.ModelName + @"\((?<key>.+)\)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
return
(from key in controllerContext.HttpContext.Request.Params.AllKeys
let match = filterParamRegex.Match(key)
where match.Success
select new FilterViewModel
{
Key = match.Groups["key"].Value,
Value = controllerContext.HttpContext.Request[key]
}).ToArray();
}
}
which will be registered in Application_Start:
ModelBinders.Binders.Add(typeof(FilterViewModel[]), new FilterViewModelBinder());
and then:
public ActionResult GetData(FilterViewModel[] filter, int page, int pageSize)
{
...
}
The benefit of the custom model binder is that it does exactly what it name suggests: custom model binding since your query string parameters do not follow standard conventions used by the default model binder. In addition to that your controller action is clean and simple and it doesn't need to rely on some ugly plumbing code which obviously is not the responsibility of controller actions.