Marius B
Marius B

Reputation: 788

How can I iterate through different types of dictionaries using one generic method?

I have a HttpContext context and I iterate through some of its Request properties to collect info. Currently I do it this way:

if (context.Request.Headers?.Keys != null)
{
    var items = new StringBuilder();
    foreach (var key in context.Request.Headers.Keys)
    {
        items.AppendLine(key + " = " + context.Request.Headers[key]);
    }
    result.Headers = items.ToString();
}
if (context.Request.Form?.Keys != null)
{
    var items = new StringBuilder();
    foreach (var key in context.Request.Form.Keys)
    {
         items.AppendLine(key + " = " + context.Request.Form[key]);
    }
    result.Form = items.ToString();
}
if (context.Request.Query?.Keys != null)
{
    var items = new StringBuilder();
    foreach (var key in context.Request.Query.Keys)
    {
        items.AppendLine(key + " = " + context.Request.Query[key]);
    }
    result.Query = items.ToString();
}

I want to convert this repetitive code to a generic method (if you can suggest any other ways, I would be fine with them, too). I tried writing a generic method:

private static string ParseKeys<T>(IDictionary<object, object> dict)
{
    var sb = new StringBuilder();
    foreach (var key in dict.Keys)
    {
        sb.AppendLine(key + " = " + dict[key]);
    }
    return sb.ToString();
}

and calling it like this:

result.Headers = ParseKeys<IHeaderDictionary>(context.Request.Headers);
result.Form = ParseKeys<IFormCollection>(context.Request.Form);
result.Query = ParseKeys<IQueryCollection>(context.Request.Query);

But I get such errors: cannot convert from 'Microsoft.AspNetCore.Http.IHeaderDictionary' to 'System.Collections.Generic.IDictionary<object, object>'

I tried various combinations but I still couldn't manage to avoid errors. Am I trying to do impossible here, or is there a simple to do what I want?

Upvotes: 1

Views: 2452

Answers (2)

Sefe
Sefe

Reputation: 14007

You are using a generic method, but you are not using the generic type parameter T. As far as the IHeaderDictionary and the IFormCollection, it is enough to accept an IEnumerable<KeyValuePair<string, string[]>>, since both interfaces inherit it:

private static string ParseKeys(IEnumerable<KeyValuePair<string, string[]>> dict)
{
    var sb = new StringBuilder();
    foreach (var keyValuePair in dict)
    {
        sb.AppendLine(keyValuePair.Key + " = " + String.Join(", ", keyValuePair.Value));
    }
    return sb.ToString();
}

As far as the IQueryCollection is concerned, it is an IEnumerable<KeyValuePair<string, StringValues>>. You can easily transform this to the required type with an iterator:

private static IEnumerable<KeyValuePair<string, string[]>> Transform(IEnumerable<KeyValuePair<string, StringValues>> source) {
    foreach(var item in source) {
        yield return new KeyValuePair<string, string[]>(item.Key, item.Value.ToArray());
    }
}

Eventually, you can call the method like:

result.Headers = ParseKeys(context.Request.Headers);
result.Form = ParseKeys(context.Request.Form);
result.Query = ParseKeys(Transform(context.Request.Query));

Upvotes: 0

DavidG
DavidG

Reputation: 119066

Those collections you mention (IHeaderDictionary, IFormCollection and IQueryCollection) all implement the same interface: IEnumerable<KeyValuePair<string, StringValues>> so here you don't need a generic method. Instead, you can do something like this:

private static string ParseKeys(IEnumerable<KeyValuePair<string, StringValues>> values)
{
    var sb = new StringBuilder();
    foreach (var value in values)
    {
        sb.AppendLine(value.Key + " = " + string.Join(", ", value.Value));
    }
    return sb.ToString();
}

And call it as you were previously:

result.Headers = ParseKeys(context.Request.Headers);
result.Form = ParseKeys(context.Request.Form);
result.Query = ParseKeys(context.Request.Query);

Upvotes: 7

Related Questions