MVC3 Dropdown drillthrough hierarchy - asp.net-mvc-3

I have a table for product category that has a hierarchical structure. Each Category_ID may have a number of children determined by their Parent_id. For example Air Fresheners (26) has children 26, 27 and 28 as they have a Parent_id of 25.
I would like to set up a page to drill through the categories with dropdowns. A user would select a level 2 category such as Air Fresheners they would then get a dropdown containing children of the previous selection.
What is the best way to do this? I am considering jQuery and JSON, but there might be a better way.
I have the following GetCategoryChildren method:
public string ThisName { get; set; }
public int ThisHLevel { get; set; }
public IEnumerable<SelectListItem> Children { get; set; }
public GetCategoryChildren(int category_ID)
{
var rep = new Product_CategoryRepository();
Children = rep.All.Where(x => x.Parent_id == category_ID).ToList()
.Select(x => new SelectListItem
{
Text = x.Name,
Value = x.Category_ID.ToString()
});
ThisName = rep.All.Where(x => x.Category_ID == category_ID)
.FirstOrDefault().Name;
ThisHLevel = rep.All.Where(x => x.Category_ID == category_ID)
.FirstOrDefault().HLevel;
}
Lend me your brains.

Take a look at Project Awesome
The AjaxDropdown does the trick.

Related

not getting child category when returning parent category in linq entity framework

I want to return the parent category along with the subcategory. There is a self join in my table. This is my class:
public partial class CategoryMaster
{
public int Id { get; set; }
public string Name { get; set; }
public Nullable<int> ParentId { get; set; }//parentid is link to Id
public virtual ICollection<CategoryMaster> CategoryMaster1 { get; set; }
public virtual CategoryMaster CategoryMaster2 { get; set; }
}
I use this query to return the parent category and the child category:
var GetallCategories = context.CategoryMaster.Include("CategoryMaster1")
.Where(d => d.ParentId == null)
.OrderBy(d => d.Name)
.Select(d => new SelectListItem { Text = d.Name,
Value = d.Id.ToString()
})
.ToList();
The problem is that it is just returning parent category. Whats wrong with my query?
The first problem is your where clause. You told it to ONLY bring back CategoryMasters with a null parent. So you won't get any children out of the DB.
We have a more rigid (read: slower) pattern we follow where I work with our EF queries, so I've never done exactly what you're doing here, but I think all you have to do is move the where clause, like this:
var GetallCategories = context.CategoryMaster.Include("CategoryMaster1")
.OrderBy(d => d.Name)
.ToList();
.Where(d => d.ParentId == null)
.Select(d => new SelectListItem { Text = d.Name,
Value = d.Id.ToString()
})
The ToList forces a DB call and then the where and select after it get processed after it comes back from the DB, at which point, it's gotten all the data with no where clause. If I understand correctly, you do want all the data, you just want your list to contain only the parents at the top level and then drill down to get to the children.
So if you add any where clause before the tolist, it's going to restrict what comes back from the DB before it even tries to build the hierarchy of objects. If you look at the actual sql generated by EF, you can see this. This means that if you legitimately need a where clause - say you want to bring back all parents with a lastname of "Smith" plus their children - you have to make it more complex. If you need a meaningful where caluse to filter parents in the DB (which you might for performance) and you have to allow for an unknown number of generations of children, this becomes VERY difficult to do with EF includes.

MVC 3 dropdownfor populated but cannot get user's selection

I'm new to MVC 3. I want 3 dropdown lists. The user picks an item from each list and then retrieves the selections based on the matches. For my problem I'm just using 2 dropdowns and code snippets. I have all 3 dropdowns populated - the first two from Linq to Sql and the 3rd is an option list created with Razor (A-Z).
I'm missing the step of setting the values of what was chosen into MemberSetup.SelectedProgramID and MemberSetup.SelectedOrganizationID. I'd like the text stored in MemberSetup.SelectedProgramName and MemberSetup.SelectedOrganizationName respectively. I think if I can get the selected ids into the model, I can pass them through ActionLink and not deal with #Html.Hidden variables. However, no matter what is selection the model SelectedProgramID and SelectedOrganizationID are zero (expect 1, 2, 3 or 4 for Program ID and 1, 2, 5, or 6 for Organization ID).
Any help / corrections would be appreciated.
Model:
{
public int SelectedProgramID { get; set; }
public string SelectedProgramName {
get { return this._myProgramName; }
set { this._myProgramName = value; }
}
public IEnumerable<SelectListItem> ProgramList { get; set; }
public int SelectedOrganizationID { get; set; }
public string SelectedOrganizationName
{
get { return this._myOrganizationName; }
set { this._myOrganizationName = value; }
}
public IEnumerable<SelectListItem> OrganizationList { get; set; }
}
Controller:
{
var db = new STARDataContext();
MemberSetup setupModel = new MemberSetup();
setupModel.ProgramList = db.ProgramAlls.ToList()
.Select(p => new SelectListItem
{
Value = p.ProgramID.ToString(),
Text = p.ProgramName.ToString()
});
setupModel.OrganizationList = db.AreaAlls.ToList()
.Select(p => new SelectListItem
{
Value = p.AreaID.ToString(),
Text = p.AreaName.ToString()
});
return View(setupModel);
}
View:
{
#using ( Html.BeginForm() ) {
#Html.DropDownListFor(model => model.SelectedProgramID, Model.ProgramList)
#Html.DropDownListFor(model => model.SelectedOrganizationID, Model.OrganizationList, "Please select Organization", new { #class = "DropDownList" } )
#Html.Hidden("SelectedProgramID", Model.SelectedProgramID)
#Html.Hidden("SelectedProgramName", Model.SelectedProgramName)
#Html.Hidden("SelectedOrganizationID", Model.SelectedOrganizationID)
#Html.Hidden("SelectedOrganizationName", Model.SelectedOrganizationName)
#Html.ActionLink("Get Members", "Select", new {programID=Model.SelectedProgramID,orgID=Model.SelectedOrganizationID })
}
}
use hiddenFor instead of hidden,so your syntax should look like this
#Html.HiddenFor(a=>a.SelectedProgramName)
Since you have SelectedProgramID and SelectedOrganizationID defined, you won't need to use hidden field for it. To construct the dropdown, you should do something like this
Html.DropDownListFor(x=>x.ID,
new SelectList(Model.Products,"ID", "Sku", Model.ID), " select ")
For your reference: Use Html.DropDownListFor to get a selected value
The above class, controller and view are correct for presenting the drop-downs. This is why I was perplexed.
However, I had the SelectMember in the Get section. [HttpPost] and SelectMember can see what was selected.

implementing dropdownlist in asp.net mvc 3

I am teaching myself asp .net mvc3. I have researched a lot but the more I read the more confused I become. I want to create a page where users can register their property for sale or rent.
I have created a database which looks like this:
public class Property
{
public int PropertyId { get; set; }
public int PropertyType { get; set; }
ยทยทยท
public int Furnished { get; set; }
...
}
Now, I want dropdownlistfor = PropertyType and Furnished.
Property type would be
1 Flat
2 House
3 Detached House
...
Furnished would be:
1 Furnished
2 UnFurnished
3 PartFurnished
...
Now, I am really not sure where to keep this information in my code. Should I have 2 tables in my database which store this lookup? Or should I have 1 table which has all lookups? Or should I just keep this information in the model?
How will the model bind to PropertyType and Furnished in the Property entity?
Thanks!
By storing property types and furnished types in the database, you could enforce data integrity with a foreign key, rather than just storing an integer id, so I would definitely recommend this.
It also means it is future proofed for if you want to add new types. I know the values don't change often/will never change but if you wanted to add bungalow/maisonette in the future you don't have to rebuild and deploy your project, you can simply add a new row in the database.
In terms of how this would work, I'd recommend using a ViewModel that gets passed to the view, rather than passing the database model directly. That way you separate your database model from the view, and the view only sees what it needs to. It also means your drop down lists etc are strongly typed and are directly in your view model rather than just thrown into the ViewBag. Your view model could look like:
public class PropertyViewModel
{
public int PropertyId { get; set; }
public int PropertyType { get; set; }
public IEnumerable<SelectListItem> PropertyTypes { get; set; }
public int Furnished { get; set; }
public IEnumerable<SelectListItem> FurnishedTypes { get; set; }
}
So then your controller action would look like:
public class PropertiesController : Controller
{
[HttpGet]
public ViewResult Edit(int id)
{
Property property = db.Properties.Single(p => p.Id == id);
PropertyViewModel viewModel = new PropertyViewModel
{
PropertyId = property.Id,
PropertyType = property.PropertyType,
PropertyTypes = from p in db.PropertyTypes
orderby p.TypeName
select new SelectListItem
{
Text = p.TypeName,
Value = g.PropertyTypeId.ToString()
}
Furnished = property.Furnished,
FurnishedTypes = from p in db.FurnishedTypes
orderby p.TypeName
select new SelectListItem
{
Text = p.TypeName,
Value = g.FurnishedTypeId.ToString()
}
};
return View();
}
[HttpGet]
public ViewResult Edit(int id, PropertyViewModel propertyViewModel)
{
if(ModelState.IsValid)
{
// TODO: Store stuff in the database here
}
// TODO: Repopulate the view model drop lists here e.g.:
propertyViewModel.FurnishedTypes = from p in db.FurnishedTypes
orderby p.TypeName
select new SelectListItem
{
Text = p.TypeName,
Value = g.FurnishedTypeId.ToString()
};
return View(propertyViewModel);
}
}
And your view would have things like:
#Html.LabelFor(m => m.PropertyType)
#Html.DropDownListFor(m => m.PropertyType, Model.PropertyTypes)
I usually handle this sort of situation by using an enumeration in code:
public enum PropertyType {
Flat = 1,
House = 2,
Detached House = 3
}
Then in your view:
<select>
#foreach(var val in Enum.GetNames(typeof(PropertyType)){
<option>val</option>
}
</select>
You can set the id of the option equal to the value of each item in the enum, and pass it to the controller.
EDIT: To directly answer your questions:
You can store them as lookups in the db, but for small unlikely to change things, I usually just use an enum, and save a round trip.
Also look at this approach, as it looks better than mine:
Converting HTML.EditorFor into a drop down (html.dropdownfor?)

MVC 3 - validating values on multiple rows

I have an MVC project similar to this...
Model
Public Class ItemDetails
Public Property SerialNo As Integer
Public Property Description As String
Public Property GroupNo As Integer
Public Property Price As String
Public Property Quantity As Integer
End Class
Controller
Function ListItems() As ActionResult
' GetItems retrieves the items from the database
Dim i As List(Of ItemDetails) = ItemsRepository.GetItems
Return View(i)
End Function
View
#ModelType List(Of MyApp.ItemDetails)
#Using Html.BeginForm()
Dim RowNo As Integer
For i As Integer = 0 To Model.Count - 1
RowNo = i
#Html.HiddenFor(Function(model) model(RowNo).SerialNo)
#Html.LabelFor(Function(model) model(RowNo).Description)
#Html.HiddenFor(Function(model) model(RowNo).GroupNo)
#Html.LabelFor(Function(model) model(RowNo).Price)
#Html.TextBoxFor(Function(model) model(RowNo).Quantity)
Next
End Using
Note: This is done from memory so may not be completely accurate.
As you can see this displays a list of items. The items are retrieved from a database. Each item has a description and a Group No. The user can enter how many of each item they would like to order.
Two or more items can be in the same group. For examle there might be: Item 1 in Group 1, Item 2 in Group 1, Item 3 in Group 2 and Item 4 in Group 3.
When the user clicks submit I would like to validate that for each Group No the combined Quantity does not exceed 10. So in the example above if I enter a Quantity of 7 for Item 1 then quantity for Item 2 must be 3 or less.
I can validate this easily client side.
What is the best way to go about validating this server side (where and how)?
I need to total the combined value of Quantity does not exceed 10 for each Group No and if it does display an error.
Personally I use FluentValidation.NET for this kind of tasks. It allows you to define your complex validation rules for a given view model into a separate layer, express them in a fluent manner, it has an absolutely marvelous and seamless integration with ASP.NET MVC and allows you to unit test your validation logic in isolation.
If you don't want to use third party libraries you could always write a custom validation attribute and decorate your view model property with it. You could introduce a view model that will contain the collection of ItemDetails as a property and then decorate this property with your custom validator.
public class ItemDetails
{
public int SerialNo { get; set; }
public string Description { get; set; }
public int GroupNo { get; set; }
// Please take a note that I have allowed myself
// to use the decimal datatype for a property called
// Price as I was a bit shocked to see String in your code
public decimal Price { get; set; }
public int Quantity { get; set; }
}
public class MyViewModel
{
[EnsureMaxGroupItems(10, ErrorMessage = "You cannot have more than 10 items in each group")]
public IList<ItemDetails> Items { get; set; }
}
and the validation attribute itself:
[AttributeUsage(AttributeTargets.Property)]
public class EnsureMaxGroupItemsAttribute : ValidationAttribute
{
public int MaxItems { get; private set; }
public EnsureMaxGroupItemsAttribute(int maxItems)
{
MaxItems = maxItems;
}
public override bool IsValid(object value)
{
var items = value as IEnumerable<ItemDetails>;
if (items == null)
{
return true;
}
return items
.GroupBy(x => x.GroupNo)
.Select(g => g.Sum(x => x.Quantity))
.All(quantity => quantity <= MaxItems);
}
}
and finally your controller actions will work with the view model:
public ActionResult ListItems()
{
var model = new MyViewModel
{
Items = ItemsRepository.GetItems()
};
return View(model);
}
[HttpPost]
public ActionResult ListItems(MyViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
...
}
and next the corresponding strongly typed view:
#model MyViewModel
#Html.ValidationSummary()
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.Items)
<button type="submit">Go go go</button>
}
and the last bit is the corresponding editor template that will automatically be rendered for each element of the Items collection so that you don't even need to write for loops (~/Views/Shared/EditorTemplates/ItemDetails.cshtml):
#model ItemDetails
#Html.HiddenFor(x => x.SerialNo)
#Html.LabelFor(x => x.Description)
#Html.HiddenFor(x => x.GroupNo)
#Html.LabelFor(x => x.Price)
#Html.TextBoxFor(x => x.Quantity)

Counting in a Many-To-Many Relationship in Entity Framework

Using Entity Framework 4.1 Code First I have two objects with a many-to-many relationship:
public class Article
{
public int ID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public ICollection<Tag> Tags { get; set; }
}
public class Tag
{
[Key]
public string UrlSlug { get; set; }
public string Name { get; set; }
public ICollection<Article> Articles { get; set; }
}
I want to count the most common Tags applied to Articles. How do I do this in LINQ?
I tried the below code which only ordered the Tags:
var tagsFromDb = db.Tags
.GroupBy(q => q.UrlSlug)
.OrderByDescending(gp => gp.Count())
.Select(g => g.FirstOrDefault());
If I understand right you have a many-to-many relationship between articles and tags but the navigation property on the tag side is not exposed (there is no ICollection<Article> in Tag). I think in this case you have to start from the articles to get all used tags along with the information how often they are used in the articles:
var tagQuery = db.Articles
.SelectMany(a => a.Tags) // flat enumeration of all used tags
.GroupBy(t => t, (k, g) => new // k = key = Tag, g = group of same tags
{
Tag = k, // the tag
Count = g.Count() // how often does it occur in the articles
})
.OrderByDescending(g => g.Count); // most used tags first
If you want all tags sorted descending, call
var tagList = tagQuery.ToList();
If you just want the tag which is most often used in the articles, call
var mostUsedTag = tagQuery.FirstOrDefault();
In both cases the result is a collection/single object of an anonymous type which has the Tag and Count as members. If you just want the tag(s) and are not interested in the Count anymore you can project before apply ToList or FirstOrDefault: tagQuery.Select(anonymous => anonymous.Tag).....
Edit
Just saw the Edit in your question. Now, when you have an Article collection in the Tag entity it is easier:
var tagQuery = db.Tags
.Select(t => new
{
Tag = t, // the Tag
Count = t.Articles.Count() // just count the number of artices
// the tag is used in
})
.OrderByDescending(x => x.Count); // most used tags first

Resources