How can we populate a ViewModel with following two properties:
query result type
from a LINQ query that returns only a single recordSelectList
typeIn other words, suppose we have a view that displays only a single movie based on the selected ID. And we want to assign a year of release to that movie from a dropdown list of years, how do I fill in the ????
in the following ViewModel and Controller?
Note: In the following ViewModel if I use myMovie property of type IQueryable<Movie>
and assign this property of ViewModel to the query qrySingleMovie from following controller I get the error the name qrySingleMovie does not exist in the current context
Models
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
public class Year
{
public int YearId {get; set;}
public int MovieYear {get; set;}
}
ViewModel
public class MovieGenreViewModel
{
public ???? myMovie;
public SelectList MovieReleaseYears;
public int ReleasedYr { get; set; }
}
Controller
public async Task<IActionResult> Index(int MovieID)
{
// Use LINQ to get list of Release years.
IQueryable<string> YearQuery = from m in _context.Years
orderby m.MovieYear
select m.MovieYear;
//Following query selects only a single movie based on Primary Key ID
var qrysingleMovie = from m in _context.Movies
where m.ID == movieID
select m;
var movieReleaseYrVM = new MovieGenreViewModel();
movieReleaseYrVM.MovieReleaseYears = new SelectList(await YearQuery.ToListAsync());
movieReleaseYrVM.myMovie = ????
return View(movieReleaseYrVM);
}
UPDATE
In my real project, the model has lots of properties and the corresponding View also displays most of those properties. I was thinking if there is a simpler way to define a ViewModel that does not contain all the properties of the model such as this ASP.NET Article defines a view model MovieGenreViewModel
but there they are using List and in their View they are iterating through multiple Movies. I'm trying to mimic their example but in my case it's a single record (not the list of records) with a SelectList
. How can I mimic that kind of scenario with a single record and not be able to include in View Model tons of model properties that model has?
Since you want to update a Movie record, all you need to get the movie record is it's unique Id. So why not add a MovieId property to your view model. If you prefer to show the MovieName in the UI, you may add a MovieName
property as well.
Remember view models are specific to views. It is not necessary to mimic your entire entity model when you create a view model. Add only those properties your view absolutely need.
public class MovieGenreViewModel
{
public int MovieId;
public string MovieName { set;get;}
public SelectList MovieReleaseYears;
public int ReleasedYr { get; set; }
}
Now in your GET action set these property values.
public async Task<IActionResult> Index(int MovieID)
{
var vm = new MovieGenreViewModel();
var movie = _context.Movies.FirstOrDefault(x=>x.ID==movieID);
if(movie==null)
return Content("Movie not found"); // to do :Return a view with "not found" message
IQueryable<string> YearQuery = from m in _context.Years
orderby m.MovieYear
select m.MovieYear;
// set the property values.
vm .MovieReleaseYears = new SelectList(await YearQuery.ToListAsync());
vm .MovieId= movie.ID;
vm .MovieName= movie.Name;
return View(vm );
}
Now in your view, you keep the movie id in a hidden field so that when you submit the form, it will be submitted to the HttpPost action method which handles the form submit.
@model MovieGenreViewModel
@using(Html.BeginForm("AssignYear","Home"))
{
<p>@Model.MovieName</p>
@Html.HiddenFor(s=>s.MovieId)
@Html.DropDownListFor(d=>d.ReleasedYr, Model.MovieReleaseYears)
<input type="submit"/>
}
You can use the same view model as your AssignYear
action method parameter
[HttpPost]
public ActionResult AssignYear(MovieGenreViewModel model)
{
// check model.MovieId and model.ReleasedYr
// to do : Save data and return something
}
If you do not prefer to add only those properties needed by the view to the view model, but want to show a tons of properties of the Movie, you may consider adding a new property of type Movie
to your view model
public class MovieGenreViewModel
{
public Movie Movie { set;get;}
public SelectList MovieReleaseYears;
public int ReleasedYr { get; set; }
}
Your existing LINQ statement returns a collection of one element. You cannot assign a collection of Movies to a property of type Movie. You may use the FirstOrDefault() method to get a single object.
var movie = _context.Movies.FirstOrDefault(x=>x.ID==movieID);
//After null check
vm.Movie = movie;