Подтвердить что ты не робот

Словарь POST json

Я пытаюсь сделать следующее: Модель со встроенным словарем отправляет ее по первому запросу ajax, а затем возвращает результат и снова передает его в контроллер.

Это должно проверить, что я могу вернуть словарь в моей модели. Это не работает.

Здесь мой простой тест:

public class HomeController : Controller
{
    public ActionResult Index (T a)
    {
      return View();
    }

    public JsonResult A(T t)
    {
      if (t.Name.IsEmpty())
      {
        t = new T();
        t.Name = "myname";
        t.D = new Dictionary<string, string>();
        t.D.Add("a", "a");
        t.D.Add("b", "b");
        t.D.Add("c", "c");
      }
      return Json(t);
    }
}

//model
public class T
{
  public string Name { get; set; }
  public IDictionary<string,string> D { get; set; }
}

javascript:

$(function () {
    var o = {
        Name: 'somename',
        "D": {
            "a": "b",
            "b": "c",
            "c": "d"
        }
    };

    $.ajax({
        url: actionUrl('/home/a'),
        contentType: 'application/json',
        type: 'POST',
        success: function (result) {
            $.ajax({
                url: actionUrl('/home/a'),
                data: JSON.stringify(result),
                contentType: 'application/json',
                type: 'POST',
                success: function (result) {
                }
            });
        }
    });
});

В firebug, полученный json, и отправленные json идентичны. Я могу только предположить, что на пути что-то потеряется.

У кого-нибудь есть идея, что я делаю неправильно?

4b9b3361

Ответ 1

В связи с тем, что JsonValueProviderFactory реализовано, словарные словари привязки не поддерживаются.

Ответ 2

Несчастливое обходное решение:

data.dictionary = {
    'A': 'a',
    'B': 'b'
};

data.dictionary = JSON.stringify(data.dictionary);

. . .

postJson('/mvcDictionaryTest', data, function(r) {
    debugger;
}, function(a,b,c) {
    debugger;
});

postJSON js lib function (использует jQuery):

function postJson(url, data, success, error) {
    $.ajax({
        url: url,
        data: JSON.stringify(data),
        type: 'POST',
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        success: success,
        error: error
    });
}

Объект ViewModel размещается (предположительно, имеет гораздо больше продолжения, чем словарь):

public class TestViewModel
{
    . . .
    //public Dictionary<string, string> dictionary { get; set; }
    public string dictionary { get; set; }
    . . .
}

Метод контроллера, отправляемый в:

[HttpPost]
public ActionResult Index(TestViewModel model)
{
    var ser = new System.Web.Script.Serialization.JavascriptSerializer();
    Dictionary<string, string> dictionary = ser.Deserialize<Dictionary<string, string>>(model.dictionary);

    // Do something with the dictionary
}

Ответ 3

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

    public class DictionaryModelBinder : IModelBinder
    {
        /// <summary>
        /// Binds the model to a value by using the specified controller context and binding context.
        /// </summary>
        /// <returns>
        /// The bound value.
        /// </returns>
        /// <param name="controllerContext">The controller context.</param><param name="bindingContext">The binding context.</param>
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
                throw new ArgumentNullException("bindingContext");

            string modelName = bindingContext.ModelName;
            // Create a dictionary to hold the results
            IDictionary<string, string> result = new Dictionary<string, string>();

            // The ValueProvider property is of type IValueProvider, but it typically holds an object of type ValueProviderCollect
            // which is a collection of all the registered value providers.
            var providers = bindingContext.ValueProvider as ValueProviderCollection;
            if (providers != null)
            {
                // The DictionaryValueProvider is the once which contains the json values; unfortunately the ChildActionValueProvider and
                // RouteDataValueProvider extend DictionaryValueProvider too, so we have to get the provider which contains the 
                // modelName as a key. 
                var dictionaryValueProvider = providers
                    .OfType<DictionaryValueProvider<object>>()
                    .FirstOrDefault(vp => vp.ContainsPrefix(modelName));
                if (dictionaryValueProvider != null)
                {
                    // There no public property for getting the collection of keys in a value provider. There is however
                    // a private field we can access with a bit of reflection.
                    var prefixsFieldInfo = dictionaryValueProvider.GetType().GetField("_prefixes",
                                                                                      BindingFlags.Instance |
                                                                                      BindingFlags.NonPublic);
                    if (prefixsFieldInfo != null)
                    {
                        var prefixes = prefixsFieldInfo.GetValue(dictionaryValueProvider) as HashSet<string>;
                        if (prefixes != null)
                        {
                            // Find all the keys which start with the model name. If the model name is model.DictionaryProperty; 
                            // the keys we're looking for are model.DictionaryProperty.KeyName.
                            var keys = prefixes.Where(p => p.StartsWith(modelName + "."));
                            foreach (var key in keys)
                            {
                                // With each key, we can extract the value from the value provider. When adding to the dictionary we want to strip
                                // out the modelName prefix. (+1 for the extra '.')
                                result.Add(key.Substring(modelName.Length + 1), bindingContext.ValueProvider.GetValue(key).AttemptedValue);
                            }
                            return result;
                        }
                    }
                }
            }
            return null;
        }
    }

Связывание зарегистрировано в файле Global.asax в приложении application_start

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);

        ModelBinders.Binders.Add(typeof(Dictionary<string,string>), new DictionaryModelBinder());
    }

Ответ 4

Я получил его для работы с настраиваемым связующим устройством и изменением способа отправки данных; без использования Stringify и установки типа контента.

JavaScript:

    $(function() {
        $.ajax({
            url: '/home/a',
            type: 'POST',
            success: function(result) {
                $.ajax({
                    url: '/home/a',
                    data: result,
                    type: 'POST',
                    success: function(result) {

                    }
                });
            }
        });
    });

Пользовательское связующее устройство:

public class DictionaryModelBinder : IModelBinder
{          
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException("bindingContext");

        string modelName = bindingContext.ModelName;
        IDictionary<string, string> formDictionary = new Dictionary<string, string>();

        Regex dictionaryRegex = new Regex(modelName + @"\[(?<key>.+?)\]", RegexOptions.CultureInvariant);
        foreach (var key in controllerContext.HttpContext.Request.Form.AllKeys.Where(k => k.StartsWith(modelName + "[")))
        {
            Match m = dictionaryRegex.Match(key);
            if (m.Success)
            {
                formDictionary[m.Groups["key"].Value] = controllerContext.HttpContext.Request.Form[key];
            }
        }
        return formDictionary;
    }
}

И добавив привязку модели в Global.asax:

ModelBinders.Binders[typeof(IDictionary<string, string>)] = new DictionaryModelBinder();

Ответ 5

Возьмите пакет NuGet для System.Json, который включает новый тип JsonValue. JsonValue - это гибкий новый тип JSON, который полностью поддерживает динамику С# 4, а также IEnumerable<KeyValuePair<string, JsonValue>>, если вы хотите обрабатывать полезную нагрузку в качестве словаря/ассоциативного массива.

Вы можете выбрать System.Json(бета) с NuGet здесь. Кажется, что System.Json будет включено изначально в .NET 4.5, как показано на странице здесь.

Вы также можете прочитать следующую статью, чтобы помочь правильному десериализации объектов JSON HTTP в объектах JsonValue в параметрах метода Action:

JSON, ASP.NET MVC и JQuery - Работа с Untyped JSON упрощена

Два соответствующих фрагмента кода из вышеприведенной статьи - это DynamicJsonBinder и DynamicJsonAttribute, реплицированные здесь для потомков:

public class DynamicJsonBinder : IModelBinder  
{  
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)  
    { 
        if (!controllerContext.HttpContext.Request.ContentType.StartsWith  
              ("application/json", StringComparison.OrdinalIgnoreCase))  
        {  
            // not JSON request  
            return null;  
        }  

        var inpStream = controllerContext.HttpContext.Request.InputStream;  
        inpStream.Seek(0, SeekOrigin.Begin);  

        StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);  
        string bodyText = reader.ReadToEnd();  
        reader.Close();  


        if (String.IsNullOrEmpty(bodyText))  
        {  
            // no JSON data  
            return null;  
        }  

        return JsonValue.Parse(bodyText);  
    }  
} 

public class DynamicJsonAttribute : CustomModelBinderAttribute
{
    public override IModelBinder GetBinder()
    {
        return new DynamicJsonBinder();
    }
}

Соответствующим примером использования примера будет:

public class HomeController : Controller
{
    public ActionResult Index (T a)
    {
      return View();
    }

    public JsonResult A([DynamicJson] JsonValue value)
    {
      dynamic t = value.AsDynamic();

      if (t.Name.IsEmpty())
      {
        t = new // t is dynamic, so I figure just create the structure you need directly
        {
            Name = "myname",
            D = new // Associative array notation (woot!): 
            {
                a = "a",
                b = "b",
                c = "c" 
            }
        };
      }

      return Json(t);
    }
}

Ответ 6

Просто используйте лучший десериализатор. Эта первая строка, где я устанавливаю позицию, заключается в том, что JsonValueProvider оставляет поток в конце. Больше MS JSON не работает.

Request.InputStream.Position = 0;
var reader = new StreamReader(Request.InputStream);

var model = Newtonsoft.Json.JsonConvert.DeserializeObject<CreativeUploadModel>(reader.ReadToEnd());

Итак, где-то в этом объектном графе CreativeUploadModel есть такая пропозиция:

public Dictionary<string, Asset> Assets { get; set; }

который десериализуется из (например):

"assets":{"flash":{"type":"flash","value":"http://1234.cloudfront.net/1234.swf","properties":"{\"clickTag\":\"clickTAG\"}"}

Newtonsoft JSON является поставщиком JSON по умолчанию для WebAPI... поэтому он никуда не уйдет.

Ответ 7

Вот мое решение аналогичной проблемы:

using System.Collections.Generic;
using System.IO;
using System.Web.Mvc;
using System.Web.Script.Serialization;

namespace Controllers
{
    public class DictionaryModelBinder : IModelBinder
    {
        public object BindModel(ControllerContext context, ModelBindingContext bindingContext)
        {
            context.HttpContext.Request.InputStream.Seek(0, SeekOrigin.Begin);
            using (TextReader reader = new StreamReader(context.HttpContext.Request.InputStream))
            {
                string requestContent = reader.ReadToEnd();
                var arguments = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(requestContent);
                return arguments[bindingContext.ModelName];
            }
        }
    }
}

using Controllers;
using Moq;
using NUnit.Framework;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;
using System.Web.Mvc;

namespace ControllersTest
{
    [TestFixture]
    public class DictionaryModelBinderTest
    {
        private ControllerContext controllerContext;

        [Test]
        public void ReturnsDeserializedPrimitiveObjectsAndDictionaries()
        {
            string input =
@"{
    arguments: {
        simple: 1,
        complex: { a: 2, b: 3 },
        arrayOfSimple: [{ a: 4, b: 5 }],
        arrayOfComplex: [{ a: 6, b: 7 }, { a: 8, b: 9 }]},
    otherArgument: 1
}";
            SetUpRequestContent(input);

            var binder = new DictionaryModelBinder();
            var bindingContext = new ModelBindingContext();
            bindingContext.ModelName = "arguments";

            var model = (Dictionary<string, object>)binder.BindModel(controllerContext, bindingContext);

            Assert.IsFalse(model.ContainsKey("otherArgument"));
            Assert.AreEqual(1, model["simple"]);
            var complex = (Dictionary<string, object>)model["complex"];
            Assert.AreEqual(2, complex["a"]);
            Assert.AreEqual(3, complex["b"]);
            var arrayOfSimple = (ArrayList)model["arrayOfSimple"];
            Assert.AreEqual(4, ((Dictionary<string, object>)arrayOfSimple[0])["a"]);
            Assert.AreEqual(5, ((Dictionary<string, object>)arrayOfSimple[0])["b"]);
            var arrayOfComplex = (ArrayList)model["arrayOfComplex"];
            var complex1 = (Dictionary<string, object>)arrayOfComplex[0];
            var complex2 = (Dictionary<string, object>)arrayOfComplex[1];
            Assert.AreEqual(6, complex1["a"]);
            Assert.AreEqual(7, complex1["b"]);
            Assert.AreEqual(8, complex2["a"]);
            Assert.AreEqual(9, complex2["b"]);
        }

        private void SetUpRequestContent(string input)
        {
            var stream = new MemoryStream(Encoding.UTF8.GetBytes(input));
            stream.Seek(0, SeekOrigin.End);

            var controllerContextStub = new Mock<ControllerContext>();
            var httpContext = new Mock<HttpContextBase>();
            httpContext.Setup(x => x.Request.InputStream).Returns(stream);
            controllerContextStub.Setup(x => x.HttpContext).Returns(httpContext.Object);
            this.controllerContext = controllerContextStub.Object;
        }
    }
}

using Controllers;
using PortalApi.App_Start;
using System.Collections.Generic;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;

namespace PortalApi
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            ModelBinders.Binders.Add(typeof(Dictionary<string, object>), new DictionaryModelBinder());
        }
    }
}

Удачи!:-П Приветствую Łukasz Duda

Ответ 8

Поместите сложный объект в виде строки и десериализуйте на другом конце. Однако для этого нет безопасности типов. Вот словарь со строковыми и строковыми значениями.

JS:

var data = { 'dictionary': JSON.stringify({'A': ['a', 'b'] }) };

$.ajax({
    url: '/Controller/MyAction',
    data: JSON.stringify(data),
    type: 'POST',
    contentType: 'application/json',
    dataType: 'json'
});

Контроллер С#:

[HttpPost]
public ActionResult MyAction(string dictionary)
{
    var s = new System.Web.Script.Serialization.JavaScriptSerializer();
    Dictionary<string, string[]> d = s.Deserialize<Dictionary<string, string[]>>(dictionary);
    return View();
}

Ответ 9

Для тех, кто недавно пришел к этой проблеме, пока вам не нужен ваш контроллер, чтобы специально принять словарь, вы можете сделать следующее:

HttpResponseMessage SomeMethod([FromBody] IEnumerable<KeyValuePair<Key, Value>> values)
{
    Dictionary<Key, Value> dictionary = values.ToDictionary(x => x.Key, x = x.Value);
}

Хотя это немного хаки.

Ответ 10

Используя ASP.NET 5 и MVC 6 прямо из коробки, я делаю это:

JSon:

{
    "Name": "somename",
    "D": {
        "a": "b",
        "b": "c",
        "c": "d"
    }
}

Контроллер:

[HttpPost]
public void Post([FromBody]Dictionary<string, object> dictionary)
{
}

Это то, что появляется, когда оно появляется (имя и D - это клавиши):

введите описание изображения здесь