Reputation: 23749
In my ASP.NET MVC Core app, from an action method shown below, I'm passing Blogs data and its related data from Posts table to a view as return View(await _context.Blogs.Include(p => p.Posts).ToListAsync());
Since I'm passing data from two tables, I need to use a ViewModel shown below. Question: How can I use ViewModel to pass the related data from my Controller Action method
Test()
to view shown below?
In the code below I'm getting the obvious error:
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'System.Collections.Generic.List'1[ASP_Core_Blogs.Models.Blog]', but this ViewDataDictionary instance requires a model item of type 'System.Collections.Generic.IList'1[ASP_Core_Blogs.Models.BlogPostViewModels.BlogsWithRelatedPostsViewModel]'.
Model:
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int PostYear { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
Controller:
[HttpGet]
public async Task<IActionResult> Test(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View(await _context.Blogs.Include(p => p.Posts).ToListAsync());
}
ViewModel:
public class BlogsWithRelatedPostsViewModel
{
public int BlogID { get; set; }
public int PostID { get; set; }
public string Url { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int PostYear { get; set; }
}
View:
@model IList<ASP_Core_Blogs.Models.BlogPostViewModels.BlogsWithRelatedPostsViewModel>
<div class="row">
<div class="col-md-12">
<form asp-controller="DbRelated" asp-action="EnterGrantNumbers" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post">
<table class="table">
<thead>
<tr>
<th></th>
<th></th>
<th>Url</th>
<th>Title</th>
<th>Content</th>
</tr>
</thead>
<tbody>
@for (int t = 0; t < Model.Count; t++)
{
<tr>
<td><input type="hidden" asp-for="@Model[t].BlogID" /></td>
<td><input type="hidden" asp-for="@Model[t].PostID" /></td>
<td>
<input type="text" asp-for="@Model[t].Url" style="border:0;" readonly /> <!--Not using /*Html.DisplayFor(modelItem => Model[t].Url)*/ since it does not submit stateName on Post. Not using <label asp-for=.....> since Bootstrap bold the text of <label> tag-->
</td>
<td>
<input asp-for="@Model[t].Title" />
</td>
<td>
<input asp-for="@Model[t].Content" />
</td>
</tr>
}
</tbody>
</table>
<button type="submit" class="btn btn-default">Save</button>
</form>
</div>
</div>
Upvotes: 2
Views: 1481
Reputation: 39326
You need to project your query using your BlogsWithRelatedPostsViewModel
class:
return View( _context.Blogs
.Include(p => p.Posts)
.SelectMany(e=> e.Posts.Select(p=> new BlogsWithRelatedPostsViewModel
{
BlogId= e.BlogId,
PostId=p.PostId,
Url=e.Url,
...
})
.ToList());
SelectMany
extension method allows you flatten each projection from e.Posts
into one sequence, so at the end you will get a List<BlogsWithRelatedPostsViewModel>
Upvotes: 2
Reputation: 2607
For passing data from Action to view as ViewModel. Create a new instance of your View Model first and assign value to each propery by calling your context query(whatever your Linq query is) and return the list of view as your View model variable.
var blogWithRelatedPost = new BolblogWithRelatedPost();
// your logic here for assigning value to property or LINQ query
return View(blogWithRelatedPost);
Upvotes: 0
Reputation: 495
On top of Octavioccl's, answer there is a nice little extension method I have been using (I don't know of the author to this but if anyone else knows, I will happily update my answer to give credit). This way, you don't have to write out each property.
public static T Cast<T>(this object myobj)
{
var target = typeof(T);
var x = Activator.CreateInstance(target, false);
var d = from source in target.GetMembers().ToList()
where source.MemberType == MemberTypes.Property
select source;
var memberInfos = d as MemberInfo[] ?? d.ToArray();
var members = memberInfos.Where(memberInfo => memberInfos.Select(c => c.Name)
.ToList().Contains(memberInfo.Name)).ToList();
foreach (var memberInfo in members)
{
var propertyInfo = typeof(T).GetProperty(memberInfo.Name);
if (myobj.GetType().GetProperty(memberInfo.Name) == null) continue;
var value = myobj.GetType().GetProperty(memberInfo.Name).GetValue(myobj, null);
propertyInfo.SetValue(x, value, null);
}
return (T)x;
}
Usage:
var ViewModelList = ModelList.Select(model => model.Cast<ViewModel>()).ToList();
There is also a well supported framework built for this specific problem. Called AutoMapper (http://automapper.org/).
Upvotes: 1