Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Model binding to Dictionary<string,string> in Nancy

I can't bind JSON to Dictionary<string,string> in Nancy.

This route:

Get["testGet"] = _ =>
{
    var dictionary = new Dictionary<string, string>
    {
         {"hello", "world"},
         {"foo", "bar"}
    };

    return Response.AsJson(dictionary);
};

returns the following JSON, as expected:

{
    "hello": "world",
    "foo": "bar"
}

When I try and post this exact JSON back to this route:

Post["testPost"] = _ =>
{
    var data = this.Bind<Dictionary<string, string>>();
    return null;
};

I get the exception:

The value "[Hello, world]" is not of type "System.String" and cannot be used in this generic collection.

Is it possible to bind to Dictionary<string,string> using Nancys default model binding and, if so, what am I doing wrong here?

like image 697
Dave S Avatar asked May 28 '15 16:05

Dave S


1 Answers

Nancy doesn't have a built-in converter for dictionaries. Because of this you'd need to use BindTo<T>() like so

var data = this.BindTo(new Dictionary<string, string>());

which will use the CollectionConverter. The issue with doing it like this is it will only add string values, so if you send

{
    "hello": "world",
    "foo": 123
}

your result will only contain the key hello.

If you want to capture all the values as strings, even if they aren't supplied as such, then you'll need to use a custom IModelBinder.

This will convert all the values to strings and return a Dictionary<string, string>.

public class StringDictionaryBinder : IModelBinder
{
    public object Bind(NancyContext context, Type modelType, object instance, BindingConfig configuration, params string[] blackList)
    {
        var result = (instance as Dictionary<string, string>) ?? new Dictionary<string, string>();

        IDictionary<string, object> formData = (DynamicDictionary) context.Request.Form;

        foreach (var item in formData)
        {
            var itemValue = Convert.ChangeType(item.Value, typeof (string)) as string;

            result.Add(item.Key, itemValue);
        }

        return result;
    }

    public bool CanBind(Type modelType)
    {
        // http://stackoverflow.com/a/16956978/39605
        if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof (Dictionary<,>))
        {
            if (modelType.GetGenericArguments()[0] == typeof (string) &&
                modelType.GetGenericArguments()[1] == typeof (string))
            {
                return true;
            }
        }

        return false;
    }
}

Nancy will auto register this for you and you can bind your models as you normally would.

var data1 = this.Bind<Dictionary<string, string>>();
var data2 = this.BindTo(new Dictionary<string, string>());
like image 81
Brian Surowiec Avatar answered Nov 05 '22 15:11

Brian Surowiec