ASP.NET MVC опубликовал модель потерял интерфейс Значения свойств и ModelState содержит ошибки. Ограничение привязки модели? Проблема сериализации? - PullRequest
2 голосов
/ 01 марта 2012

С помощью следующего простого действия ...

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

    return Something();
}

Публикация этого действия является хорошей, и большинство свойств animalInfo доступны, за исключением объекта, который я создал сам.Я предполагал, что это была проблема с сериализацией, поэтому я добавил некоторую базовую сериализацию в свой класс, но я все еще получаю нулевое значение для объекта AnimalKey (который определенно не равен нулю во время рендеринга).

Вот как я определяю 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; }
}

И я публикую в действии вид, подобный этому ...

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

Но к тому времени, когда мое действие выполнено, AnimalKey становится пустым, а имя равно "Собака".Просмотр 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 в мою модель, значение возвращается.Так что, похоже, он потерял знание о том, какой тип объекта он находился на посту или вообще не поддерживает интерфейсы.


DIRTY FIX

См. Мой ответ

Ответы [ 4 ]

2 голосов
/ 05 марта 2012

Не получено никаких ответов на конкретную проблему, возможно, из-за того, что она не поддерживается ASP.NET MVC, хотя я также не смог найти подтверждения на MSDN. Итак, вот мое грязное исправление из-за отсутствия лучшего ответа. По сути, я превратил AnimalInfo в абстрактный класс и реализовал так называемые методы Serialize и Deserialize, которые преобразуют каждый AnimalKey в строку, которую можно использовать для воссоздания AnimalKey позднее.

Это работает, потому что в моем новом классе AnimalInfo, который я использую как модель MVC, у меня есть свойство для SerializedKey, которое при Get пытается сериализовать AnimalKey. Использование HtmlHelper для сохранения этого свойства приведет к тому, что Get выполнит сериализацию, сохраняя продукт в визуализированном представлении. Метод Set попытается десериализовать предоставленную строку в AnimialKey. Поэтому, когда происходит действие Post, 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();
    }
}
1 голос
/ 02 марта 2012

Помните, что @Html.Hidden и @Html.HiddenFor являются просто помощниками для создания этого

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

Скрытое поле очень полезно для строк, чисел, Guid и т. Д. Размещение сложного объекта на нем, вероятно, будет делать ToString операция на нем.Вы можете использовать панель инструментов разработчика браузера (обычно F12), чтобы проверить, что содержит input.

Я должен признать, что я обычно делал подобные вещи, используя JavaScript (с jQuery).Поскольку пользовательские привязки имеют тенденцию становиться сложными.Что я делаю, так это то, что у меня есть модель представления, которая отображается на экране, и при отправке я собираю необходимую информацию и выполняю $.ajax или $.post.

Может быть, кто-то еще может дать вам идеи, как реализовать пользовательское связывание.

0 голосов
/ 03 марта 2012

Я думаю, у вас есть базовое недопонимание модели MVC.

То, что вы описываете, десериализацию экземпляра и хранение на стороне клиента, - это способ работы в Web Forms. Экземпляры сериализуются в ViewState, и когда страница отправляется обратно, данные экземпляра повторно гидрируются с использованием ViewSource.

Примерно так:

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

В данном случае (в мире веб-форм) экземпляр сериализуется в ViewState в set и десериализуется из ViewState в get.

Этот механизм не существует в MVC.

Во-первых, нет механизма ViewState. Это на самом деле одно из главных преимуществ MVC, поскольку в результате получается страница, которая весит намного меньше, чем страница веб-форм. Это в основном из-за размера ViewState. И всем нужна более легкая страница:)

Во-вторых, концепция PostBack также не существует в MVC. Как только страница отправлена ​​клиенту, вот и все.

Что делает MVC - использует оригинальные веб-методы POST и GET.

Передача информации осуществляется одним из двух способов: через строку запроса (GET) или через форму (POST).

Все это, как говорится, вот несколько способов MVC делать то, что вы хотите.

Если это форма редактирования / обновления, вы можете использовать Html.EditorForModel. Это создаст поле формы для каждого свойства вашей ViewModel. Мало того, что он это сделает, но если вы правильно подключите свой контроллер ([HttpPost] public PartialViewResult DoSomething(AnimalInfo animalInfo)), MVC будет сканировать форму POST и восстановит для вас экземпляр по значениям поля формы и назначит новый экземпляр переменной animalInfo.

Если это не форма редактирования / обновления, то зачем вам контроллер для регидратации экземпляра?

Существуют также способы увлажнения только определенных свойств экземпляра, как предложено @HiTeddy (@Html.TextBoxFor(o => Model.Name);). Это потребует некоторой дополнительной проводки на контроллере, чтобы сообщить ему, откуда извлекать значения свойств.

0 голосов
/ 02 марта 2012

Вы не можете добиться цели при использовании

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

Значения передаются на сервер в данных поста, и это работает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...