2012-02-27 4 views
8

Во время игры с новой инструментами приложений с одной страницей MVC 4 я заметил, что ни один из примеров, которые я нашел, не содержит пример обновления DateTime через WebApi. Вскоре я узнал, почему.MVC 4 Single Page Application and DateTime

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

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

была произведена ошибка, которая говорит:

Server error: HTTP status code: 500, message: There was an error deserializing the object of type System.Web.Http.Data.ChangeSetEntry[]. DateTime content '01/01/2012' does not start with '/Date(' and end with ')/' as required for JSON.

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

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

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

При использовании сериалайзера Json.NET, я получаю следующее сообщение об ошибке:

This DataController does not support operation 'Update' for entity 'JObject'.

Причина заключается в том, что JSON.Net не полностью заполнить объект, который форматировщик пытается deserailize к (системе. 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 в объект с Embeded набор изменений объектов TodoItem в Сущности и OriginalEntity полей.

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

+0

Показать код, позволяющий воспроизвести вашу проблему, действительно будет полезен. –

+0

К сожалению, мне нужно будет предоставить все решение. Приведенный шаблон приложения одной страницы генерирует много кода. Чтобы воспроизвести ошибку, просто создайте новое приложение с одной страницей MVC 4. Как только инструмент закончит, откройте TodoItem.cs и добавьте поле datetime. Затем щелкните правой кнопкой мыши «Контроллеры» и добавьте контроллер. Выберите имя для него, выберите TodoItem в качестве модели, шаблон SPA и создайте новый контекст. Как только это будет сделано, запустите приложение и перейдите к новому контроллеру. Нажмите «Добавить» и введите данные, затем нажмите «Сохранить». –

ответ

3

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

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

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

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(); 
       } 
     }); 
    } 
}  
+0

Я думаю, что это может быть близко к правильному ответу, но, похоже, он не работает со сценарием, изложенным выше. После использования этого кода и подключения его к webapi при попытке сохранить следующую ошибку: Ошибка сервера: код состояния HTTP: 500, сообщение: этот DataController не поддерживает операцию «Обновить» для объекта «JObject». Это может быть проблемой с upshot.js, но я не смог выяснить, откуда эта ошибка. –

+0

Мне пришлось в значительной степени отказаться от всего этого пути. Json.Net, казалось, был ответом, но, чтобы получить уважение к формату Json, который был взят upshot.js в примере с примером одной страницы, был основной головной болью, которую я просто не мог нормально работать. В конце дня я могу сделать это быстрее в MVC3 и ждать MVC4, надеюсь, добавить поддержку этих вещей. Я собираюсь отметить это как ответ, так как плакат продемонстрировал это как ошибку. –

+0

Спасибо, что приняли ответ; возможно, стоит обратиться к JSON.Net и поднять ошибку, чтобы проверить, можно ли установить JSON.Net (или отправить патч!) –

0

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; 
     } 
    } 
} 

и использовать его для deserialise в JSon с` `` `` используя (MyJsonTextReader jsonTextReader = новый MyJsonTextReader (StreamReader))

1

у меня был то же самое. Я потратил слишком много времени, пытаясь заставить 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»

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

1

Вы также можете запустить всплывающее окно календаря 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 будет выглядеть связываться с DatePicker

<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».

Смежные вопросы