DefaultModelBinder не может десериализовать объект словаря .NET, переданный действию как объект JSON? - PullRequest
6 голосов
/ 12 августа 2011

У меня очень простой класс:

public class FilterItem
{
    public Dictionary<string, string> ItemsDictionary { get; set; }

    public FilterItem()
    {
        ItemsDictionary = new Dictionary<string, string>();
    }
}

Я хочу заполнить данные в словаре на клиенте и затем передать их моему действию контроллера как объект JSON. Однако, независимо от того, что я пробую на клиенте, DefaultModelBinder, похоже, не сможет его десериализовать.

Вот пример кода javascript для вызова моего действия:

var simpleDictionary = {"ItemsDictionary": {"1": "5", "2": "7"}};

$.ajax({ cache: false, type: "POST", data: JSON.stringify(simpleDictionary),
contentType: "application/json; charset=utf-8", 
url: "/Catalog7Spikes/GetFilteredProductsJson", success: function (data) {...});

А вот упрощенная версия моего метода действия:

[HttpPost]
public ActionResult GetFilteredProductsJson(FilterItem filterItem)
{   
    ProductsModel productsModel = new ProductsModel();
    return View("SevenSpikes.Nop.UI.Views.Products", productsModel);
}

Обратите внимание, что работает наоборот. При передаче в виде JsonResult объект FilterItem успешно сериализуется и передается как объект JSON клиенту. Однако попытка пойти другим путем не работает.

Я прочитал тикет на Connect и подумал, что обходной путь будет работать, но это не так.

Можно ли вообще десериализовать словарь .NET с помощью DefaultModelBinder в ASP.NET MVC 3?

Ответы [ 5 ]

4 голосов
/ 06 июля 2012

Гансельман говорит об этом:

Источник: http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx

DefaultModelBinder ожидает некоторый менее оптимальный синтаксис для словарей.Попробуйте использовать такой синтаксис:

 {
 "dictionary[0]":{"Key":"a", "Value":"b"},
 "dictionary[1]":{"Key":"b", "Value":"b"}
 }

Это довольно громоздко, но связывает.Следующее работает, но я лично предпочитаю выше;это короче.

 {
 "dictionary[0].Key":"a",
 "dictionary[0].Value":"b",
 "dictionary[1].Key":"b"
 "dictionary[1].Value":"b"
 }
1 голос
/ 13 августа 2011

UPDATE

Основываясь на сообщении в блоге Jeroen (см. Его ответ ниже, со ссылкой), а также на флеш-памяти, которая была у меня после повторного просмотра кода, я обновил ExtendedJsonValueProviderFactory , чтобы он всегда правильно создать BackingStore для словаря верхнего уровня, переданного через JSON.

Код доступен на GitHub на https://github.com/counsellorben/ASP.NET-MVC-JsonDictionaryBinding,, а рабочий пример на http://oss.form.vu/json-dictionary-example/.


Удалив текущий JsonValueProviderFactory и заменив тот, который может обрабатывать создание словаря, вы можете связать свой словарь. Во-первых, как указал Кейт, в вашем Javascript обязательно закройте свой словарь внутри «filterItem», так как это имя переменной модели в действии вашего контроллера, а для JSON - имя переменной в действии контроллера должен совпадать с именем возвращаемого элемента Json. Кроме того, при передаче класса все вложенные элементы должны соответствовать именам свойств в классе.

Затем создайте класс ExtendedJsonValueProviderFactory следующим образом:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Web.Script.Serialization;

public sealed class ExtendedJsonValueProviderFactory : ValueProviderFactory
{

    private void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, object value)
    {
        IDictionary<string, object> d = value as IDictionary<string, object>;
        if (d != null)
        {
            foreach (KeyValuePair<string, object> entry in d)
            {
                if (entry.Key.EndsWith("Dictionary", StringComparison.CurrentCulture))
                    CreateDictionary(backingStore, entry);
                else
                    AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);
            }
            return;
        }

        IList l = value as IList;
        if (l != null)
        {
            for (int i = 0; i < l.Count; i++)
            {
                AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]);
            }
            return;
        }

        // primitive
        backingStore[prefix] = value;
    }

    private void CreateDictionary(Dictionary<string, object> backingStore, KeyValuePair<string, object> source)
    {
        var d = source.Value as IDictionary<string, object>;
        var dictionary = new Dictionary<string, string>();
        foreach (KeyValuePair<string, object> entry in d)
            dictionary.Add(entry.Key, entry.Value.ToString());

        AddToBackingStore(backingStore, source.Key, dictionary);
        return;
    }

    private static object GetDeserializedObject(ControllerContext controllerContext)
    {
        if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
        {
            // not JSON request
            return null;
        }

        StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
        string bodyText = reader.ReadToEnd();
        if (String.IsNullOrEmpty(bodyText))
        {
            // no JSON data
            return null;
        }

        JavaScriptSerializer serializer = new JavaScriptSerializer();
        object jsonData = serializer.DeserializeObject(bodyText);
        return jsonData;
    }

    public override IValueProvider GetValueProvider(ControllerContext controllerContext)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }

        object jsonData = GetDeserializedObject(controllerContext);
        if (jsonData == null)
        {
            return null;
        }

        Dictionary<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
        AddToBackingStore(backingStore, String.Empty, jsonData);

        return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
    }

    private static string MakeArrayKey(string prefix, int index)
    {
        return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
    }

    private static string MakePropertyKey(string prefix, string propertyName)
    {
        return (String.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName;
    }
}

Вы можете заметить, что этот класс практически идентичен стандартному классу JsonValueProviderFactory, за исключением расширения для создания записи в DictionaryValueProvider типа Dictionary<string,string>. Вы также должны заметить, что для того, чтобы обрабатываться как словарь, элемент должен иметь имя, оканчивающееся на «Словарь» (и хотя я думаю, что это значительный запах кода, я не могу придумать другую альтернативу в это время ... я открыт для предложений).

Затем добавьте следующее к Application_Start в Global.asax.cs:

var j = ValueProviderFactories.Factories.FirstOrDefault(f => f.GetType().Equals(typeof(JsonValueProviderFactory)));
if (j != null)
    ValueProviderFactories.Factories.Remove(j);
ValueProviderFactories.Factories.Add(new ExtendedJsonValueProviderFactory());

Это удалит стандартную JsonValueProviderFactory и заменит ее нашим расширенным классом.

Последний шаг: наслаждайся совершенством.

0 голосов
/ 21 апреля 2012

Связыватель модели по умолчанию не может обрабатывать список. Я решил эту проблему в своем проекте с открытым исходным кодом: http://jsaction.codeplex.com и написал статью об этой проблеме: прочитайте здесь http://jsaction.codeplex.com/wikipage?title=AllFeatures&referringTitle=Documentation

... Asp.net MVC имеет встроенные возможности преобразования отправленных данных в объекты строгого типа. Но данные, которые мы отправляем, должны быть правильно подготовлены, чтобы связыватель данных по умолчанию мог их заполнить и заполнить свойства объектов параметров действия контроллера. Проблема в том, что предоставление объекта JSON для вызова функции jQuery.ajax () не работает. Совсем. Данные не привязываются к данным на сервере, поэтому параметры действий контроллера имеют значения по умолчанию, которые в любом случае, вероятно, являются недопустимыми. Проблема в том, что объект JSON был преобразован jQuery для запроса строки запроса, а значения свойств второго уровня были искажены в форму, которую не понимает механизм связывания модели по умолчанию для MVC Asp.net ...

0 голосов
/ 19 января 2012

Вчера у меня возникла точно такая же проблема, когда я пытался отправить словарь JavaScript (JSON) в метод действия контроллера. Я создал специальный механизм связывания моделей, который обрабатывает общие словари с аргументами разных типов, как напрямую (в параметре метода действия), так и содержащиеся в классе модели. Я проверял это только в MVC 3.

Подробную информацию о моем опыте и исходном коде пользовательского связывателя моделей см. В моем блоге по адресу http://buildingwebapps.blogspot.com/2012/01/passing-javascript-json-dictionary-to.html

0 голосов
/ 13 августа 2011

Вы пробовали следующее?

var simpleDictionary = {"ItemsDictionary": {"1": "5", "2": "7"}};

$.ajax({ cache: false, type: "POST", data: {filterItem : JSON.stringify(simpleDictionary)},
contentType: "application/json; charset=utf-8", 
url: "/Catalog7Spikes/GetFilteredProductsJson", success: function (data) {...});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...