【问题标题】:ASP.NET MVC Posted Model lost interface Property values and ModelState has errors. Model Binding restriction? Serialization issue?ASP.NET MVC Posted Model 丢失接口属性值和 ModelState 有错误。模型绑定限制?序列化问题?
【发布时间】:2012-03-20 04:51:56
【问题描述】:

通过以下简单的操作...

[HttpPost]
public PartialViewResult DoSomething(AnimalInfo animalInfo)
{
    // animalInfo.AnimalKey SHOULD == DogKey { Id = 1, Name = "Dog" }
    // BUT animalInfo.AnimalKey == null

    return Something();
}

发布到此操作很好,并且animalInfo 的大多数属性都可用,除了我自己创建的对象。我假设这是一个序列化问题,所以我在我的类中添加了一些基本的序列化,但 AnimalKey 对象仍然为 null(在渲染时绝对不是 null)。

我是这样定义 AnimalInfo 的:

[DataContract]
public class AnimalInfo : IAnimalInfo
{
    [DataMember]
    public IAnimalKey AnimalKey { get; set; }

    public string Name { get; set; }
}

[DataContract]
public class DogKey : IAnimalKey
{
    public DogKey(int id){ DogId = id; }

    [DataMember]
    public int DogId { get; set; }
}

我从这样的视图发布到 Action...

<% var currentAnimal = new AnimalInfo { AnimalKey = new DogKey(1), Name = "Dog" }; %>
<% using (Html.BeginForm("DoSomething", "Controller", currentAnimal.AnimalInfo))
{
    %><button type="submit">post</button><%
} %>

但是当我的动作被执行时,AnimalKey 已经变为空,而 Name 是“Dog”。调查 ModelState 也发现了同样的情况。这似乎是一个序列化问题。是这样吗?如果是这样,DataContract 和 DataMember 是否不足以作为处理此问题的方法?


更新

作为一种替代方法,我尝试将其更改为强类型视图并使用 HiddenFor 发布数据,但我仍然得到完全相同的结果。

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Namespace.IAnimalInfo>" %>

<% using (Html.BeginForm("DoSomething", "OM", Model))
{
    Html.HiddenFor(m => m.AnimalKey);
    Html.HiddenFor(m => m.Name);
    %><button type="submit">revert</button><%
} %>
  • MVC HtmlHelpers 根本不序列化复杂对象吗?

  • 或者我是否需要为每个 AnimalKey 创建不同的模型才能取回这些值?

  • 或者甚至将我自己的序列化写入一个隐藏的字符串,然后在返回时反序列化它?


可能的原因

经过一番折腾,我很确定这是因为 IAnimalKey 是一个接口。当我向模型添加另一个名为 DogKey 的属性时,将返回该值。因此,它似乎失去了关于帖子上的对象类型或根本不支持接口的知识。


脏修复

看我的回答

【问题讨论】:

  • 我不认为你可以像这样将丰富的对象传递给BeginForm。该对象用于创建RouteValueCollection,它只是一个平面字典,而不是完整的对象树。
  • 如果你看一下 Html.HiddenFor(m => m.AnimalKey); 生成的 HTML,它是什么样子的——生成的隐藏字段的名称是什么,它的值是什么?
  • 我只是面临着类似的情况......无论我尝试什么ConcurrencyCheck 类型的byte[] 属性都不会添加到ModelState。你猜怎么了?它在接口中声明,然后在每个POCO 对象上声明。我必须创建ViewModels。没有其他方法可以开箱即用。我认为ViewModel 是要走的路。更多代码在路上... :D 不管怎样,你做了一些艰苦的代码!恭喜...

标签: .net asp.net-mvc serialization model-binding modelstate


【解决方案1】:

使用时无法完成任务

<% using (Html.BeginForm("DoSomething", "Controller", currentAnimal.AnimalInfo)) 

在BeginForm方法中放置模型对象会将序列化的字符串放入表单查询字符串中,它只支持简单的普通对象,甚至不支持集合。

你可以试试这个

@using (Html.BeginForm()) 
{
    @Html.TextBoxFor(o => Model.Name);
    @Html.TextBoxFor(o => Model.AnimalKey.DogId);
    <button type="submit">post</button>
}

值将在发布数据中传递给服务器并且它可以工作。

【讨论】:

  • 问题是我的 AnimalKey 是 IAnimalKey 并且我不知道它是否包含 DogId 或 CatId 所以不能明确地为它们编写 HtmlHelpers。这可能是我以前没有意识到的限制。
  • 是否可以在 IAnimalKey 中包含一个 ID,或者也可以包含一个属性“Type”?我认为这会让事情变得简单。
【解决方案2】:

请记住,@Html.Hidden 和 @Html.HiddenFor 只是创建这个的助手

<input type="hidden" value="yourValue" name="yourName" />

隐藏字段对于字符串、数字、Guid 等非常有用。在其上放置一个复杂的对象可能会对其进行ToString 操作。您可以使用浏览器的开发人员工具栏(通常为 F12)来检查 input 包含的内容。

我不得不承认,我通常使用 JavaScript(使用 jQuery)来完成这类事情。因为自定义绑定往往会变得复杂。我所做的是我有一个视图模型,我渲染到屏幕上并在提交时收集所需的信息并执行$.ajax 或$.post。

也许其他人可以为您提供如何实现自定义绑定的想法。

【讨论】:

  • 正如您所提到的,我认为 HtmlHelper 很可能只是在我的对象上调用 ToString ......所以似乎只支持最简单的值类型。我实现了自己的 Serialize 来输出一个字符串,并用它来存储和重建我的信息。
  • @Arkiliknam 有趣的解决方案。我偶然发现了许多绑定问题,最后放弃并转向客户端(jQuery)和服务器(ASP.NET MVC)之间的 JSON 通信。
  • ASP.NET MVC 模型绑定在工作时非常棒,但有时在尝试做一些稍微不同的事情时可能会很痛苦。我也多次使用 jQuery 和 JSON 来处理这些问题,但我更愿意尽可能保留 .NET。
【解决方案3】:

我认为你对 MVC 模型有一个基本的误解。

您所描述的、反序列化实例并在客户端存储是 Web 窗体的处理方式。实例被序列化为 ViewState,当页面回发时,实例数据会使用 ViewSource 重新水化。

类似这样的:

public AnimalInfo animalInfo
{
    get
    {
        return ViewState("AnimalInfo")
    }
    set (AnimalInfo value)
    {
        ViewState("AnimalInfo") = value
    }
}

这里发生的事情(在 Web 窗体世界中)是实例被序列化为 set 中的 ViewState 并从 get 中的 ViewState 反序列化。

MVC 中不存在这种机制。

首先没有 ViewState 机制。这实际上是 MVC 的主要优点之一,因为它使页面的重量比 Web 窗体页面轻很多。这主要是由于 ViewState 的大小。每个人都想要一个更轻的页面:)

其次,PostBack 的概念在 MVC 中也不存在。一旦页面被发送到客户端,就是这样。

MVC所做的是利用 POST 和 GET 的原始 Web 方法。

通过查询字符串 (GET) 或通过表单 (POST) 以两种方式之一传递信息。

说了这么多,这里有一些 MVC 方法来做你想做的事。

如果这是一个编辑/更新表单,您可以使用 Html.EditorForModel。这将为 ViewModel 的每个属性创建一个表单字段。它不仅会这样做,而且如果您正确连接控制器 ([HttpPost] public PartialViewResult DoSomething(AnimalInfo animalInfo)),MVC 将抓取 POST 表单并根据表单字段值为您重建实例,并将新实例分配给 animalInfo 变量。

如果这不是一个编辑/更新表单,那么为什么需要控制器为实例重新水化?

还有一些方法可以仅对实例的某些属性进行水合,例如 @HiTeddy 建议的 (@Html.TextBoxFor(o =&gt; Model.Name);)。这需要在控制器上进行一些额外的接线来告诉它从哪里检索属性值。

【讨论】:

  • 我不确定你是否理解这个问题。我有模型并了解它是什么。我正在尝试发布一个具有一个恰好是 IAnimalKey 类型的属性的模型。例如,当我使用表单发布帖子时,只有此属性变为空。但是,如果我将 IAnimalKey 换成具体类 DogKey,它将起作用。但我不想这样做,因为我有很多 IAnimalKey 类。
  • 如果有什么我想发布到我的控制器,它必须被序列化到渲染视图中才能这样做,在大多数情况下,一个简单的 .ToString() 就足够了,否则模型的所有知识都将丢失。
【解决方案4】:

还没有收到任何关于具体问题的答案,可能是因为它不受 ASP.NET MVC 支持,尽管我在 MSDN 上也找不到任何确认。因此,由于缺乏更好的答案,这是我的肮脏修复。基本上,我将 AnimalInfo 变成了一个抽象类,并实现了我称之为 Serialize 和 Deserialize 的方法,它们将每个 AnimalKey 转换为一个字符串,以便以后可以用来重新创建 AnimalKey。

这是因为在我用作 MVC 模型的新 AnimalInfo 类中,我有一个 SerializedKey 属性,它在 Get 尝试序列化 AnimalKey 时。使用 HtmlHelper 存储此属性将导致 Get 执行序列化,将产品存储在渲染视图中。 Set 方法将尝试将提供的字符串反序列化为 AnimialKey。因此,当 Post Action 发生时,MVC 会尝试设置 SerializedAnimalKey,并通过这样做调用 Deserialize 并使用我打算序列化的所有内容重新创建 AnimalKey 对象。

很想看到这个特定问题的其他答案,但现在这是我必须解决的问题。

public abstract class AnimalKey
{
    public abstract AnimalType AnimalType { get; }
    public abstract string Serialize();

    public static AnimalKey Deserialize(string serializedKey)
    {
        var split = serializedKey.Split('|');
        switch ((AnimalType)Convert.ToInt32(split[0]))
        {
            case AnimalType.Dog:
                var dogKey = new DogKey();
                var dogProperties = split[1].Split(',');
                dogKey.DogId = Convert.ToInt32(dogProperties[0]);
                return dogKey;
            // TODO: Other key implementations
        }
    }
}

public class AnimalInfo : IAnimalInfo
{
    public AnimalKey AnimalKey { get; set; }

    public string SerializedAnimalKey 
    {
        get { return AnimalKey != null 
            ? AnimalKey.Serialize() 
            : string.Empty;
        }
        set { AnimalKey = String.IsNullOrEmpty(value) ? null : AnimalKey.Deserialize(value); }
    }       
}

public class DogKey : AnimalKey
{
    [DataMember]
    public int DogId { get; set; }

    public override AnimalType AnimalType
    {
        get { return AnimalType.Dog; }
    }

    public override string Serialize()
    {
        var serializeBuilder = new StringBuilder();
        serializeBuilder.AppendFormat("{0}|", (int)AnimalType);
        serializeBuilder.AppendFormat("{0},", DogId);

        return serializeBuilder.ToString();
    }
}

【讨论】:

  • 它确实会引发其他潜在问题,但它可以完成这个相对简单的案例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-02
  • 2015-07-17
  • 2018-03-15
  • 1970-01-01
  • 2023-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多