Не могу получить модели на бэк из view по связи many-to many в приложении MVC
Помогите, пожалуйста, разобраться с проблемой. Делаю проект Movie. БД подключал через CodeFirst подход. Есть модели со связями many-to-many (к примеру Actor и Movie). После реализации контроллера с методом create, при создании нового фильма, во вьюшке не могу получить данные, т.е. у меня отображаются доступные актеры, но я не могу их выбрать, фильм создается с пустыми данными. Но если прописать связи напрямую в БД (через SQLManager, что ActorId = 1 принадлежит MovieId = 3), то все срабатывает.

вот каким кодом пытаюсь это получить модели
public class Actor
{
public int ActorId { get; set; }
[Required(ErrorMessage = "Full Name is required")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "Last name cannot be longer than 50 characters and less 3.")]
[Column("FullName")]
public string FullName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:dd-MM-yyyy}", ApplyFormatInEditMode = true)]
public DateTime DayOfBirth { get; set; }
[Required(ErrorMessage = "Image is required")]
public string ImageUrl { get; set; }
public string Biografy { get; set; }
//movies
public virtual ICollection<Movie> Movies { get; set; }
}
public class Movie
{
public int MovieId { get; set; }
[Required(ErrorMessage = "Title is required")]
[Display(Name = "Title"), StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Required(ErrorMessage = "Image is required")]
public string ImageUrl { get; set; }
[StringLength(500, MinimumLength = 3, ErrorMessage = "Descriptipn cannot be longer than 500 characters and less 3.")]
public string Description { get; set; }
[Display(Name = "Release Date"), DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
//Genre
public Genre Genre { get; set; }
//Rating
public Rating Rating { get; set; }
//actors
public virtual ICollection<Actor> Actors { get; set; }
//producers
public virtual ICollection<Producer> Producers { get; set; } }
Контроллер с методом Create
public class MoviesController : Controller
{
private readonly IMoviesRepository _service;
private readonly IProducerRepository _producer;
private readonly IToastNotification _toastNotification;
public MoviesController(IMoviesRepository service, IToastNotification toastNotification, IProducerRepository producer)
{
_service = service;
_toastNotification = toastNotification;
_producer = producer;
}
// GET: Movies/Create
public async Task<IActionResult> Create()
{
var movieDropdownsData = await _service.GetMovieDropdownsValues();
ViewBag.Producers = new MultiSelectList(movieDropdownsData.SelectedProducers, "ProducerId", "FullName");
ViewBag.Actors = new SelectList(movieDropdownsData.SelectedActors, "ActorId", "FullName");
return View();
}
// POST: Movies/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("MovieId,Title,ImageUrl,Description,ReleaseDate,Genre,Ratin,ActorsId,ProducersId")] Movie movie)
{
if (ModelState.IsValid)
{
await _service.AddAsync(movie);
_toastNotification.AddSuccessToastMessage("Movie created");
return RedirectToAction(nameof(Index));
}
var movieDropdownsData = await _service.GetMovieDropdownsValues();
ViewBag.Producers = new MultiSelectList(movieDropdownsData.SelectedProducers, "ProducerId", "FullName");
ViewBag.Actors = new SelectList(movieDropdownsData.SelectedActors, "ActorId", "FullName");
return View(movie);
} }
вот вьюшка на создание фильма
@model WebAppMovie.Models.Movie
@using WebAppMovie.Data.Enums
@using WebAppMovie.Data
@{
ViewData["Title"] = "Create a new movie";
}
<br />
<h1 class="mt-5">Create Movies</h1>
<br />
<div class="row">
<div class="col-md-8 offset-2">
<div class="row flex-nowrap">
<div class="form-group text-center">
<img id="ImagePreview" style="max-width: 350px" />
</div>
<div class="col-md-8 offset-2">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="control-label"></label>
<input asp-for="ReleaseDate" type="date" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ImageUrl" class="control-label"></label>
<input asp-for="ImageUrl" class="form-control" />
<span asp-validation-for="ImageUrl" class="text-danger"></span>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label asp-for="Genre" class="control-label"></label>
<select asp-for="Genre" class="form-control" asp-
items="Html.GetEnumSelectList<Genre>()"></select>
<span asp-validation-for="Genre" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Rating" class="control-label"></label>
<select asp-for="Rating" class="form-control" asp-
items="Html.GetEnumSelectList<Rating>()"></select>
<span asp-validation-for="Rating" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="@Model.Producers" class="control-label"></label>
<select asp-for="@Model.Producers" class="form-control" asp-
items="ViewBag.Producers" multiple></select>
@*@Html.DropDownListFor(m => m.Producers,
(MultiSelectList)ViewBag.Producers)*@
@*@Html.ListBoxFor(m => m.Producers,
(MultiSelectList)ViewBag.Producers)*@
<span asp-validation-for="@Model.Producers" class="text-danger">
</span>
</div>
<div class="form-group">
<label asp-for="Actors" class="control-label"></label>
<select asp-for="Actors" class="form-control" asp-
items="ViewBag.Actors"></select>
<span asp-validation-for="Actors" class="text-danger"></span>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label asp-for="Description" class="control-label"></label>
<textarea asp-for="Description" class="form-control"></textarea>
<span asp-validation-for="Description" class="text-danger"></span>
</div>
</div>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-outline-success float-right" />
<a class="btn btn-outline-secondary" asp-action="Index">Back to List</a>
</div>
</form>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script>
$("#ImageUrl").on("change", function () {
var output = document.getElementById('ImagePreview');
output.src = $(this).val();
})
</script>
}
ну и как пытаюсь связать Movie с Actor
public async Task<NewMovieDropdown> GetMovieDropdownsValues()
{
var response = new NewMovieDropdown()
{
SelectedActors = await _context.Actors.OrderBy(n => n.FullName).ToListAsync(),
SelectedProducers = await _context.Producers.OrderBy(n => n.FullName).ToListAsync()
};
return response;
}
public class NewMovieDropdown
{
public NewMovieDropdown()
{
SelectedProducers = new List<Producer>();
SelectedActors = new List<Actor>();
}
public List<Producer> SelectedProducers { get; set; }
public List<Actor> SelectedActors { get; set; }
}
создаю таблицы в БД
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
public DbSet<Actor> Actors { get; set; }
public DbSet<Movie> Movies { get; set; }
public DbSet<Producer> Producers { get; set; }
//public DbSet<Score> Scores { get; set; }
public DbSet<Person> Persons { get; set; }
}
Буду благодарен за любую помощь. Спасибо.
Ответы (1 шт):
В общем для начала нужно было в классе class ApplicationDbContext добавить метод с указанием ключей
modelBuilder.Entity<Movie>()
.HasMany(p => p.Actors)
.WithMany(m => m.Movies)
.UsingEntity<ActorMovies>(
j => j
.HasOne(pm => pm.Actor)
.WithMany()
.HasForeignKey(p => p.ActorId),
j => j
.HasOne(pm => pm.Movie)
.WithMany()
.HasForeignKey(m => m.MovieId)
);
base.OnModelCreating(modelBuilder);
потом создать класс связующий
public class ActorMovies
{
public int ActorId { get; set; }
public Actor Actor { get; set; }
public int MovieId { get; set; }
public Movie Movie { get; set; }
}
далее класс с новым фильмом
public class NewMovieViewModel
{
public int NewMovieId { get; set; }
[Required(ErrorMessage = "Title is required")]
[Display(Name = "Title"), StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Required(ErrorMessage = "Image is required")]
public string ImageUrl { get; set; }
[StringLength(1500, MinimumLength = 3, ErrorMessage = "Descriptipn cannot be longer than 1500 characters and less 3.")]
public string Description { get; set; }
[Display(Name = "Release Date"), DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
//Genre
public Genre Genre { get; set; }
//Rating
public Rating Rating { get; set; }
//Grade
//public List<double> Grade { get; set; }
//actors
public List<int> ActorsMovieId { get; set; }
//producers
public List<int> ProducersMovieId { get; set; }
//comments
public List<Comment> CommentsMovie { get; set; }
}
нужно описать новый метод, который достает актеров по id
public async Task AddNewMovieAsync(NewMovieViewModel data)
{
var newMovie = new Movie()
{
Title = data.Title,
ImageUrl = data.ImageUrl,
Description = data.Description,
ReleaseDate = data.ReleaseDate,
Genre = data.Genre,
Rating = data.Rating,
Comments = data.CommentsMovie,
};
await _context.Movies.AddAsync(newMovie);
await _context.SaveChangesAsync();
foreach (var producer in data.ProducersMovieId)
{
var newProducerMovies = new ProducerMovies()
{
MovieId = newMovie.MovieId,
ProducerId = producer
};
await _context.AddAsync(newProducerMovies);
}
await _context.SaveChangesAsync();
foreach (var actors in data.ActorsMovieId)
{
var newActorMovies = new ActorMovies()
{
MovieId = newMovie.MovieId,
ActorId = actors
};
await _context.AddAsync(newActorMovies);
}
await _context.SaveChangesAsync();
}
ну и во вьюшке добавить возможность выбирать по id
<div class="form-group">
<label asp-for="ProducersMovieId" class="control-label"></label>
<select asp-for="ProducersMovieId" class="form-control" asp-items="ViewBag.Producers" multiple></select>
<span asp-validation-for="ProducersMovieId" class="text-danger">
</span>
</div>
И обязательно прописать в контроллере с методом Create выбранный параметр ProducersMovieId в Bind[]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("MovieId,Title,ImageUrl,Description,ReleaseDate,Genre,Rating,ProducersMovieId,ActorsMovieId,CommentId")] NewMovieViewModel movie)
{
if (ModelState.IsValid)
{
await _service.AddNewMovieAsync(movie);
_toastNotification.AddSuccessToastMessage("Movie created");
return RedirectToAction(nameof(Index));
}
var movieDropdownsData = await _service.GetMovieDropdownsValues();
ViewBag.Producers = new SelectList(movieDropdownsData.SelectedProducers, "ProducerId", "FullName");
ViewBag.Actors = new SelectList(movieDropdownsData.SelectedActors, "ActorId", "FullName");
return View(movie);
}
И только после этого все получилось.

