【问题标题】:MVC 4 Single Page Application and DateTimeMVC 4 单页应用程序和日期时间
【发布时间】:2012-02-27 22:56:16
【问题描述】:

在使用 MVC 4 的新单页应用程序工具时,我注意到我发现的所有示例都没有包含通过 WebApi 更新回 DateTime 的示例。我很快就发现了原因。

我首先从提供的模板生成标准 SPA。然后我打开 TodoItem.cs 并添加了一个 DateTime 字段。然后我按照 cmets 的指示生成了控制器。 (没有 datetime 字段,一切正常)。

生成所有内容后,我启动了应用程序并导航到控制器索引(我将控制器称为“任务”)。我按预期得到了包含 0 条记录的网格页面,然后单击了添加按钮。我按预期被带到编辑页面,并在我闪亮的新日期时间字段中输入了一些数据,包括日期。然后点击保存。

产生了一个错误,说:

服务器错误:HTTP 状态代码:500,消息:反序列化 System.Web.Http.Data.ChangeSetEntry[] 类型的对象时出错。 DateTime 内容 '01/01/2012' 不以 JSON 要求的 '/Date(' 开头并以 ')/' 结尾。

该工具似乎还不支持 DateTime。我确信我可以通过并花一些时间来解决它并让它工作,但我想我可能会在这里找到一些已经解决了这个问题并可以提供见解的人的运气。

有人已经为此战斗了吗?

更新:我正在添加自提出此问题后发现的更多信息。我尝试使用 JSON.Net 作为我的格式化程序,如下所示。我认为这将是最终的解决方案,但是,仅按照下面推荐的海报进行操作是不够的。

使用 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 对象,并在 Entity 和 OriginalEntity 字段中嵌入 TodoItem 对象。

有没有人让 JSON.Net 正确反序列化这个?

【问题讨论】:

  • 显示一些允许重现您的问题的代码真的很有帮助。
  • 不幸的是,我需要提供整个解决方案。提供的单页应用程序模板会生成大量代码。要重现该错误,只需创建一个新的 MVC 4 单页应用程序。工具完成后,打开 TodoItem.cs 并添加一个日期时间字段。然后右键单击“控制器”并添加一个控制器。为其选择一个名称,选择 TodoItem 作为您的模型,SPA 模板并让它创建一个新的上下文。完成后,运行应用程序并导航到新控制器。点击添加并输入数据,然后点击保存。

标签: datetime asp.net-mvc-4 asp.net-web-api asp.net-spa


【解决方案1】:

问题在于,在当前的测试版中,ASP.NET Web API 使用DataContractJsonSerializer,这是众所周知的DateTime 序列化问题。 Here is 最近在 Microsoft Connect 上针对该问题提出了一个安静的错误; MS 回应说他们已经有一个跟踪问题的错误,但不会在 .Net 4.5/VS11 时间范围内修复。

幸运的是,您可以替换一个替代的 JSON 序列化程序,例如 James Newton-King'sexcellent 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();
              }
         });
    }
}    

【讨论】:

  • 我认为这可能接近正确答案,但它似乎不适用于我上面列出的场景。使用此代码并将其插入 webapi 后,尝试保存时触发以下错误:服务器错误:HTTP 状态代码:500,消息:此 DataController 不支持实体 'JObject' 的操作 'Update'。这可能是 upshot.js 的问题,但我还没有弄清楚这个错误来自哪里。
  • 我几乎不得不放弃这整条路。 Json.Net 似乎是答案,但是让它尊重 upshot.js 在单页应用程序示例代码中想要的 Json 格式是一个令人头疼的问题,我只是无法完全正确地工作。归根结底,我可以在 MVC3 中更快地做到这一点,并等待 MVC4 希望添加对这些东西的支持。我将把它标记为答案,因为海报将其证明为一个错误。
  • 感谢您的回答;可能值得联系 JSON.Net 并提出错误以查看 JSON.Net 是否可以修复(或提交补丁!)
【解决方案2】:

我遇到了完全相同的问题。我花了太多时间试图让 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);

周围的代码行包含在上下文中。我在 cmets 中找到了这个:http://blog.stevensanderson.com/2012/03/06/single-page-application-packages-and-samples/

您还想更改 _Editor.cshtml 中的 html 以绑定到“DateJson”,而不是“EnterDate”

这当然是一个杂牌,但它具有工作的优点,这是一个不小的壮举。

【讨论】:

    【解决方案3】:

    您还可以通过添加以下代码使 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 代码绑定到日期选择器的样子

        <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”。

    【讨论】:

      【解决方案4】:

      JSON.NET 需要 $type,而您有 __type 来指定实体类型,以便将其转换为 JObject。

      我用下面的笨蛋解决了它

      首先确保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 ````using (MyJsonTextReader jsonTextReader = new MyJsonTextReader(streamReader))

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多