I have categories table, see its design:
And I want to display this nested categories inside dropdown list like this image:
Can anybody help me to find a solution?
You should retrieve all your categories, ordered by ParentId and then by Name if you need that. You should do this either in your controller or better trough a Service layer that talks to your repository or EntityFramework Datacontext. You didn't specify your data access strategy.
Then in your controller, you'll have to iterate over all categories and create each item taking into account the parent relationship. For example, if you iterate over "top categories" you could then add all child categories of the current category. If you have more than 1 level nesting, you would have to use recursion.
Pseudo-code written directly here, probably won't compile as-is:
//Assume you want to produce a SelectList to use in your View, let's use a view model like the following
public class CategoryViewModelItem
{
public string Value {get;set;}
public string Text {get;set;}
}
In your controller/service layer:
List<CategoryViewModelItem> items = new List<CategoryViewModelItem>();
//get all of them from DB
List<Category> allCategories = dataContext.Categories.ToList();
//get parent categories
List<Category> parentCategories = allCategories.Where(c => c.ParentId == null)
.OrderBy(c => c.Title);
foreach(var cat in parentCategories)
{
//add the parent category to the item list
items.Add(new CategoryViewModelItem { Value = cat.Id, Text = cat.Title });
//now get all its children (separate function in case you need recursion)
GetSubTree(allCategories, cat, items);
}
private void GetSubTree(IList<Category> allCats, Category parent, IList<CategoryViewModelItem> items)
{
var subCats = allCats.Where(c => c.ParentId == parentId);
foreach(var cat in subCats)
{
//add this category
items.Add(new CategoryViewModelItem { Value = cat.Id, Text = parent.Title + " >> " + cat.Title });
//recursive call in case your have a hierarchy more than 1 level deep
GetSubTree(allCats, cat, items);
}
}
Then to render your SelectList, you could send the SelectList as your model (or part of it) for your view:
//in your controller's action
SelectList select = new SelectList(items, "Value", "Text");
return View(select);
And use that in your View:
#Html.DropDownList("Categories", #Model)
Related
This question already has answers here:
Dynamic Anonymous type in Razor causes RuntimeBinderException
(12 answers)
Closed 6 years ago.
I'm trying to pass dynamic results to View from Controller, method ShowColor returns dynamic results. In View I try to loop through the collection but I'm getting error
'object' does not contain a definition for 'ColorID'.
I have the following code in Controller and View
public class myColor
{
public int ID { get; set; }
public string Name { get; set; }
public string Like { get; set; }
}
public dynamic ShowColor()
{
IList<myColor> color = new List<myColor>();
color.Add(new myColor { ID = 1, Name = "Red", Like = "***" });
color.Add(new myColor { ID = 2, Name = "Green", Like = "*****" });
color.Add(new myColor { ID = 3, Name = "Blue", Like = "**" });
color.Add(new myColor { ID = 4, Name = "Yellow", Like = "*" });
var select = (from c in color
select new
{
ColorID = c.ID,
ColorName = c.Name
}).ToList();
return select;
}
public ActionResult DBDynamic()
{
return View(ShowColor());
}
View
#model dynamic
#{
ViewBag.Title = "DBDynamic";
}
<h2>DBDynamic</h2>
<p>
<ul>
#foreach (var m in Model)
{
<li> #m.ColorID</li>
}
</ul>
</p>
Found the solution here and a nice blog here:
public static ExpandoObject ToExpando(this object anonymousObject)
{
IDictionary<string, object> expando = new ExpandoObject();
foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(anonymousObject))
{
var obj = propertyDescriptor.GetValue(anonymousObject);
expando.Add(propertyDescriptor.Name, obj);
}
return (ExpandoObject)expando;
}
And call it like this
var select = (from c in color
select new
{
ColorID = c.ID,
ColorName = c.Name
})
.AsEnumerable()
.Select(x => x.ToExpando());
return View(select);
An anonymous object is not the same thing as a dynamic. If you want to use it like a dynamic then cast it to that:
#foreach (dynamic m in Model)
However, dynamics are best avoided if at all possible. You lose all compile-time checking and even intellisense. You won't know if you fat-fingered a property name until runtime or even if you've accidentally used the wrong type of thing the wrong way until runtime. If something is broken, you want to know about it at compile-time, not when it's already live and affecting users, when you may not even know that an error has occurred unless a user notifies you. That's a horrible situation for your app to be in.
Long and short, use strong types. If you want something with properties, ColorID and ColorName, create a view model with those properties and select your query into instances of that type. Then, everything will be nice and strongly-typed and you'll know well in advance if there's some error or problem with your code.
I have ASP.NET MVC 4 application with one view model class and about 20 views representing this view model. This views differs only by fields which user can edit. I want to merge all that views to one and define list of properties available to editing in strongly-typed manner. Ideally, I want something like this:
// Action
public ActionResult EditAsEngineer(int id)
{
//...
viewModel.PropertiesToChange = new List<???>()
{
v => v.LotNumber,
v => v.ShippingDate,
v => v.Commentary
};
return View(viewModel);
}
// View
if (#Model.PropertiesToChange.Contains(v => v.LotNumber)
{
#Html.TextBoxFor(m => m.LotNumber)
}
else
{
#Model.LotNumber
}
Is it possible to do something like this? Or is there a better solution?
Thank you.
Why note something like this (its pseudo code)
public class Prop{
string PropertyName {get;set;}
bool PropertyEditable {get;set;}
}
public ActionResult EditAsEngineer(int id)
{
viewModel.PropertiesToChange = new List<Prop>()
{
new Prop{PropertyName = LotNumber, PropertyEditable = true}
};
return View(viewModel);
}
#foreach (var pin Model.PropertiesToChange)
{
if(p.PropertyEditable){
#Html.TextBoxFor(p)
}else{
#Html.DisplayFor(p)
}
}
This will solve HALF of your problem. You will also need to create a IEqualityComparer<Expression> for your code to work (the default is to check for ref-equals).
return from p in typeof(T).GetProperties()
let param = System.Linq.Expressions.Expression.Parameter(typeof(T), "x")
let propExp = System.Linq.Expressions.Expression.Property(param, p)
let cast = System.Linq.Expressions.Expression.Convert(propExp, typeof(object))
let displayAttribute = p.CustomAttributes.OfType<System.ComponentModel.DataAnnotations.DisplayAttribute>()
.Select(x => x.Order).DefaultIfEmpty(int.MaxValue).FirstOrDefault()
orderby displayAttribute
select System.Linq.Expressions.Expression.Lambda<Func<T, object>>(cast, new [] {param});
This will list out ALL the properties for T. You would also probabily want to use Expression<Func<T, object>> as the type for defining your list of properties.
This will allow you to create a generic view over all properties.
Also you will want to wrap this in some kind of a cache, as this code is SLOW.
Hi I am trying to create a dynamic menu an I seem to have gotten into some trouble.
The problem is that when run the application only the items belonging to the last category get displayed repeatedly for all categories.
There are two places where I might have made the mistake but I can not be sure wicth is the place I made it I will also post a diagram of the two tables I am working with.This is the diagram:
I am using LINQ TO SQL to acces the database and have created a separate class to do this.Here is the code for accesing the data.This is the first place where I might have made the mistake when adding the items to the Dictionary aldo I stepped over this method with the debugger and it seems to be ok I can not be sure so I posted it:
public Dictionary<string , List<string>> subCatByCatList() {
Dictionary<string , List<string>> SubcatByCat = new Dictionary<string , List<string>>();
var subcategoriesByCategory = from category in dataContext.Categories
join subcategory in dataContext.SubCategories
on category.CatId equals subcategory.CatId
into cs
select new {
CategoryName = category.CatName ,
SubCategories = cs
};
List<string> subcategories = new List<string>();
foreach( var category in subcategoriesByCategory ) {
string CategoryName = category.CategoryName;
subcategories.Clear();
foreach( var subCategory in category.SubCategories ) {
subcategories.Add(subCategory.SubCatName);
}
SubcatByCat.Add(CategoryName , subcategories);
}
return SubcatByCat;
}
THe seccond place is when I am trying to display the data.This is where I most likely think I made the mistake when I tryed to display it.Here is the code:
#{
ComputerStoreDataAccess data = new ComputerStoreDataAccess();
Dictionary<string, List<string>> nav = data.subCatByCatList();
<ul>
#foreach (var category in nav)
{
<li>#category.Key</li>
foreach (var subcategory in category.Value)
{
<div>
<ul>
<li>#subcategory</li>
</ul>
</div>
}
}
</ul>
}
EDIT
In this loop you have to create the subcategories object on every iteration, otherwise you have reference to single list in a SubcatByCat:
foreach( var category in subcategoriesByCategory ) {
string CategoryName = category.CategoryName;
subcategories = new List<string>(); // <-- create new object!
foreach( var subCategory in category.SubCategories ) {
subcategories.Add(subCategory.SubCatName);
}
SubcatByCat.Add(CategoryName , subcategories);
}
I have a create method in my controller and this opens up a view. On the view the user can enter data to populate the model. Some of that data comes from select lists. These select lists are populated from the database.
What I would like to know is should I:
a) Get data for the select lists in the controller, populate a field like this:
public IEnumerable<SelectListItem> Statuses { get { return GetStatusType(); } }
pass Statuses to the model and then do a for () to loop through statuses and create a select list and options HTML
b) Do nothing in the controller and in the view have the following in the model:
<select id="Q_StatusID" name="Q.StatusID">#Html.Raw(
SelectHelper.Status(false, #Model.PageMeta.StatusID))</select>
Where the SelectHelper is C# code that gets all the select list and options HTML.
c) Some better way:
I would go with the first one. some thing like this
a helper method
public List<SelectListItem> getAllSelectList(List<Items> lists)
{
List<SelectListItem> selectListItems = new List<SelectListItem>();
foreach (Term term in lists)
{
selectListItems.Add(new SelectListItem() { Text = term.yourselectfield, Value = term.your value });
}
return selectListItems;
}
on your controller
//assuming you GetStatusType() method will return list of objects
ViewData.selectlist=getAllSelectList(GetStatusType());
on your view, if you are using Razor
#Html.DropDownList("selectlist", null, "Choose")
or
<%: Html.DropDownList("selectlist", null, "Choose") %>
Create a static Look-up class & static method for your GetStatusType there.
Cache all the status types after first time loading from your database.
Call GetAllStatusType from the view to display.
I would create a view model, which has a IEnumerable< Statuses > property. Then in your view to display the select element :
#Html.DropDownListFor(model => model.PageMeta.StatusID, new SelectList(Model.Statuses, "Id", "Name"), "Some default which has no value")
Where Id and Name are set to the appropriate properties in your Statuses model.
I am creating a SelectList from a List(T) class to use to populate a HtmlHelper.DropDownList in an MVC view, setting the dataValueField and dataTextField in the Select List constructor. The text that should be displayed doesn't excatly match one of the properties on my class and still needs to be manipulated. Is there any way of doing this through the SelectList constructor? I realise I could do this by creating a struct or class to use as the IEnumerable input for the Select list but I'd prefer not if I don't need to.
My Controller code is currently:
var members = MemberService.GetAll();
this.ViewData["Members"] = new SelectList(members, "Id", "Name");
My View code is currently:
<%= Html.DropDownList("Members") %>
Instead of "Surname" as my display field I'd like to be able to preformat the field by combining (and formatting) two properties.
You can create loop through the members list, adding the values to a new list that you then create the SelectList from. Something like:
var members = MemberService.GetAll();
List<object> newList = new List<object>();
foreach(var member in members)
newList.Add( new {
Id = member.Id,
Name = member.Name + " " + member.Surname
} );
this.ViewData["Members"] = new SelectList(newList, "Id", "Name");
I can't remember if you can use an anonymous type like that or not, but if not you can create an small data class to use as an intermediary.
You can create an extension method and set whatever you want as the text. It could be something as
public static IList<SelectListItem> ToSelectList<T>(this IEnumerable<T> itemsToMap, Func<T, string> textProperty, Func<T, string> valueProperty, Predicate<T> isSelected)
{
var result = new List<SelectListItem>();
foreach (var item in itemsToMap)
{
result.Add(new SelectListItem
{
Value = valueProperty(item),
Text = textProperty(item),
Selected = isSelected(item)
});
}
return result;
}
Then you call this method as:
var membersSelectList = MemberService.GetAll().ToSelectList(m=>m.FirstName + " " + m.Surname, m.Id.ToString(), m=>m.Id < -1); //Any predicate you need here.
For those that use LINQ.
Instead of:
var members = MemberService.GetAll();
you can use:
ViewBag.MembersList = new SelectList(
from x in db.members where x.role = 5 select new {x.ID, x.Name, x.Surname, S_Name = x.Surname + " " + x.Name},
"ID", "S_Name");
and then use ViewBag.MemberList in your Razor code like
#Html.DropDownList("MembersList", String.Empty)