Working with Entity Framework and Razor pages, I've currently got a system going where I wrap my models inside of view models and display them to the screen, which I think is what I'm supposed to be doing in the MVVM architecture.
However, I'm having some trouble casting a list of type 'Foo' to type 'FooView', but casting a variable of type 'Foo' to type 'FooView' is fine. Here is the code:
Student Class
public class Student
{
public Student()
{
Id = null;
FirstName = "";
Surname = "";
Age = 0;
DateOfBirth = new DateTime();
Results = new HashSet<Result>();
}
public Student(string firstName, string surname, DateTime dateOfBirth)
{
Id = null;
FirstName = firstName;
Surname = surname;
DateOfBirth = dateOfBirth;
Results = new HashSet<Result>();
}
public int? Id { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
public int Age { get; private set; }
// Implement backing field
private DateTime _dateOfBirth;
public DateTime DateOfBirth
{
get => _dateOfBirth;
set
{
// Set Age when date of birth is provided
_dateOfBirth = value;
Age = (DateTime.Today.Year - this._dateOfBirth.Year);
if (_dateOfBirth.Date > DateTime.Today.AddYears(-this.Age)) this.Age--;
}
}
public HashSet<Result> Results { get; set; }
public static implicit operator StudentView(Student student)
{
return new StudentView(student);
}
}
Successfully casting student item to student view item on razor page WORKS
@foreach (var item in Model.Result)
{
<tr>
<td>
@Html.DisplayFor(modelItem => ((StudentView)item.Student).FullName)
</td>
<td>
@Html.DisplayFor(modelItem => item.Score)
</td>
<td>
@Html.DisplayFor(modelItem => item.Grade)
</td>
<td>
<a asp-page="./Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-page="./Details" asp-route-id="@item.Id">Details</a> |
<a asp-page="./Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
Trying to cast list to type 'Student View' FAILS
public async Task OnGetAsync()
{
Student = await _context.Student.Cast<StudentView>().ToListAsync();
}
InvalidCastException: Unable to cast object of type 'StudentManagerDemoCore.Models.Student' to type 'StudentManagerDemoCore.ViewModels.StudentView'.
StudentManagerDemoCore.Pages.Teacher.ManageStudent.IndexModel.OnGetAsync() in Index.cshtml.cs
Student = await _context.Student.Cast<StudentView>().ToListAsync();
I have a feeling there is just something little I'm missing here, but I'm not sure what it is. The error implies there is an issue with my casting method, but that doesn't explain why it works sometimes.
Anyone know where I'm going wrong?
I managed to solve the problem by also implementing a conversion in the 'StudentView' class from 'StudentView' to 'Student' and then using Magnetron's solution.
So I implemented a conversion in the 'View' class. Why this is needed I have no idea, seems magical to me considering this conversion shouldn't be necessary for the conversion of 'Student' to 'StudentView'. Perhaps there is something written somewhere that a two way cast is required for some reason.
public class StudentView : ViewBase<Student>
{
// Removed for brevity
public static implicit operator Student(StudentView student)
{
return new Student(student);
}
}
Then updated the casting to be performed like so as highlighted by Magnetron:
public async Task OnGetAsync()
{
Student = await _context.Student.Select(x => (StudentView)x).ToListAsync();
}
I'd also like to highlight billybob's answer as he provided another correct way to solve the solution, I just chose a different way.
You have several options to solve your issue.
The first solution would be to manually do something like this:
await _context.Student.Select(student => new StudentView()
{
Id = student.Id,
//....
}
Another solution would be to have a manual mapper that will perform the mapping. A static function that takes a Student
and converts it to a StudentView
and returns it. You can reuse it elsewhere.
The best solution that I would suggest is to use AutoMapper.