将查询字符串数组参数转换为字典值

时间:2013-02-01 18:06:57

标签: c# asp.net-web-api

我正在尝试接受表单

的查询字符串
?param[key1]=value1&param[key2]=value2

或者

?where[column1]=value1&where[column2]=value1
&orderby[column1]=asc&orderby[column2]=desc

并将其转换为C#MVC 4 Web API中的Dictionary,以便可以param["key1"]访问它。这在PHP中是微不足道的,但我无法找到任何在C#中复制它的方法。

我知道我可以通过

获取阵列
?param=value1&param=value2

这样我就可以做一些像

这样的事情
?where=column1&where=column2
&whereval=value1&whereval=value2
&orderby=column1&orderby=column2
&orderbycard=asc&orderbycard=desc

但这会留下排序问题,对我的目的来说几乎没用。

希望这不是框架的限制,我如何在C#中实现字典式转换,最好是我可以在函数的参数中使用字典?

  

澄清一下:我不打算将查询字符串转换为NVC,而是将查询字符串参数转换为自己的NVC。这是 NOT 和ParseQueryString()或Request.QueryString对象一样简单。

This question上次因this question的副本而被关闭,所以我添加了澄清说明。我要求任何考虑将其标记为副本的人在考虑之前考虑我在这里要求的内容,并认真审视我的示例。

3 个答案:

答案 0 :(得分:3)

没有内置的方法来实现这一结果。下面是我尝试使用带有命名组的正则表达式。可以跳过拆分并处理正则表达式本身中的?&字符,但我采用这种方法来保持模式更清晰。

string input = "?where[column]=value&where[column2]=value2&orderby[column]=asc&orderby[column2]=desc";
string pattern = @"(?<Type>[^[]+)\[(?<Key>[^]]+)\]=(?<Value>.+)";
var re = new Regex(pattern);
var dict = input.Split(new[] { "?", "&" }, StringSplitOptions.RemoveEmptyEntries)
                .Select(s => re.Match(s))
                .GroupBy(m => m.Groups["Type"].Value)
                .ToDictionary(g => g.Key,
                    g => g.ToDictionary(x => x.Groups["Key"].Value,
                                        x => x.Groups["Value"].Value));

foreach (var t in dict.Keys)
{
    Console.WriteLine("Type: " + t);
    foreach (var k in dict[t].Keys)
    {
        Console.WriteLine("{0}: {1}", k, dict[t][k]);
    }
}

当然,这取决于您事先知道要检查的密钥,例如dict["where"]["column"]

您可能会发现ContainsKeyTryGetValue方法有助于检查是否存在密钥:

string result;
if (dict["where"].TryGetValue("column", out result))
{
    Console.WriteLine(result);
}

答案 1 :(得分:0)

你可以尝试这样的事情

var queryString = "http://example.com/api/objects?where[id]=4&orderby[id]=asc?";
string[] arr = queryString.Split(new char[] { '&' });
int cntVar = 0; // start key
Dictionary<int, string> dictQuery;
dictQuery = arr.ToDictionary(d => ++cntVar);

答案 2 :(得分:0)

我想根据Ahmad Mageed的作品与大家分享我的想法。 (顺便说一下,这是一个很好的开始。)我需要相同的功能,但我需要它能够在索引链中接受更多的深度。

这使您能够根据需要读取尽可能多的子索引,而无需将其硬编码为2或3级深度停止。因此,通过此更新,您可以阅读此类查询字符串...

columns[0][data]=0&columns[0][name]=&columns[0][searchable]=true  
&columns[0][orderable]=true&columns[0][search][value]=  
&columns[0][search][regex]=false&columns[1][data]=1  
&columns[1][name]=&columns[1][searchable]=true  
&columns[1][orderable]=true&columns[1][search][value]=  
&columns[1][search][regex]=false&columns[2][data]=2  
...  
&order[0][column]=0&order[0][dir]=asc&start=0&length=10&search[value]=&search[regex]=false

以下是该部分代码的更新版本。

public IDictionary ConvertQueryString(string sQry)
{
    string pattern = @"(?<Prop>[^[]+)(?:\[(?<Key>[^]]+)\])*=(?<Value>.*)";
    // The brackets seem to be encoded as %5B and %5D
    var qry = HttpUtility.UrlDecode(sQry);

    var re = new Regex(pattern);
    var dict = qry.Split(new[] { "?", "&" }, StringSplitOptions.RemoveEmptyEntries)
               .Select(s => re.Match(s)).Where(g => g.Success)
               .GroupBy(m => m.Groups["Prop"].Value)
               .ToDictionary<IGrouping<string, Match>, string, object>(
                    g => g.Key, 
                    g => GetKey(g, 0));
    return dict;
}

private object GetKey(IGrouping<string, Match> grouping, int level)
{
    var count = grouping.FirstOrDefault().Groups["Key"].Captures.Count;
    // If the level is equal to the captures, then we are at the end of the indexes
    if (count == level)
    {
        var gValue = grouping.Where(gr => gr.Success).FirstOrDefault();
        var value = gValue.Groups["Value"].Value;
        return value;
    }
    else
    {
        return grouping.Where(gr => gr.Success)
                .GroupBy(m => m.Groups["Key"].Captures[level].Value)
                .ToDictionary<IGrouping<string, Match>, string, object>(
                        a => a.Key, 
                        a => GetKey(a, level + 1));
    }
}

我想比构建嵌套字典对象更进一步,我想基于传递的字符串实际构建对象。我需要这个用于jQuery DataTables的新版本,它实际上是产生上面讨厌的查询字符串的原因。这是一个快速写,所以它有点粗糙,可能有点容易出错,因为我没有时间来硬化它。

// Call it like so.
MyClass object = new MyClass();
WalkDictionary(dict, object);
// At this point the object will be filled out for you.

public class MyClass
{
    public List<cColumns> columns { get; set; }
    public cSearch search { get; set; }
}
public class cColumns
{
    public int? data { get; set; }
    public string name { get; set; }
    public bool? searchable { get; set; }
    public bool? orderable { get; set; }
    public cSearch search { get; set; }
}
public class cSearch
{
    public string value { get; set; }
    public bool? regex { get; set; }
}

我只提出了我需要的基本类型。因此,如果您需要/想要更多,那么您将不得不添加它们。

private void WalkDictionary(IDictionary dict, object obj)
{
    foreach (string key in dict.Keys)
    {
        Type t = obj.GetType();
        if (t.IsGenericType
            && typeof(List<>) == t.GetGenericTypeDefinition())
        {
            Type[] typeParameters = t.GetGenericArguments();
            foreach (DictionaryEntry item in dict)
            {
                var lstPropObj = Activator.CreateInstance(typeParameters[0]);
                WalkDictionary((IDictionary)item.Value, lstPropObj);
                ((IList)obj).Add(lstPropObj);
            }
            return;
        }
        PropertyInfo prop = obj.GetType().GetProperty(key);
        if (prop == null)
            continue;
        if (dict[key] is IDictionary)
        {
            //Walk
            var objProp = prop.GetValue(obj);
            if (objProp == null)
                objProp = Activator.CreateInstance(prop.PropertyType);
            WalkDictionary(dict[key] as IDictionary, objProp);
            prop.SetValue(obj, objProp);
        }
        else if (prop.PropertyType.IsAssignableFrom(typeof(int)))
        {
            int val = Convert.ToInt32(dict[key]);
            prop.SetValue(obj, val);
        }
        else if (prop.PropertyType.IsAssignableFrom(typeof(bool)))
        {
            bool val = Convert.ToBoolean(dict[key]);
            prop.SetValue(obj, val);
        }
        else if (prop.PropertyType.IsAssignableFrom(typeof(string)))
        {
            string val = Convert.ToString(dict[key]);
            prop.SetValue(obj, val);
        }
    }
}