Кажется, это довольно простая проблема, с которой я столкнулся около часа, и я не понимаю, что я делаю неправильно.
У меня есть ViewModel:
public class SongFormViewModel
{
public Song Song { get; set; }
public SelectList AlbumList { get; set; }
public SongFormViewModel(Song song, IQueryable<Album> albumList)
{
Song = song;
AlbumList = new SelectList(albumList, "AlbumId", "Title", song.AlbumId);
}
}
У меня есть представление Create:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<NightSpot.Models.SongFormViewModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Create
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Create</h2>
<%= Html.ValidationSummary("Create was unsuccessful. Please correct the errors and try again.") %>
<% using (Html.BeginForm()) {%>
<fieldset>
<legend>Fields</legend>
<p>
<label for="AlbumId">AlbumId:</label>
<%= Html.DropDownList("AlbumId", Model.AlbumList) %>
<%= Html.ValidationMessage("AlbumId", "*") %>
</p>
<p>
<label for="Title">Title:</label>
<%= Html.TextBox("Title") %>
<%= Html.ValidationMessage("Title", "*") %>
</p>
<p>
<label for="TrackNumber">TrackNumber:</label>
<%= Html.TextBox("TrackNumber") %>
<%= Html.ValidationMessage("TrackNumber", "*") %>
</p>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
<div>
<%=Html.ActionLink("Back to List", "Index") %>
</div>
</asp:Content>
И у меня есть метод SongsController Create:
//
// POST: /Songs/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Song song)
{
if (ModelState.IsValid)
{
try
{
repository.Add(song);
repository.Save();
return RedirectToAction("Details", new { id = song.SongId });
}
catch
{
ModelState.AddRuleViolations(song.GetRuleViolations());
}
}
return View(new SongFormViewModel(song, repository.FindAllAlbums()));
}
Когда я перехожу к /Songs/Create URL, я вижу ожидаемый интерфейс. Мой раскрывающийся список содержит список всех действующих альбомов в моей базе данных (из таблицы "Альбомы" ), и я подтвердил, что каждое значение AlbumId является значением, и каждый заголовок является текстом. Отлично.
Итак, теперь, когда я отправляюсь заполнять свою форму и нажимаю "Сохранить", я получаю сообщение о том, что мне нужно выбрать альбом. Когда я перехожу к отладчику в Visual Web Developer, я вижу, что номер заголовка и трека заполняется правильно, но мой объект Album равен NULL, а AlbumId - 0. Все идеи?
UPDATE
Отвечая Мэтту, я обновил свой SongsController, чтобы использовать SongFormViewModel вместо Песни. Здесь новый контроллер:
//
// POST: /Songs/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(SongFormViewModel song)
{
Song newSong = song.Song;
newSong.AlbumId = (int) song.AlbumList.SelectedValue;
if (ModelState.IsValid)
{
try
{
repository.Add(newSong);
repository.Save();
return RedirectToAction("Details", new { id = song.Song.SongId });
}
catch
{
ModelState.AddRuleViolations(song.Song.GetRuleViolations());
}
}
return View(new SongFormViewModel(newSong, repository.FindAllAlbums()));
}
Я активировал свой /Songs/Create URL и получил сообщение о том, что SongFormViewModel требует конструктора без параметров.
Итак, я сделал один.
Когда я повторно запускал /Songs/Create URL, я получил исключение "Object reference not set to a instance of object" в этой строке в моем SongsController:
Line 93: newSong.AlbumId = (int) song.AlbumList.SelectedValue;
Идеи?
UPDATE
ОК, поэтому я обновил свой взгляд на предложение Мэтта о префиксах полей "Песня":
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<NightSpot.Models.SongFormViewModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Create
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Create</h2>
<%= Html.ValidationSummary("Create was unsuccessful. Please correct the errors and try again.") %>
<% using (Html.BeginForm()) {%>
<fieldset>
<legend>Fields</legend>
<p>
<label for="Song.AlbumId">AlbumId:</label>
<%= Html.DropDownList("Song.AlbumId", Model.AlbumList) %>
<%= Html.ValidationMessage("Song.AlbumId", "*") %>
</p>
<p>
<label for="Song.Title">Title:</label>
<%= Html.TextBox("Song.Title") %>
<%= Html.ValidationMessage("Song.Title", "*") %>
</p>
<p>
<label for="Song.TrackNumber">TrackNumber:</label>
<%= Html.TextBox("Song.TrackNumber") %>
<%= Html.ValidationMessage("Song.TrackNumber", "*") %>
</p>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
<div>
<%=Html.ActionLink("Back to List", "Index") %>
</div>
</asp:Content>
Поведение, к сожалению, такое же. Null SelectedValue и пустой объект Song.
Вы пытаетесь использовать MVC как классический ASP.NET
MVC возвращает исходные значения HTTP-сообщения, а помощники по умолчанию с MVC пытаются преобразовать их в основные модели
Вы можете получить значение выбранного списка, например. AlbumId, но не сложные типы, такие как AlbumList.SelectedValue
Попробуйте что-то вроде этого
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(int albumId, ...)
SongFormViewModel не помогает вам здесь. Он не отражает то, что вы действительно хотите. Он должен иметь что-то вроде выбранного в настоящее время альбома в качестве поля
Я только сейчас изучаю этот материал сам, но из того, что я понимаю, передавая song.AlbumId в конструктор SelectList, только устанавливает начальное выбранное значение списка. Он не привязывает список к свойству Album объекта песни.
В вашем методе Create вам придется вручную назначить альбом, представленный выбранным элементом, в списке SongFormViewModel.Albums. Вы можете использовать свойство SelectedValue для извлечения идентификатора альбома, выбранного пользователем в этом случае. Вам нужно будет передать его в int (или Guid - независимо от типа AlbumId).
Конечно, это означает, что вам нужен доступ к классу ViewModel из вашего метода Create, а не только к созданной песне. Я вполне уверен, что вы можете просто изменить тип параметра из песни в SongFormViewModel, и пусть связующее по умолчанию позаботится об этом для вас.
Дайте мне знать, как вы поживаете - я очень хочу узнать больше об этом, поэтому я хотел бы увидеть окончательное решение!
ИЗМЕНИТЬ
Если вы хотите сохранить свою существующую модель. Измените свой SongFormViewModel на этот
public class SongFormViewModel
{
public Song Song { get; set; }
public SelectList AlbumList { get; set; }
}
а затем измените
return View(new SongFormViewModel(song, repository.FindAllAlbums()));
к
var songFormViewModel = new SongFormViewModel();
songFormViewModel.AlbumList = repository.FindAllAlbums();
songFormViewModel.Song = song;
return View(songFormViewModel);
-
Ваше представление должно быть строго типизированным типом Song not SongFormViewModel. Поэтому измените
Inherits="System.Web.Mvc.ViewPage<NightSpot.Models.SongFormViewModel>"
to
Inherits="System.Web.Mvc.ViewPage<NightSpot.Models.Song>"
Теперь избавьтесь от изменений полей префиксов. Это должно работать.