Использование Json.NET для привязки модели JSON


У меня есть метод, который публикуется через AJAX со следующим заголовком:

public JsonResult GetDocuments(string searchTerm, SortRequest sort)

Объект SortRequest определяется следующим образом:

[DataContract]
public class SortRequest
{
    [DataMember(Name = "field")]
    public string Field { get; set; }

    [DataMember(Name = "dir")]
    public string Direction { get; set; }
}

Из-за устаревшего кода объект JSON имеет имя свойства "dir", которое напрямую не совпадает с именем свойства C#. Мы хотим использовать Json.NET как связыватель модели для запросов JSON, потому что он может обрабатывать это, но проблема в том, что JSON, входящий в связыватель модели, выглядит как один объект с двумя свойствами верхнего уровня, "searchTerm" и "сортировка". Затем процесс десериализации пытается отобразить всю строку JSON в каждый параметр метода, что, очевидно, не удается.

Я попытался просмотреть теперь открытый исходный код .NET MVC и до сих пор не смог определить, как класс DefaultModelBinder обрабатывает это изящно. Единственный вариант, который я вижу до сих пор, - это преобразовать каждое действие JSON в один параметр запроса, но это не кажется хорошим решением, так как DefaultModelBinder этого не делает требовать этого.

Изменить для уточнения:

Строка запроса JSON выглядит примерно так:

{
    "searchTerm": "test",
    "sort": {
        "field": "name",
        "dir": "asc"
    }
}

Мы переопределяем DefaultModelBinder и используем только Json.NET если запрос имеет тип application/json. Вот соответствующий код:

var request = controllerContext.HttpContext.Request;

request.InputStream.Seek(0, SeekOrigin.Begin);

using (var reader = new StreamReader(request.InputStream))
{
    var jsonString = reader.ReadToEnd();

    result = JsonConvert.DeserializeObject(jsonString, bindingContext.ModelType);
}

BindingContext.ModelType будет установлен в String и SortRequest для каждого параметра в методе, но так как выше указан один объект JSON, он не сопоставляется ни с одним из этих типов и, таким образом, внутри сам метод, все установлено на значения по умолчанию.

2 2

2 ответа:

Я думаю, что атрибут JsonProperty может быть использован для этого следующим образом:

[DataContract]
public class SortRequest
{
    [DataMember(Name = "field")]
    [JsonProperty("field")]
    public string Field { get; set; }

    [DataMember(Name = "dir")]
    [JsonProperty("dir")]
    public string Direction { get; set; }
}

Обновить

На основе json добавьте префикс привязки:

public JsonResult GetDocuments(string searchTerm, [Bind(Prefix="sort"] SortRequest sort)

В итоге я решил использовать решение с помощью JToken.Метод Parse в Json.NET библиотека. По существу, происходит то, что мы проверяем свойства верхнего уровня объекта JSON и видим, существует ли текущий параметр действия, к которому мы пытаемся привязаться. Это происходит, если имеется перекрытие между именем параметра действия и именем свойства одного передаваемого запроса. Я думаю, что это достаточно крайний случай, чтобы позволить скользить, поскольку это потребует только один объект будет передан в действие, которое ожидает несколько.

Вот модифицированный метод BindModel:

public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    object result;

    if (IsJSONRequest(controllerContext))
    {
        var request = controllerContext.HttpContext.Request;

        request.InputStream.Seek(0, SeekOrigin.Begin);

        using (var reader = new StreamReader(request.InputStream))
        {
            var jsonString = reader.ReadToEnd();

            // Only parse non-empty requests.
            if (!String.IsNullOrWhiteSpace(jsonString))
            {
                // Parse the JSON into a generic key/value pair object.
                var obj = JToken.Parse(jsonString);

                // If the string parsed and there is a top level property of the same
                // name as the parameter name we are looking for, use that property
                // as the JSON object to de-serialize.
                if (obj != null && obj.HasValues && obj[bindingContext.ModelName] != null)
                {
                    jsonString = obj[bindingContext.ModelName].ToString();
                }
            }

            result = JsonConvert.DeserializeObject(jsonString, bindingContext.ModelType);
        }
    }
    else
    {
        result = base.BindModel(controllerContext, bindingContext);
    }

    return result;
}