Indented SelectList Items - asp.net-mvc

I have a List of Categories (entities) with each of them having parents. Here is the Model for the category :
public class Category
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CategoryId { get; set; }
public int? ParentId { get; set; }
public string Name { get; set; }
public virtual Category Parent { get; set; }
}
I need to populate a SelectList such that the Parents come first and the childrens go indented Like:
Category 1
SubCategory 1
SubCategory 2
Category 2
I have to use this selectlist to poulate a dropdown in the edit and update forms. How do I go about doing this? clientside? serverside?
Thanks for any help you can offer.

This got me thinking about implementing this without the dependency of an interface, which I still think is reasonable. Here is an alternative solution using an extension method that does not require an interface to be implemented.
Extension Method
public static class ExtensionMethods
{
/// <summary>
/// Returns a single-selection select element containing the options specified in the items parameter.
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
/// <param name="helper">The class being extended.</param>
/// <param name="items">The collection of items used to populate the drop down list.</param>
/// <param name="parentItemsPredicate">A function to determine which elements are considered as parents.</param>
/// <param name="parentChildAssociationPredicate">A function to determine the children of a given parent.</param>
/// <param name="dataValueField">The value for the element.</param>
/// <param name="dataTextField">The display text for the value.</param>
/// <returns></returns>
public static MvcHtmlString DropDownGroupList<T>(
this HtmlHelper helper,
IEnumerable<T> items,
Func<T, bool> parentItemsPredicate,
Func<T, T, bool> parentChildAssociationPredicate,
string dataValueField,
string dataTextField)
{
var html = new StringBuilder("<select>");
foreach (var item in items.Where(parentItemsPredicate))
{
html.Append(string.Format("<optgroup label=\"{0}\">", item.GetType().GetProperty(dataTextField).GetValue(item, null)));
foreach (var child in items.Where(x => parentChildAssociationPredicate(x, item)))
{
var childType = child.GetType();
html.Append(string.Format("<option value=\"{0}\">{1}</option>", childType.GetProperty(dataValueField).GetValue(child, null), childType.GetProperty(dataTextField).GetValue(child, null)));
}
html.Append("</optgroup>");
}
html.Append("</select>");
return new MvcHtmlString(html.ToString());
}
}
Usage based on your Category class
#this.Html.DropDownGroupList(YourCollection, x => !x.ParentId.HasValue, (x, y) => { return x.ParentId.Equals(y.CategoryId); }, "CategoryId", "Name")
By the time I finished writing this post I wasn't so sure this was all that valuable but thought I'd post it anyways.
As you can see, your class must know the id of it's parent and the display name of both the child and parent should use the same property as indicated by the dataTextField parameter. So, essentially, your class needs the properties: Id, ParentId, and Name and you use the Func<T, bool> and Func<T, T, bool> parameters to determine relationships.
Don't forget to add in the necessary validation!

I would do this server-side using optgroup like this in razor syntax.
<select>
#foreach(var parent in categories.Where(x => !x.ParentId.HasValue)
{
<optgroup label="#parent.Name">
#foreach(var child in categories.Where(x => x.ParentId.Equals(parent.CategoryId))
{
<option value="#child.CategoryId">#child.Name</option>
}
</optgroup>
}
</select>
I would also make this an extension methods so it would like the other available HTML helper methods.
Edit
An extension method could accept a collection of items which implement a simple interface like this:
public interface IGroupable
{
int Id { get; set; }
string Name { get; set; }
int? ParentId { get; set; }
}
public class Category : IGroupable
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CategoryId { get; set; }
public int? ParentId { get; set; } //implements IGroupable.ParentId
public string Name { get; set; } //implements IGroupable.Name
public virtual Category Parent { get; set; }
#region [IGroupable Specific Implementation]
public int Id { get { return this.CategoryId; } }
#endregion
}
public static class ExtensionMethods
{
public static MvcHtmlString DropDownGroupList(this HtmlHelper helper, IEnumerable<IGroupable> items)
{
var html = new StringBuilder("<select>");
foreach (var item in items.Where(x => !x.ParentId.HasValue))
{
html.Append(string.Format("<optgroup label=\"{0}\">", item.Name));
foreach(var child in items.Where(x => x.ParentId.Equals(item.Id)))
{
html.Append(string.Format("<option value=\"{0}\">{1}</option>", child.Id, child.Name));
}
html.Append("</optgroup>");
}
html.Append("</select>");
return new MvcHtmlString(html.ToString());
}
}

Related

how to add dropdownlistfor(dynamic object)

I have class include the following objects
public class ProductViewer
{
public J_Items Item { get; set; }
public List<J_ItemsImages> lstItemImages { get; set; }
}
how can i add DropDownListFor Item.ItemName
J_Items definition
public int ID { get; set; }
public string ItemName { get; set; }
public string ItemShortDesc { get; set; }
public string ItemLongDesc { get; set; }
#Html.DropdownlistFor(m => m.Item.ItemName)
Where m is your ProductViewer class, of course that should be made in to a viewmodel.
You can use the first argument of the Html.DropDownListFor() helper method to designate which property you want to bind to and then use the second to supply a collection to populate the actual drop down list with :
<!-- This would bind to the ItemName property, but would populate the list with
the contents of your lstItemImages property -->
#Html.DropDownListFor(m => m.Item.Name, new SelectList(Model.lstItemImages, "Name", "Name"))
Likewise, if you had a collection of values on your J_Items class, you could access those in the second parameter as well:
#Html.DropDownListFOr(m => m.Item.Name, new SelectList(Model.Item.SomeCollection,"Value,"Text")
This assumes that your parent ProductViewer class would be passed into the view.

EF7 many to many relationship. How to pass data from multi select dropdown to controller?

My Model
public class FlightBooking
{
public int Id { get; set; }
public ICollection<FlightPassenger> Passengers { get; set; }
public DateTime DateJourney { get; set; }
public virtual City FromCity { get; set; }
public virtual City ToCity { get; set; }
}
public class FlightPassenger
{
public int FlightBookingId { get; set; }
public FlightBooking FlightBooking { get; set; }
public int CustomerId { get; set; }
public Customer Passenger { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public DateTime BirthDate { get; set; }
public ICollection<FlightPassenger> FlightPassengers { get; set; }
}
And in the OnModelCreating I have added
modelBuilder.Entity<FlightPassenger>().HasKey(x => new { x.FlightBookingId, x.CustomerId });
This creates the 3 tables in the database. Customer, FlightBooking and FlightPassenger. All this is fine to represent the many to many relationship in EF7. Now I am trying to take this input from the user.
My view
<select asp-for="Passengers" asp-items="Enumerable.Empty<SelectListItem>()" class="form-control customer"></select>
I am getting the data properly using Ajax and able to select the multiple values in the dropdown. But in the controller no value is passed in Passengers and its count is 0. I checked for the value in the dropdown before posting and it shows ids of the selected customers with comma. I know Passengers is not an integer array but adding an integer array to the model gives another error, so I was thinking there has to be another way. I did a small hack to by adding a string to my view model and before posting adding this integer array to the string. This string has all the values (comma sep) in the controller. But I am sure there should be a better way. Any guidance on getting this value from the view and eventually storing in the database would be great.
In my current project I have a lot of many-to-many relationships. As far as I know EF Core does not yet support many-to-many so I assume it has to be done manually. I generalized the solution.
As I'm new to EF/MVC feedback is welcome:
First I created a JoinContainer to hold the necessary data for the many-to-many entity.
public class SimpleJoinContainerViewModel
{
public int[] SelectedIds { get; set; }
public IEnumerable<SelectListItem> SelectListItems { get; set; }
// keeping track of the previously selected items
public string PreviousSelectedHidden { get; set; }
public int[] PreviousSelectedIds
{
get
{
// if somebody plays around with the hidden field containing the ints the standard exception/error page is ok:
return PreviousSelectedHidden?.Split(' ').Where(s => !string.IsNullOrEmpty(s)).Select(int.Parse).ToArray();
}
private set { PreviousSelectedHidden = value == null ? "" : string.Join(" ", value); }
}
/// <summary>
/// Call when form is loaded - not on post back
/// </summary>
/// <param name="selectListItems"></param>
/// <param name="selectedIds">Currently selected referenced ids. Get via m:n/join-table</param>
public void Load(IEnumerable<SelectListItem> selectListItems, IEnumerable<int> selectedIds)
{
SelectListItems = selectListItems;
SelectedIds = selectedIds?.ToArray();
PreviousSelectedIds = SelectedIds;
}
}
In the view model (of FlightBooking):
[Display(Name = "Passengers")]
public SimpleJoinContainerViewModel PassengersJoinContainer { get; set; } = new SimpleJoinContainerViewModel();
In the GET action I use the Load() method to fill the Container with the data:
viewModel.PassengerJoinContainer.Load(
DbContext.Customers
.Select(s => new SelectListItem
{
Text = s.LastName,
Value = s.Id.ToString()
}),
flightBookingEntity?.Passengers?.Select(p => p.CustomerId));
In the view I use the properties of the JoinContainer:
<div class="form-group">
<label asp-for="PassengersJoinContainer" class="col-sm-3 control-label"></label>
<div class="col-sm-9">
<div class="nx-selectize">
#Html.ListBoxFor(m => m.PassengersJoinContainer.SelectedIds, Model.PassengersJoinContainer.SelectListItems)
</div>
#Html.HiddenFor(m => m.PassengersJoinContainer.PreviousSelectedHidden)
<span asp-validation-for="PassengersJoinContainer" class="text-danger"></span>
</div>
</div>
Then I have a generalized Update class/method.
public class SimpleJoinUpdater<T> where T : class, new()
{
private DbContext DbContext { get; set; }
private DbSet<T> JoinDbSet { get; set; }
private Expression<Func<T, int>> ThisJoinIdColumn { get; set; }
private Expression<Func<T, int>> OtherJoinIdColumn { get; set; }
private int ThisEntityId { get; set; }
private SimpleJoinContainerViewModel SimpleJoinContainer { get; set; }
/// <summary>
/// Used to update many-to-many join tables.
/// It uses a hidden field which holds the space separated ids
/// which existed when the form was loaded. They are compared
/// to the current join-entries in the database. If there are
/// differences, the method returns false.
/// Then it deletes or adds join-entries as needed.
/// Warning: this is not completely safe. A race condition
/// may occur when the update method is called concurrently
/// for the same entities. (e.g. 2 persons press the submit button at the same time.)
/// </summary>
/// <typeparam name="T">Type of the many-to-many/join entity</typeparam>
/// <param name="dbContext">DbContext</param>
/// <param name="joinDbSet">EF-context dbset for the join entity</param>
/// <param name="thisJoinIdColumn">Expression to the foreign key (Id/int) which points to the current entity</param>
/// <param name="otherJoinIdColumn">Expression to the foreign key (Id/int) which points to the joined entity</param>
/// <param name="thisEntityId">Id of the current entity</param>
/// <param name="simpleJoinContainer">Holds selected ids after form post and the previous selected ids</param>
/// <returns>True if updated. False if data has been changed in the database since the form was loaded.</returns>
public SimpleJoinUpdater(
DbContext dbContext,
DbSet<T> joinDbSet,
Expression<Func<T, int>> thisJoinIdColumn,
Expression<Func<T, int>> otherJoinIdColumn,
int thisEntityId,
SimpleJoinContainerViewModel simpleJoinContainer
)
{
DbContext = dbContext;
JoinDbSet = joinDbSet;
ThisJoinIdColumn = thisJoinIdColumn;
OtherJoinIdColumn = otherJoinIdColumn;
ThisEntityId = thisEntityId;
SimpleJoinContainer = simpleJoinContainer;
}
public bool Update()
{
var previousSelectedIds = SimpleJoinContainer.PreviousSelectedIds;
// load current ids of m:n joined entities from db:
// create new boolean expression out of member-expression for Where()
// see: http://stackoverflow.com/questions/5094489/how-do-i-dynamically-create-an-expressionfuncmyclass-bool-predicate-from-ex
ParameterExpression parameterExpression = Expression.Parameter(typeof (T), "j");
var propertyName = ((MemberExpression) ThisJoinIdColumn.Body).Member.Name;
Expression propertyExpression = Expression.Property(parameterExpression, propertyName);
var value = Expression.Constant(ThisEntityId);
Expression equalExpression = Expression.Equal(propertyExpression, value);
Expression<Func<T, bool>> thisJoinIdBooleanExpression =
Expression.Lambda<Func<T, bool>>(equalExpression, parameterExpression);
var joinedDbIds = JoinDbSet
.Where(thisJoinIdBooleanExpression)
.Select(OtherJoinIdColumn).ToArray();
// check if ids previously (GET) and currently (POST) loaded from the db are still the same
if (previousSelectedIds == null)
{
if (joinedDbIds.Length > 0) return false;
}
else
{
if (joinedDbIds.Length != previousSelectedIds.Length) return false;
if (joinedDbIds.Except(previousSelectedIds).Any()) return false;
if (previousSelectedIds.Except(joinedDbIds).Any()) return false;
}
// create properties to use as setters:
var thisJoinIdProperty = (PropertyInfo) ((MemberExpression) ThisJoinIdColumn.Body).Member;
var otherJoinIdProperty = (PropertyInfo) ((MemberExpression) OtherJoinIdColumn.Body).Member;
// remove:
if (joinedDbIds.Length > 0)
{
DbContext.RemoveRange(joinedDbIds.Except(SimpleJoinContainer.SelectedIds).Select(id =>
{
var e = new T();
thisJoinIdProperty.SetValue(e, ThisEntityId);
otherJoinIdProperty.SetValue(e, id);
return e;
}));
}
// add:
if (SimpleJoinContainer.SelectedIds?.Length > 0)
{
var toAddIds = SimpleJoinContainer.SelectedIds.Except(joinedDbIds).ToList();
if (toAddIds.Count > 0)
{
DbContext.AddRange(SimpleJoinContainer.SelectedIds.Except(joinedDbIds).Select(id =>
{
var e = new T();
thisJoinIdProperty.SetValue(e, ThisEntityId);
otherJoinIdProperty.SetValue(e, id);
return e;
}));
}
}
return true;
}
}
In the Post action I call this class/method:
var flightPassengersUpdater = new SimpleJoinUpdater<FlightPassenger>(
DbContext,
DbContext.FlightPassengers,
mm => mm.FlightBookingId,
mm => mm.CustomerId,
model.Id, // model = current flightBooking object
viewModel.PassengersJoinContainer);
if (!flightPassengersUpdater .Update())
{
ModelState.AddModelError("PassengersJoinContainer", "Since you opened this form the data has already been altered by someone else. ...");
}

Automapping an object with a child collection NHibernate

I just recently switched from EF5 to NHibernate due to a few features that I want in my ORM but didn't find in EF. So, I'm new to NHibernate. I'm working in ASP.Net MVC.
I'm using Automapper to map FNH objects to my view models, but I'm having issues translating previously how I did things in EF to FNH. For example, I have a self referencing table that is a menu system.
Here is the model:
public partial class Menu {
private int _Id;
private string _Title;
private string _Link;
private int _SortOrder;
private System.Nullable<int> _ParentMenuId;
private Iesi.Collections.ISet _ChildMenus;
private Menu _ParentMenu;
#region Extensibility Method Definitions
partial void OnCreated();
#endregion
public Menu()
{
this._ChildMenus = new Iesi.Collections.HashedSet();
OnCreated();
}
/// <summary>
/// There are no comments for Id in the schema.
/// </summary>
public virtual int Id
{
get
{
return this._Id;
}
set
{
this._Id = value;
}
}
/// <summary>
/// There are no comments for Title in the schema.
/// </summary>
public virtual string Title
{
get
{
return this._Title;
}
set
{
this._Title = value;
}
}
/// <summary>
/// There are no comments for Link in the schema.
/// </summary>
public virtual string Link
{
get
{
return this._Link;
}
set
{
this._Link = value;
}
}
/// <summary>
/// There are no comments for SortOrder in the schema.
/// </summary>
public virtual int SortOrder
{
get
{
return this._SortOrder;
}
set
{
this._SortOrder = value;
}
}
/// <summary>
/// There are no comments for ParentMenuId in the schema.
/// </summary>
public virtual System.Nullable<int> ParentMenuId
{
get
{
return this._ParentMenuId;
}
set
{
this._ParentMenuId = value;
}
}
/// <summary>
/// There are no comments for ChildMenus in the schema.
/// </summary>
public virtual Iesi.Collections.ISet ChildMenus
{
get
{
return this._ChildMenus;
}
set
{
this._ChildMenus = value;
}
}
/// <summary>
/// There are no comments for ParentMenu in the schema.
/// </summary>
public virtual Menu ParentMenu
{
get
{
return this._ParentMenu;
}
set
{
this._ParentMenu = value;
}
}
}
Here is the Mapping:
public class MenuMap : ClassMap<Menu>
{
public MenuMap()
{
Schema(#"dbo");
Table(#"Menus");
LazyLoad();
Id(x => x.Id)
.Column("Id")
.CustomType("Int32")
.Access.Property()
.CustomSqlType("int")
.Not.Nullable()
.Precision(10)
.GeneratedBy.Identity();
Map(x => x.Title)
.Column("Title")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("varchar");
Map(x => x.Link)
.Column("Link")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("varchar")
.Not.Nullable()
.Length(50);
Map(x => x.SortOrder)
.Column("SortOrder")
.CustomType("Int32")
.Access.Property()
.Generated.Never()
.Not.Nullable()
.UniqueKey("KEY1");
Map(x => x.ParentMenuId)
.Column("ParentMenuId")
.CustomType("Int32")
.Access.Property()
.Generated.Never()
.UniqueKey("KEY1");
HasMany<Menu>(x => x.ChildMenus)
.Access.Property()
.AsSet()
.Cascade.None()
.LazyLoad()
.Inverse()
.Not.Generic()
.KeyColumns.Add("ParentMenuId", mapping => mapping.Name("ParentMenuId")
.SqlType("int")
.Nullable());
References(x => x.ParentMenu)
.Class<Menu>()
.Access.Property()
.Cascade.None()
.LazyLoad()
.Columns("ParentMenuId");
}
}
Here is my View Model or DTO:
public class MainMenuItemViewModel
{
public Int32 Id { get; set; }
public string Title { get; set; }
public string Link { get; set; }
public Int32 SortOrder { get; set; }
public Int32? ParentMenuId { get; set; }
public IList<MainMenuItemViewModel> ChildMenus { get; set; }
}
When I try to map the domain object to the view model, using this:
Mapper.CreateMap<Menu, MainMenuItemViewModel>();
I get the following error on I check if the configuration is valid on run:
The following property on WinStream.WebUI.Models.MainMenuItemViewModel cannot be mapped: ChildMenus
Add a custom mapping expression, ignore, add a custom resolver, or modify the destination type WinStream.WebUI.Models.MainMenuItemViewModel.
Context:
Mapping to property ChildMenus from System.Object to WinStream.WebUI.Models.MainMenuItemViewModel
Mapping to property ChildMenus from Iesi.Collections.ISet to System.Collections.Generic.IList`1[[WinStream.WebUI.Models.MainMenuItemViewModel, WinStream.WebUI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
Mapping from type WinStream.Services.Entities.Menu to WinStream.WebUI.Models.MainMenuItemViewModel
Exception of type 'AutoMapper.AutoMapperConfigurationException' was thrown.
I thought it might be related to converting ISet to IList, so I put in an ISet in my view model, but still had the issue.
Thank you for your help - I realize this could be a complete newbie question, but I couldn't find much help through Google. I've been struggling with this for several days now.
Thanks!
EDIT:
I have gotten past the error above, but now when I'm querying the database, the ChildMenus collection for the root object includes a null object for every child object in the database, including the associated child objects, instead of just the actual related child objects.
For example:
Root Menu
ChildMenus collection is supposed to have 3 child objects, but it has 8 (5 null and 3 populated)
List item
ChildMenus collection is supposed to have 1 child objects, but it has 8 (7 null and 1 populated)
List item
ChildMenus collection is supposed to have 0 child objects, and it has no child objects.
This is the code:
IList<Menu> menus = session.Query<Menu>().Where(x => x.ParentMenuId== null).ToList()
Any ideas on this, or do I need to put it into another question? Thank you!
NHibernate doesn't need a lot of the workarounds from EF. You basicly have a menu with ordered childmenues having a parent reference.
public class Menu
{
public int Id { get; protected set; }
public string Title { get; set; }
public string Link { get; set; }
public IList<Menu> ChildMenus { get; protected set; }
public Menu ParentMenu { get; set; }
public Menu()
{
ChildMenus = new List<Menu>();
}
}
public class MenuMap : ClassMap<Menu>
{
public MenuMap()
{
Table(#"Menus");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Title).Length(100);
Map(x => x.Link).Length(50);
HasMany<Menu>(x => x.ChildMenus)
.AsList("SortOrder")
.Inverse()
.KeyColumn("ParentMenuId");
References(x => x.ParentMenu).Column("ParentMenuId");
}
}
Notes:
Schema should be defined by convention or as default schema/catalog in the Configuration object
remove all unnessesary declarations from the mapping because it often introduces portability issues (eg. customsqltypes), complicates the code and prevents conventions
customsqltype() renders length() useless
Sortorder is not really needed because the list already defines the order
parentId is duplicate for Parent.Id and can be implemented if needed ParentId { get { return ParentMenu == null ? null : (int?)ParentMenu.Id } }, no need to map or store it in field
if the parentreference is not needed remove it and .Inverse() from the collection mapping

Creating a dropdownlist from your controller or view model

How would I create a SelectList in my controller and pass it to my view? I need to give the "-- Select --" option a value of 0.
I'm responding to the replies that I got from Jeremey of Fluent Validation.
This is what I currently have. My view model:
[Validator(typeof(CreateCategoryViewModelValidator))]
public class CreateCategoryViewModel
{
public CreateCategoryViewModel()
{
IsActive = true;
}
public string Name { get; set; }
public string Description { get; set; }
public string MetaKeywords { get; set; }
public string MetaDescription { get; set; }
public bool IsActive { get; set; }
public IList<Category> ParentCategories { get; set; }
public int ParentCategoryId { get; set; }
}
My controller.
public ActionResult Create()
{
List<Category> parentCategoriesList = categoryService.GetParentCategories();
CreateCategoryViewModel createCategoryViewModel = new CreateCategoryViewModel
{
ParentCategories = parentCategoriesList
};
return View(createCategoryViewModel);
}
This is what I have in my view:
#Html.DropDownListFor(x => x.ParentCategoryId, new SelectList(Model.ParentCategories, "Id", "Name", Model.ParentCategoryId), "-- Select --")
How do I create a dropdown list in the controller or view model and pass it to the view? I need the "-- Select --" option to have a value of 0.
In your model, change the IList<Category> to SelectList and then instantiate it like this...
List<ParentCategory> parentCategories = categoryService.GetParentCategories();
parentCategories.Insert(0, new ParentCategory(){ Id = "0", Name = "--Select--"});
ParentCategories = new SelectList(parentCategories, "Id", "Name");
Then in your view you can simply call
#Html.DropDownListFor(m => m.ParentCategoryId, Model.ParentCategories);
One way I've seen it done is to create an object to wrap the id and value of the drop down item, like a List<SelectValue>, and pass it in your ViewModel to the view and then use an HTML helper to construct the dropdown.
public class SelectValue
{
/// <summary>
/// Id of the dropdown value
/// </summary>
public int Id { get; set; }
/// <summary>
/// Display string for the Dropdown
/// </summary>
public string DropdownValue { get; set; }
}
Here is the view model:
public class TestViewModel
{
public List<SelectValue> DropDownValues {get; set;}
}
Here is the HTML Helper:
public static SelectList CreateSelectListWithSelectOption(this HtmlHelper helper, List<SelectValue> options, string selectedValue)
{
var values = (from option in options
select new { Id = option.Id.ToString(), Value = option.DropdownValue }).ToList();
values.Insert(0, new { Id = 0, Value = "--Select--" });
return new SelectList(values, "Id", "Value", selectedValue);
}
Then in your view you call the helper:
#Html.DropDownList("DropDownListName", Html.CreateSelectListWithSelect(Model.DropDownValues, "--Select--"))

How do I send two lists as a model in MVC

I'm doing a small ASP.NET MVC 3 test-app with a bookshelf where I can list "Books", and loan and return these to/from "Loaners".
I want to show both my Books and my Loaners on one view, and have therefor created a ViewModel called BooksViewModel but I cannot figure out why i can't list my "Books". I can do "foreach(var item in Model)" and only touch "Books" inside it, none of the Books properties.
My Model
namespace MyTest.Models
{
/// <summary>
/// Represents a book
/// </summary>
public class Book
{
public int ID { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string ISBN { get; set; }
public virtual Loaner LoanedTo { get; set; }
}
/// <summary>
/// Represents a Loaner
/// </summary>
public class Loaner
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Book> Loans { get; set; }
}
/// <summary>
/// Bookshelf Database Context
/// </summary>
public class BookshelfDb : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Loaner> Loaner { get; set; }
}
}
My Controller:
BookshelfDb bookshelf = new BookshelfDb();
public ActionResult Index()
{
var books = from book in bookshelf.Books
select book;
var loaners = from loaner in bookshelf.Loaner
select loaner;
var bookViewModel = new BooksViewModel(books.ToList(),loaners.ToList());
return View(bookViewModel);
}
My ViewModel
public class BooksViewModel
{
public BooksViewModel(List<Book> books, List<Loaner> loaners)
{
this.Books = books;
this.Loaners = loaners;
}
public List<Book> Books { get; private set; }
public List<Loaner> Loaners { get; private set; }
}
My View
#model IEnumerable<MyTest.ViewModels.BooksViewModel>
#foreach (var item in Model.Books)
{
#item.Title
}
Any hints or code fixes would be much appreciated!
Looks to me like you got the type of the model wrong in the view declaration. Try:
#model MyTest.ViewModels.BooksViewModel
#foreach (var item in Model.Books)
{
#item.Title
}

Resources