Recursion in an ASP.NET MVC view - asp.net-mvc

I have a nested data object for a set of items within categories. Each category can contain sub categories and there is no set limit to the depth of sub categories. (A file system would have a similar structure.) It looks something like this:
class category
{
public int id;
public string name;
public IQueryable<category> categories;
public IQueryable<item> items;
}
class item
{
public int id;
public string name;
}
I am passing a list of categories to my view as IQueryable<category>. I want to output the categories as a set of nested unordered list (<ul>) blocks. I could nest foreach loops, but then the depth of sub categories would be limited by the number of nested foreach blocks. In WinForms, I have done similar processing using recursion to populate a TreeView, but I haven't seen any examples of using recursion within an ASPX MVC view.
Can recursion be done within an ASPX view? Are there other view engines that include recursion for view output?

Create your own HtmlHelper extension method like so:
namespace System.Web.Mvc
{
public static class HtmlHelperExtensions
{
public static string CategoryTree(this HtmlHelper html, IEnumerable<Category> categories)
{
string htmlOutput = string.Empty;
if (categories.Count() > 0)
{
htmlOutput += "<ul>";
foreach (Category category in Categories)
{
htmlOutput += "<li>";
htmlOutput += category.Name;
htmlOutput += html.CategoryTree(category.Categories);
htmlOutput += "</li>";
}
htmlOutput += "</ul>";
}
return htmlOutput;
}
}
}
Funny you should ask because I actually created one of these just yesterday.

You could easily do it by having each <ul> list in a PartialView, and for each new list you need to start you just call Html.RenderPartial("myPartialName");.
So the Category PartialView could look like this:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Category>>" %>
<% foreach(Category cat in ViewData.Model) { %>
<li><p><%= cat.name %></p>
<% if (cat.categories.Count > 0) {
Html.RenderPartial("Category", cat.Categories);
} %></li>
<% } %>
In your View, you simply send the "root" collection as the model for the partial view:
<% Html.RenderPartial("Category", ViewData.Model) %>
EDIT:
I had forgotten the second parameter to the Html.RenderPartial() call - of course the category has to be passed as the model.
Of course you are right about the DRY mistake I made - I have updated my code accordingly.

You can use helper methods.
#model Models.CategoryModel
#helper TreeView(List<Models.CategoryModel> categoryTree)
{
foreach (var item in categoryTree)
{
<li>
#if (item.HasChild)
{
<span>#item.CategoryName</span>
<ul>
#TreeView(item.ChildCategories)
</ul>
}
else
{
<span class="leaf #item.CategoryTreeNodeType.ToString()" id="#item._CategoryId">#item.CategoryName</span>
}
</li>
}
}
<ul id="categorytree">
<li>#Model.CategoryName
#TreeView(Model.ChildCategories)
</li>
</ul>
More info can be found on this link:
http://weblogs.asp.net/scottgu/archive/2011/05/12/asp-net-mvc-3-and-the-helper-syntax-within-razor.aspx

You can reuse html parts with lambdas
Example
public class Category
{
public int id;
public string name;
public IEnumerable categories;
}
<%
Action<IEnumerable<Category>> categoriesMacros = null;
categoriesMacros = categories => { %>
<ul>
<% foreach(var c in categories) { %>
<li> <%= Html.Encode(c.name)%> </li>
<% if (c.categories != null && c.categories.Count() > 0) categoriesMacros(c.categories); %>
<% } %>
</ul>
<% }; %>
<% var categpries = (IEnumerable<Category>)ViewData["categories"]; %>
<% categoriesMacros(categpries); %>

Related

Error when retrieving records

I'm a beginner in ASP.NET MVC. I'm getting a error while trying to retrieve records from the database.
'System.Collections.Generic.List'
does not contain a definition for 'CategoryName'
LINQ to SQL Class:
Model:
namespace MvcApplication1.Models
{
public class CategoryRepository
{
private BusDataClassesDataContext dc = new BusDataClassesDataContext();
public List<tblCategory> GetAllCategory()
{
return dc.tblCategories.ToList();
}
}
}
Controller:
public class CategoryController : Controller
{
//
// GET: /Category/
CategoryRepository cat = new CategoryRepository();
public ActionResult ViewCategory()
{
var category = cat.GetAllCategory().ToList();
return View("ViewCategory", category);
}
}
View:
<p>
Category Name:<%=Html.Encode(Model.CategoryName)%>
</p>
<p>
Description:<%= Html.Encode(Model.Description)%>
</p>
UPDATE:
You are passing List<tblCategory> to the view. Hence the Model here will be the generic list. That is the reaons why you are getting the error message:-
'System.Collections.Generic.List' does not contain a definition for 'CategoryName'
Did you intend to pass tblCategory or else did you intend to iterate thought the model to get to each tblCategory?
You can do this way
<% foreach(var category in Model)
{%>
<p>
Category Name:<%=Html.Encode(category.CategoryName)%>
</p>
<p>
Description:<%= Html.Encode(category.Description)%>
</p>
<% } %>
On a little bit different note.
You are already returning the type as GenericList in your method.
public List<tblCategory> GetAllCategory()
{
return dc.tblCategories.ToList();
}
You don't need to again perform an ambiguous .ToList() conversion.
var category = cat.GetAllCategory().ToList();
Use this::
<% foreach (var category in Model)
ruther than ::
#foreach (var category in Model)
in the view.
Like This
<% foreach (var category in Model)
{ %>
<p>
Category Name :<%=Html.Encode(category.CategoryName)%></p>
<p>
Description :<%=Html.Encode(category.Description)%></p>
<% } %>

Replicated nested repeater behaviour in MVC

Today I decided to give MVC a go, and although I really like the idea, I found it fairly difficult to transition from ASP.NET and grasp some basic concepts, like using foreach instead of nested repeaters.
It took me good few hours to come up with this solution, but it doesn't seem quite right. Could someone please explain what's wrong with this code, and what the right way to do it is. Here is my solution:
Essentially it's a survey that consists of several questions, each of which has several answers. I have tables in db, which are represented as strongly typed entities. The controller looks like this:
public ActionResult Details(int id)
{
return View(new Models.Entities().Questions.Where(r => r.PROMId == id));
}
and corresponding view like this:
<% foreach (var question in Model) { %>
<h3>Question <%: Array.IndexOf(Model.ToArray(), question) + 1 %></h3>
<p><%: question.QuestionPart1 %></p>
<p><%: question.QuestionPart2 %></p>
<% var answers = new Surveys_MVC.Models.Entities().Answers.Where(r => r.QuestionId == question.QuestionId); %>
<% foreach (var answer in answers) { %>
<input type="radio" /><%: answer.Text %>
<% } %>
<% } %>
All feedback appreciated.
As far as using for loops for the nested repeater behavior, I think that's the best way to do this in MVC. But I would suggest you use dedicated ViewModels.
ViewModel:
public class RadioQuestionListViewModel
{
public IEnumerable<RadioQuestionViewModel> Questions {get;set;}
}
public class RadioQuestionViewModel
{
public int QuestionNumber {get;set;}
public string InputName {get;set;}
public string QuestionPart1 {get;set;}
public string QuestionPart2 {get;set;}
public IEnumerable<RadioAnswerViewModel> PossibleAnswers {get;set;}
}
public class RadioAnswerViewModel
{
public int AnswerId {get;set;}
public string Text {get;set;}
}
Controller:
public ActionResult Details(int id)
{
var model = GetRadioQuestionListModelById(id);
return View(model);
}
View:
<% foreach (var question in Model) { %>
<h3>Question <%: question.QuestionNumber %></h3>
<p><%: question.QuestionPart1 %></p>
<p><%: question.QuestionPart2 %></p>
<% foreach (var answer in question.PossibleAnswers) { %>
<%: Html.RadioButton(question.InputName, answer.AnswerId) %>
<%: answer.Text %>
<% } %>
<% } %>
This approach has a few advantages:
It prevents your view code from depending on your data access classes. The view code should only be responsible for deciding how the desired view model gets rendered to HTML.
It keeps non-display-related logic out of your view code. If you later decide to page your questions, and are now showing questions 11-20 instead of 1-whatever, you can use the exact same view, because the controller took care of figuring out the question numbers to display.
It makes it easier to avoid doing a Array.IndexOf(Model.ToArray(), question) and a database roundtrip inside a for loop, which can become pretty costly if you have more than a few questions on the page.
And of course your radio buttons need to have a input name and value associated with them, or you'll have no way to retrieve this information when the form is submitted. By making the controller decide how the input name gets generated, you make it more obvious how the Details method corresponds to your SaveAnswers method.
Here's a possible implementation of GetRadioQuestionListModelById:
public RadioQuestionListViewModel GetRadioQuestionListModelById(int id)
{
// Make sure my context gets disposed as soon as I'm done with it.
using(var context = new Models.Entities())
{
// Pull all the questions and answers out in a single round-trip
var questions = context.Questions
.Where(r => r.PROMId == id)
.Select(r => new RadioQuestionViewModel
{
QuestionPart1 = r.q.QuestionPart1,
QuestionPart2 = r.q.QuestionPart2,
PossibleAnswers = r.a.Select(
a => new RadioAnswerViewModel
{
AnswerId = a.AnswerId,
Text = a.Text
})
})
.ToList();
}
// Populate question number and name
for(int i = 0; i < questions.Count; i++)
{
var q = questions[i];
q.QuestionNumber = i;
q.InputName = "Question_" + i;
}
return new RadioQuestionListViewModel{Questions = questions};
}
I don't know if it is better, but you can create a helper to do this for you:
public static void Repeater<T>(this HtmlHelper html, IEnumerable<T> items, string cssClass, string altCssClass, string cssLast, Action<T, string> render)
{
if (items == null)
return;
var i = 0;
foreach (var item in items)
{
i++;
if (i == items.Count())
render(item, cssLast);
else
render(item, (i % 2 == 0) ? cssClass : altCssClass);
}
}
Then you can call it like so:
<%Html.Repeater(Model, "css", "altCss", "lastCss", (question, css) => { %>
<h3>Question <%: Array.IndexOf(Model.ToArray(), question) + 1 %></h3>
<p><%: question.QuestionPart1 %></p>
<p><%: question.QuestionPart2 %></p>
<% var answers = new Surveys_MVC.Models.Entities().Answers.Where(r => r.QuestionId == question.QuestionId); %>
<% foreach (var answer in answers) { %>
<input type="radio" /><%: answer.Text %>
<% } %>
<% }); %>
This has a lot of power and the above is just a general example. You can read more here http://haacked.com/archive/2008/05/03/code-based-repeater-for-asp.net-mvc.aspx

How to show nested data using MVC Views and PartialViews

The problem I am working on is very similar to the way StackOverflow is displaying Question, its comments, Posts and comments related to the Posts. The hierarchy is the same.
How is this accomplished in ASP.Net MVC?
So far I have this: (I have named the files similar to SO site to make my question more readable)
Views/Questions/Details.aspx
public class QuestionsController : Controller
{
public ActionResult Details(int? questionId)
{
Question question= _db.Question .First(i => i.QuestionId== questionId);
return View(question);
}
}
This loads the details and display the question.
I have a user control called QuestionComment, which should display the comments for the question but I am not sure how to go about wiring it up. I have been using the Dinners solution as a guide.
Create ViewModel for displaying Question with Comments. Something like this:
public class QuestionViewModel
{
public Question Question { get; set; }
public IEnumerable<Comment> Comments { get; set; }
}
your controller become:
public class QuestionsController : Controller
{
public ActionResult Details(int? questionId)
{
var question = _db.Question.First(x => x.QuestionId == questionId);
var comments = _db.Comment.Where(x => x.QuestionId == questionId).ToList();
var model = new QuestionViewModel {
Question = question,
Comments = comments
};
return View("Details", model);
}
}
your "Details" View:
<%# Page Inherits="System.Web.Mvc.ViewPage<QuestionViewModel>" %>
<% Html.Renderpartial("QuestionControl", model.Question); %>
<% Html.Renderpartial("CommentsControl", model.Comments); %>
"QuestionControl" partial View:
<%# Control Inherits="System.Web.Mvc.ViewUserControl<Question>" %>
<h3><%= Model.Title %></h3>
...
"CommentsControl" partial View:
<%# Control Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Comment>>" %>
<ul>
<% foreach (var comment in Model) { %>
<li>
<%= comment.Content %>
</li>
<% } %>
</ul>
...
In your view write something like this;
<% foreach (var question in Model.Questions) { %>
<%=question.bodyText%>
<%}%>
Hope this helps, if not post a comment and I'll be less cryptic.

Trouble passing ViewModel to Partial View

My ViewModel class (ItemViewModel.cs) looks like this:
public class ItemViewModel
{
public ItemViewModel(xxx.Product product)
{
this.product = product;
}
private readonly xxx.xxx.Product product;
private readonly Pers pers;
private readonly Item item;
public xxx.xxx.Product Product
{
get{ return product;}
}
public Item Item
{
get { return item; }
}
public ItemList Items
{
get { return product.Items; }
}
public Pers Pers
{
get { return pers; }
set { value = pers; }
}
public PersList PersList
{
get { return product.PersList; }
}
}
The view has this code defined in it (I took out some other case lines, just to show one of them as an example):
<%# Page Language="C#" MasterPageFile="~/Views/Shared/MasterPages/Item.Master" Inherits="System.Web.Mvc.ViewPage<xxx.ViewModels.ItemViewModel>" %>
<% foreach (Pers p in Model.Perslist)
{
switch(p.DispType)
{
case DisType.Dropdown:
Model.Pers = p;
Html.RenderPartial("~/Views/Shared/Controls/Custom/PForm/DropDown.ascx",*Model);
break;
}
}
%>
And the RenderPartial looks like this:
<fieldset>
<div class="xxx">
<span class="xxx">*</span><label><%=Model.Pers.Name %></label>
<p class="xxx"><%=Model.Pers.Info %></p>
</div>
<div class="formField"><% Html.DropDownList(Model.Pers.Name, new SelectList(Model.Items[0].DropdownItems));%></div>
</fieldset>
The problem or dilemma I'm having is I not only need the p from the foreach but the entire ItemViewModel instance that was originally passed to my View. Because I need to use the Pers in that foreach as well as be able to reference the Items. So what I tried is to set the Pers property of the ItemViewModel class instance to the current p in the foreach. Then tried to send the whole Model (now that I have that Pers set on the property) which is of type ItemViewModel so that I can now use the property Pers on that object and also still be able to reference the Items property that was populated already when it hit the View.
So when the page renders I get:
System.NullReferenceException: Object reference not set to an instance of an object.
for this line:
<span class="xxx">*</span><label><%=Model.Pers.Name %></label>
So far I'm unsuccesful because I still get a null reference error on the property Pers when I attempt to use the ITemViewModel in my Partial View.
This could be because your partial view file needs this line above your html:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<xxx.ViewModels.**ItemViewModel**>" %>
So this is how your partial view should look:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<xxx.ViewModels.**ItemViewModel**>" %>
<fieldset>
<div class="xxx">
<span class="xxx">*</span><label><%=Model.Pers.Name %></label>
<p class="xxx"><%=Model.Pers.Info %></p>
</div>
<div class="formField"><% Html.DropDownList(Model.Pers.Name, new SelectList(Model.Items[0].DropdownItems));%></div>
</fieldset>
Good God, I set the property wrong. resolved. duh.

How to bind nested array element property value to TextBox in ASP.NET MVC

I have model
public class User
{
public User()
{
Addreses = new List<Address>();
}
public String Name { get; set; }
public String LastName { get; set; }
public List<Address> Addresses { get; private set; }
}
public class Address
{
public String Street { get; set; }
public String City { get; set; }
}
And I want do display user addresses as ul list. I do this in view page
using (Html.BeginForm("UpdateUser", "Home", FormMethod.Post))
{
%>
<% =Html.TextBox("user.Name")%><br />
<% =Html.TextBox("user.LastName")%><br />
<ul>
<%
for (Int32 index = 0; index < ((User)ViewData["user"]).Addresses.Count; index++)
{%>
<li>
<% =Html.TextBox("user.Addresses[" + index + "].Street")%>,
<% =Html.TextBox("user.Addresses[" + index + "].PostalCode")%>,
<% =Html.TextBox("user.Addresses[" + index + "].City")%>
</li>
<%
}
%>
</ul>
<input type="submit" value="Submit" />
<% }%>
And data in textboxes populated in for statement are empty. Ofcourse I could add next parameter TextBox method to assign value, but two textboxes upper (for instance "user.Name") correctly read/set value.
What I'm doing wrong?
PS. I'm using MVC RTM 1.0
The Html.TextBox method need the name of the control and the value of the control
<ul>
<%foreach (var address in ((User)ViewData["user"]).Addresses){
<li>
<% =Html.TextBox("Street", address.Street)%>,
<% =Html.TextBox("PostalCode", address.PostalCode)%>,
<% =Html.TextBox("City", address.City)%>
</li>
<%}%>
</ul>
you can also try using the Html.TextBoxFor method in the MVC Futures project
In your example you are only setting the texbox's "name" and "id" attributes and not the value object as the second overload.
try this:
<% =Html.TextBox("user.Addresses[" + index + "].Street", user.Addresses[index].Street)%>

Resources