在使用MVC 4的新单页应用程序工具时,我注意到我找到的所有示例都没有包含通过WebApi更新的DateTime示例。我很快就找到了原因。
我首先从提供的模板生成标准SPA。然后我打开了TodoItem.cs并添加了DateTime字段。然后我按照评论的指示生成了控制器。 (没有datetime字段,一切正常)。
生成所有内容后,我启动了应用程序并导航到控制器索引(我称之为控制器“任务”)。我按预期获得了0个记录的网格页面并单击了添加按钮。我按预期被带到了编辑页面并输入了一些数据,包括我闪亮的新日期时间字段中的日期。然后点击保存。
产生了一个错误:
服务器错误:HTTP状态代码:500,消息:反序列化System.Web.Http.Data.ChangeSetEntry []类型的对象时出错。根据JSON的要求,DateTime内容'01 / 01/2012'不以'/ Date('和以')/'开头。
工具似乎还不支持DateTime。我相信我可以花一些时间来搞清楚并让它发挥作用,但我想我可能会在这里找到一些运气,因为有人已经解决了这个问题,并且可以提供见解。
任何人都在与此作斗争?
更新:我提出了自问这个以来我发现的更多信息。我尝试使用JSON.Net作为我的格式化程序,如下所示。我认为这将是最终的解决方案,但是,正如下面推荐的海报还不够。
使用JSON.Net序列化程序时,出现以下错误:
此DataController不支持实体'JObject'的操作'Update'。
原因是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对象,并在Entity和OriginalEntity字段中嵌入TodoItem对象。
有没有人让JSON.Net正确反序列化?
答案 0 :(得分:3)
问题在于,在当前的测试版中,ASP.NET Web API使用DataContractJsonSerializer
,其中DateTime
的序列化存在众所周知的问题。 Here is最近在Microsoft Connect上出现了一个安静的问题: MS回应说他们已经有一个跟踪问题的bug,但不会在.Net 4.5 / VS11时间框架内修复。
幸运的是,您可以替换其他JSON序列化程序,例如James Newton-King's优秀JSON.Net。
ASP.NET团队的Henrik Nielsen有一篇excellent blog帖子,展示了如何将JSON.Net与ASP.NET Web API结合使用。以下是他使用JSON.Net的MediaTypeFormatter
的实现(它还需要连接到ASP.NET Web API配置,Henrik的博客也证明了这一点。)
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 :(得分: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/
您还想更改_Editor.cshtml中的html以绑定到“DateJson”,而不是“EnterDate”
这当然是一种kludge,但它具有工作的优点,这不是一件小事。
答案 2 :(得分:1)
您还可以通过添加以下代码来使JQuery日历弹出窗口正常工作。
在示例MVC 4 SPA项目的TodoItemsViewModel.js底部添加:
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”。
答案 3 :(得分: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;
}
}
}
并使用它来使用````使用(MyJsonTextReader jsonTextReader = new MyJsonTextReader(streamReader))反序列化Json