nam nam - 1 month ago 17
C# Question

ViewModel with a SelectList and a LINQ Query result with a single reocrd

How can we populate a ViewModel with following two properties:


  1. A
    query result type
    from a LINQ query that returns only a single record

  2. A
    SelectList
    type



In 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?

Answer

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;