Одностраничное приложение MVC 4 и DateTime - PullRequest
8 голосов
/ 28 февраля 2012

Играя с новым одностраничным инструментарием MVC 4, я заметил, что ни один из найденных примеров не содержит пример обновления DateTime обратно через WebApi.Вскоре я узнал, почему.

Я начал с создания стандартного SPA из предоставленного шаблона.Затем я открыл TodoItem.cs и добавил поле DateTime.Затем я сгенерировал контроллер, как указано в комментариях.(Без поля datetime все работает просто отлично).

После того, как все сгенерировано, я запустил приложение и перешел к индексу контроллера (я назвал контроллер «задачами»).Я получил страницу сетки с 0 записями, как и ожидалось, и нажал кнопку добавления.Я был доставлен на страницу редактирования, как и ожидалось, и ввел некоторые данные, включая дату, в свое блестящее новое поле даты и времени.Затем нажмите «Сохранить».

Произошла ошибка, в которой говорилось:

Ошибка сервера: код состояния HTTP: 500, сообщение: произошла ошибка десериализации объекта типа System.Web.Http.Data.ChangeSetEntry [].Содержимое DateTime '01 / 01/2012 'не начинается с' / Date ('и заканчивается') / ', как требуется для JSON.

Может показаться, что инструмент не поддерживает DateTimeеще.Я уверен, что смогу пройти и потратить немного времени на то, чтобы разобраться с этим и заставить его работать, но я подумал, что мне может повезти с кем-то, кто уже исправил эту проблему и может дать понимание.

Кто-нибудь уже боролся с этим?

Обновление: я добавляю больше информации, которую я нашел с тех пор, как спросил об этом.Я попытался использовать JSON.Net в качестве моего Formatter, как предложено ниже.Я думаю, что это будет возможным решением, однако, просто выполнить то, что рекомендовано ниже, недостаточно.

При использовании сериализатора JSON.Net я получаю следующую ошибку:

Этот DataController не поддерживает операцию «Обновление» для объекта «JObject».

Причина в том, что JSON.Net не полностью заполняет объект, к которому форматировщик пытается десертилизировать (System.Web.Http.Data.ChangeSet).

Передаваемый файл json:

[{"Id":"0",
  "Operation":2,
  "Entity":
    {"__type":"TodoItem:#SPADateProblem.Models",
     "CreatedDate":"/Date(1325397600000-0600)/",
     "IsDone":false,
     "Title":"Blah",
     "TodoItemId":1},
  "OriginalEntity":
    {"__type":"TodoItem:#SPADateProblem.Models",
     "CreatedDate":"/Date(1325397600000-0600)/",
     "IsDone":false,
     "Title":"Blah",
     "TodoItemId":1}
}]

Встроенный Json Formatter способен преобразовать этот Json в объект ChangeSet с внедренным TodoItem.объекты в полях Entity и OriginalEntity.

Кто-нибудь получил JSON.Net для правильной десериализации?

Ответы [ 4 ]

3 голосов
/ 28 февраля 2012

Проблема заключается в том, что в текущей бета-версии ASP.NET Web API использует DataContractJsonSerializer, что имеет известные проблемы с сериализацией DateTime. Вот тихая недавно возникшая ошибка в Microsoft Connect для этой проблемы;MS отвечает, что у них уже есть ошибка отслеживания проблемы, но она не будет исправлена ​​на таймфрейме .Net 4.5 / VS11.

К счастью, вы можете заменить альтернативный сериализатор JSON, такой как Джеймс Ньютон.King's отлично JSON.Net .

Хенрик Нильсен из команды ASP.NET опубликовал отличный блог , в котором показано, как вы можете использовать JSON.Net с ASP.NET Web API.Вот его реализация MediaTypeFormatter, использующего JSON.Net (он также должен быть подключен к конфигурации ASP.NET Web API, в блоге Хенрика это тоже демонстрируется).

public class JsonNetFormatter : MediaTypeFormatter
{
    private readonly JsonSerializerSettings settings;

    public JsonNetFormatter(JsonSerializerSettings settings = null)
    {
        this.settings = settings ?? new JsonSerializerSettings();

        SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
        Encoding = new UTF8Encoding(false, true);
    }

    protected override bool CanReadType(Type type)
    {
        return type != typeof(IKeyValueModel);
    }

    protected override bool CanWriteType(Type type)
    {
        return true;
    }

    protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext)
    {
        var ser = JsonSerializer.Create(settings);

        return Task.Factory.StartNew(() => {
            using (var strdr = new StreamReader(stream))
            using (var jtr = new JsonTextReader(strdr))
            {
                var deserialized = ser.Deserialize(jtr, type);
                return deserialized;
            }
        });
    }

    protected override Task OnWriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, TransportContext transportContext)
    {
         JsonSerializer ser = JsonSerializer.Create(settings);

         return Task.Factory.StartNew(() =>
         {
              using (JsonTextWriter w = new JsonTextWriter(new StreamWriter(stream, Encoding)) { CloseOutput = false})
              {
                   ser.Serialize(w, value);
                   w.Flush();
              }
         });
    }
}    
1 голос
/ 14 апреля 2012

Вы также можете заставить работать календарь JQuery, добавив следующий код.

Добавьте это в конец файла TodoItemsViewModel.js в примере проекта MVC 4 SPA:

    ko.bindingHandlers.datepicker = {
    init: function (element, valueAccessor, allBindingsAccessor) {
        //initialize datepicker with some optional options
        var options = allBindingsAccessor().datepickerOptions || {};
        $(element).datepicker(options);

        //handle the field changing
        ko.utils.registerEventHandler(element, "change", function () {
            var observable = valueAccessor();
            observable($(element).datepicker("getDate"));
        });

        //handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            $(element).datepicker("destroy");
        });

    },
    update: function (element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
        current = $(element).datepicker("getDate");

        if (value - current !== 0) {
            //$(element).datepicker("setDate", value);
            $(element).val(value.toString());
        }
    }
}

ko.bindingHandlers.date = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        var jsonDate = "/Date(12567120000-1000)/";
        var value = new Date(parseInt(jsonDate.substr(6)));
        var ret = value.getMonth() + 1 + "/" + value.getDate() + "/" + value.getFullYear();
        element.innerHTML = ret;
    },

    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
    }
};

Это то, что ваш код _Editor.cshtml будет привязывать к датчику

    <p>
    EnterDate:
    @*<input name="EnterDate" data-bind="value: EnterDate, autovalidate: true" />
    <span class="error" data-bind="text: EnterDate.ValidationError"></span>*@
    <input name="DateJson" data-bind="datepicker: DateJson, datepickerOptions: { minDate: new Date() }" />
    <span class="error" data-bind="text: DateJson.ValidationError"></span>

</p>

Кроме того, вы хотите изменить переменную, отображаемую на странице _Grid.cshtml, с «EnterDate» на «DateJson».

1 голос
/ 14 апреля 2012

У меня была точно такая же проблема. Я потратил слишком много времени, пытаясь заставить работать json.net. Я наконец-то нашел этот обходной путь, который вы должны вставить в TodoItemsViewModel.js в примере проекта:

    self.IsDone = ko.observable(data.IsDone);
    self.EnterDate = ko.observable(data.EnterDate);
    self.DateJson = ko.computed({
        read: function () {
            if (self.EnterDate() != undefined) {
                 var DateObj = new Date(parseInt(self.EnterDate().replace("/Date(", "").replace(")/", ""), 10));    //.toDateString();
                var ret = DateObj.getMonth() + 1 + "/" + DateObj.getDate() + "/" + DateObj.getFullYear();
                return ret;
            }
            else {
                return self.EnterDate();
            }
        },
        write: function (value) {
            var formattedDate = "\/Date(" + Date.parse(value) + ")\/"
            self.EnterDate(formattedDate);
        }
     });
    upshot.addEntityProperties(self, entityType);

Окружающие строки кода были включены для контекста. Я нашел это в комментариях по адресу: http://blog.stevensanderson.com/2012/03/06/single-page-application-packages-and-samples/

Вы также хотите изменить html в _Editor.cshtml для привязки к «DateJson», а не «EnterDate»

Это, конечно, клудж, но он обладает достоинством работы, что немаловажно.

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

JSON.NET ожидает $ type, тогда как у вас есть __type для указания типа сущности, поэтому он преобразует его в JObject.

Я обошел его со следующим klunk

сначала убедитесь, что JsonSerializerSettings has .TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects;

затем напишите свой собственный `` `` JsonTextReader

public class MyJsonTextReader : JsonTextReader
{
    public MyJsonTextReader(TextReader reader)
        : base(reader)
    { }

    public override object Value
    {
        get
        {
            var o = new ActivityManager.Models.Sched_ProposedActivities();

            if (TokenType == JsonToken.PropertyName && base.Value.ToString() == "__type")
                return "$type";
            if (TokenType == JsonToken.String && Path.ToString().EndsWith(".__type"))
            {
                string s = base.Value.ToString();
                var typeName = Regex.Match(s, ":#.*").ToString().Substring(2) + "." + Regex.Match(s, "^.*:#").ToString().Replace(":#", "");

                return
                    typeName + ", ActivityManager, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
            }

            return base.Value;
        }
    }
}

и использовать его для десериализации Json с использованием `` `` с помощью (MyJsonTextReader jsonTextReader = new MyJsonTextReader (streamReader))

...