How to show nested data using MVC Views and PartialViews - asp.net-mvc

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.

Related

MVC Binding to checkbox

I have found so many questions about this, but none of them go over or seem to go over my scenario. I have a model:
public class CheckBoxModel
{
public int Id{ get; set; }
public bool IsSelected { get; set; }
}
In then try and bind my IsSelected bool to a checkbox like this:
<%= Html.CheckBox("IsSelectedCheck",Model.IsSelected)%>
I have lots of items on a page, they all have a checkbox next to them, all i am trying to do is pass back to the controller all the id's of the items and which ones have been selected.
At the moment, the value of IsSelected is always false. Should Html.CheckBox set the value of Model.IsSelected each time the user toggles the checkbox.
Thanks
Try like this:
<%= Html.CheckBoxFor(x => x.IsSelected) %>
Also if you want to pass along the id don't forget to do so:
<%= Html.HiddenFor(x => x.Id) %>
And if you had a collection of those:
public class MyViewModel
{
public CheckBoxModel[] CheckBoxes { get; set; }
}
you could:
<% for (var i = 0; i < Model.CheckBoxes.Length; i++) { %>
<div>
<%= Html.HiddenFor(x => x.CheckBoxes[i].Id) %>
<%= Html.CheckBoxFor(x => x.CheckBoxes[i].IsSelected) %>
</div>
<% } %>
which will successfully bind to:
[HttpPost]
public ActionResult MyAction(MyViewModel model)
{
// model.CheckBoxes will contain everything you need here
...
}
An alternative to Darin's fantastic answer
I definitely recommend following Darin's approach for returning classes which will be most of the time. This alternative is a 'quick' and dirty hack if all you need is the checked Ids:
<% foreach (var cb in Model.CheckBoxes) { %>
<div>
<input type="checkbox"
value="<%= cb.Id %>"
<%= cb.IsSelected ? "checked=\"checked\"" : "" %>
name="ids" />
</div>
<% } %>
Will bind to the int[] ids parameter in the following action:
[HttpPost]
public ActionResult MyAction(int[] ids)
{
// ids contains only those ids that were selected
...
}
The benefit is cleaner html as there is no hidden input.
The cost is writing more code in the view.
In MVC 4.0 (Razor 2.0) you can use the following syntax in your view:
<input type="checkbox" value="#cb.Id" checked="#cb.IsSelected" name="ids" />

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

Can 2 Linq queries be passed to 1 View?

I have a view that is outputting a list of questions(yesno and multiple choice). For the Multiple Choice questions, I want to also pass the possible multiple choice answers but I can not figure out how to pass 2 queries. Is there a way to do this or is it better handled another way?
Controller Code:
public ActionResult Test(int id)
{
var listofquestions = from m in db.vQuestionnaireQuestions
where m.Questionnaire_ID.Equals(id)
select m;
return View("Test", listofquestions.ToList());
}
View Code:
<% foreach (var item in Model)
{ %>
<br />
<%: item.Question_Text %>
<%if (item.Question_Type_ID == 1) //Yes-No Question
{ %>
//Yes-No Stuffs
<% }
else if (item.Question_Type_ID == 2) //Multiple Choice
{ %>
//Can I access a Linq query again here?
//I have Question_ID to use, but I don't think
//I can have 2 Models
<% }
else //All Else
{ %>
//All Else Stuffs
<% }
} %>
EDIT
I've created a view model class
View Model Class Code:
public IEnumerable<vQuestionnaireQuestion> FindAllQuestionnaireQuestionsTest()
{
return db.vQuestionnaireQuestions;
}
public vQuestionnaireQuestion GetQuestionnaireQuestionsTest(int id)
{
return db.vQuestionnaireQuestions.FirstOrDefault(q => q.Questionnaire_ID == id);
}
public IEnumerable<Multiple_Choice_Answers> FindAllMultipleChoiceAnswersTest()
{
return db.Multiple_Choice_Answers;
}
public Multiple_Choice_Answers GetMultipleChoiceAnswersTest(int id)
{
return db.Multiple_Choice_Answers.FirstOrDefault(q => q.Question_ID == id);
}
and added it to the inherits of my view:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<QuestionnaireApp.Models.Questionnaire>>" %>
The model information does not seem to be making it as now all my item.fieldname's are coming back as not having a definition. Am I over complicating this?
Try wrapping the results in a view model class and pass that to View
class SomeClassName {
IEnumerable<Question> ListOfQuestions;
IEnumerable<Answer> ListOfAnswers;
}
Many people create a class specifically for the purpose fo passing data between controller and view. That way you can define whatever properties you need, and an instance of your class can easily have one or more LINQ result sets in it.

Asp.net mvc, view with multiple updatable parts - how?

I have started doing asp.net mvc programming and like it more everyday.
Most of the examples I have seen use separate views for viewing and editing details of a specific entity.
E.g. - table of music albums linking to separate 'detail' and 'update' views
[Action] | Title | Artist
detail, update | Uuuh Baby | Barry White
detail, update | Mr Mojo | Barry White
With mvc how can I achieve a design where the R and the U (CRUD) are represented in a single view, and furthermore where the user can edit separate parts of the view, thus limiting the amount of data the user can edit before saving?
Example mockup - editing album detials:
I have achieved such a design with ajax calls, but Im curious how to do this without ajax.
Parts of my own take on this can be seen below. I use a flag (enum EditCode)
indicating which part of the view, if any, that has to render a form. Is such a design in accordance with the framework, could it be done more elegantly?
AlbumController
public class AlbumController : Controller
{
public ActionResult Index()
{
var albumDetails = from ManageVM in state.AlbumState.ToList()
select ManageVM.Value.Detail;
return View(albumDetails);
}
public ActionResult Manage(int albumId, EditCode editCode)
{
(state.AlbumState[albumId] as ManageVM).EditCode = (EditCode)editCode;
ViewData["albumId"] = albumId;
return View(state.AlbumState[albumId]);
}
[HttpGet]
public ActionResult Edit(int albumId, EditCode editCode)
{
return RedirectToAction("Manage", new { albumId = albumId, editCode = editCode });
}
// edit album details
[HttpPost]
public ActionResult EditDetail(int albumId, Detail details)
{
(state.AlbumState[albumId] as ManageVM).Detail = details;
return RedirectToAction("Manage", new { albumId = albumId, editCode = EditCode.NoEdit });// zero being standard
}
// edit album thought
[HttpPost]
public ActionResult EditThoughts(int albumId, List<Thought> thoughts)
{
(state.AlbumState[albumId] as ManageVM).Thoughts = thoughts;
return RedirectToAction("Manage", new { albumId = albumId, editCode = EditCode.NoEdit });// zero being standard
}
Flag - EditCode
public enum EditCode
{
NoEdit,
Details,
Genres,
Thoughts
}
Mangae view
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcApplication1.Controllers.ManageVM>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Manage
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Manage</h2>
<% if(Model.EditCode == MvcApplication1.Controllers.EditCode.Details)
{%>
<% Html.RenderPartial("_EditDetails", Model.Detail); %>
<% }else{%>
<% Html.RenderPartial("_ShowDetails", Model.Detail); %>
<% } %>
<hr />
<% if(Model.EditCode == MvcApplication1.Controllers.EditCode.Thoughts)
{%>
<% Html.RenderPartial("_EditThoughts", Model.Thoughts); %>
<% }else{%>
<% Html.RenderPartial("_ShowThoughts", Model.Thoughts); %>
<% } %>
The last part feels messy to me. I would recommend wrapping those with Html Helpers to clean up your view.
<h2>Manage</h2>
<% Html.RenderDetailsPartial(Model.EditCode) %>
<hr />
<% Html.RenderThoughtsPartial(Model.EditCode) %>
Let the HTMLHelper determine which view to use based on the EditCode.

Recursion in an ASP.NET MVC view

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); %>

Resources