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.
Related
I very new to .NET and Entity Framework, and I have a problem with my code (below). I am getting the following error:
Unable to cast object of type '<>f__AnonymousType1`2[
SamWinInterface.Models.tbl_interface_category,
SamWinInterface.Models.tbl_interface_menu]' to type
'SamWinInterface.Models.tbl_interface_menu'.
This is my code:
public ActionResult Index(int id=-1)
{
ViewBag.Menus = from menu in _db.tbl_interface_menu
join cat in _db.tbl_interface_category on
menu.fld_category_id equals cat.id where
cat.fld_customer_id == id select new { cat, menu };
return View();
}
I'm trying to get menus depending on which category is chosen.
Something like:
<% foreach (tbl_interface_menu m in (IEnumerable)ViewBag.Menus)
{ %>
<%= m.fld_section2_title %>
<% } %>
but I'm getting the above error. How can I get the menus?
You cannot pass anonymous objects to views. This doesn't work because anonymous types are emitted as internal. And because ASP.NET views are compiled into a separate assembly at runtime they cannot access those times because they reside in a different assembly. This basically means that an anonymous object that you have defined in your controller action cannot be accessed in your view.
So as always in an ASP.NET MVC application start by defining view a model:
public class MyViewModel
{
public Category Category { get; set; }
public Menu Menu { get; set; }
}
then have your controller action fill this view model and pass it to the view:
public ActionResult Index(int id=-1)
{
var model =
from menu in _db.tbl_interface_menu
join cat in _db.tbl_interface_category
on menu.fld_category_id equals cat.id
where cat.fld_customer_id == id
select new MyViewModel { Category = cat, Menu = menu };
return View(model);
}
and finally have a strongly typed view:
<%# Page
Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<IEnumerable<AppName.Models.MyViewModel>>"
%>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% foreach (var item in Model) { %>
<%= item.Menu.fld_section2_title %>
<% } %>
</asp:Content>
As Darin said, you cannot pass anonymous types to views, but you could convert them to Expando objects, and that would prevent you from having to define viewmodels.
Personally I would probably just define viewmodels, but this option is handy in a pinch.
I'm building a questionnaire. The questionnaire have multiple sections, each section has multiple questions, and each question can have one to many answers. Each question can be a different type (radio buttons, checkbox, text...).
I put my tables in the model and loop through the sections table to display sections, loop through questions to display questions, loop through answerOptions to populate answers:
<fieldset>
<legend>Fields</legend>
<%foreach (var s in Model.Sections)
{ %>
<h3><%=s.SCTN_TXT %></h3>
<% var QuestsInSect = Model.GetQuestionsBySectionID(s.SCTN_ID);%>
<%foreach (var q in QuestsInSect){%>
<h4><%=q.QSTN_TXT %><%=q.QSTN_ID.ToString() %></h4>
<% var answers = Model.GetAnswerOptionByQuestionID(q.QSTN_ID); %>
<%if (q.QSTN_TYP_ID>= 3)
{%>
<%:Html.TextBox(q.QSTN_ID.ToString())%>
<%}
else if (q.QSTN_TYP_ID == 1)
{ %>
<%var answerOptions = Model.GetDropDownListAnswerOptionByQuestionID(q.QSTN_ID);%>
<%:Html.DropDownList(q.QSTN_ID.ToString(), answerOptions)%>
<%}
else
{ %>
<% foreach (var ao in answers)
{ %>
<br />
<%:Html.CheckBox(q.QSTN_ID.ToString())%>
<%=ao.ANS_VAL%>
<% }
}
}
} %>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
In my controller I loop through collection.Allkeys to figure out the answer for each question:
[HttpPost]
public ActionResult Create(FormCollection collection)
{
try
{
// TODO: Add insert logic here
List<ASSMNT_RESP> arList = new List<ASSMNT_RESP>();
foreach (string key in collection.AllKeys)
{
QSTN q = _model.GetQuestionByQuestionID(int.Parse(key));
IEnumerable<ANS_OPTN> aos = _model.GetAnswerOptionByQuestionID(int.Parse(key));
ASSMNT_RESP ar = new ASSMNT_RESP();
ar.QSTN_ID = int.Parse(key);
ar.ASSMNT_ID = 1;
if (q.QSTN_TYP_ID == 1)//dropdown
{
//do something
}
else if (q.QSTN_TYP_ID == 2)//checkboxlist
{
//do something
}
else
{
//do something
}
//_model.AddAssessmentResponse(ar);
System.Diagnostics.Trace.WriteLine(key + "---"+ collection[key]);
}
//_model.Save();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
It works, but I just don't think it's a very good design. It seems like I have too much logic in the view. I would like to move the logic in the view and controller to the model. Can you recommend an easier/cleaner way to do this?
Thanks.
I'm not an expert on MVC, but I think you'd get a lot of benefit out of using a strongly-typed view based on a custom model class that you build. The properties exposed by such a model can be nested (i.e., the top-level model can consist of properties each of which are also custom classes). Conceptually, something like:
public class MyTopLevelModel
{
public MySubModel1 SubModel1 { get; set; }
public MySubModel2 SubModel2 { get; set; }
}
public class MySubModel1
{
public string AProperty { get; set; }
public int AnotherProperty { get; set; }
}
You can include collections in the class definitions, too. And then you can decorate the individual properties with with attributes specifying whether a particular property is required, a range of permissible values, etc.
It's a big subject, though, and this only scratches the surface. FWIW, I've gotten a LOT out of Steven Sanderson's Pro ASP.NET MVC2 Framework book.
Make your viewmodel more explicit.
public class ViewModel
{
public IList<SectionViewModel> Sections {get;set;}
}
public class SectionViewModel
{
public IList<QuestionViewModel> Questions {get;set;}
}
public class QuestionViewModel
{
public IList<AnswerViewModel> Answers {get;set;}
}
In your view you can then do something like this (I'm using razor):
#foreach(var section in Model.Sections)
{
<h3>#section.Name</h3>
foreach(var question in section.Questions)
{
<h4>#question.Name</h4>
foreach(var question in section.Questions)
{
#Html.EditorFor(x=> question.Answers)
}
}
}
Then create an EditorTemplate for your AnswerViewModel.
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
I want to return to my view Product information(Name and Cost) and am running into the following error in my View code's foreach statement:
Cannot implicitly convert type 'ProductApp.Models.Product' to 'System.Collections.IEnumerable'. An explicit conversion exists (are you missing a cast?)
I seem to be missing something stupid simple. Can anyone help me correct this?
Repository Code:
public class ProductRepository
{
DatabaseEntities db = new DatabaseEntities();
public IQueryable<Product> FindAllProducts()
{
return db.Products;
}
public Product GetProductInfo(int id)
{
return db.Product.FirstOrDefault(p => p.Product_ID == id);
}
}
Controller Code:
public ActionResult Index(int id)
{
Product product = productRepository.GetProductInfo(id);
return View("Index", product);
}
View Code:
<ul>
<% foreach (var product in Model)
{ %>
<li>
<%: product.Product_Name%>
which costs
<%: product.Product_Cost%>
is available.
</li>
<% } %>
</ul>
In your view, look at the Model type. Your first line in the view will tell you what type the View expects:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<myApp.Model.Product>" %>
The Inherits portion should tell you what type you expect. In the above example it expects a single Product type. This needs to match the type of the object that you pass into the View() method in your controller.
Your view probably has an inherit type that is something like this"
...Inherits="System.Web.Mvc.ViewPage<List<myApp.Model.Product>>"...
So you should make sure to return the list of items from your Controller.
You're looping over a single instance of a Product. You're passing to the view what gets returned from GetProductInfo, which is not a collection of products.
Is is possible to apply an attribute to a collection, and then detect this when iterating through the collection members using ViewData.ModelMetadata.Properties ?
I would like to apply an attribute to the collection to specify whether items in the collection should be displayed in their entirety or not. I want to then detect this in the Object.ascx (that deals with the display of objects of unknown type) to decide what level of detail to display.
(Please see brad wilson's post for background on this generic templating approach)
For Example:
public class Parent
{
[SomeAttributeIWantToDetect]
public IList<Child> Children{ get; set; }
}
public class Child
{
public string Name { get; set; }
public string Details { get; set; }
}
object.ascx:
(Note, this code is from the ASP.NET MVC team, not mine)
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<% if (Model == null) { %>
<%= ViewData.ModelMetadata.NullDisplayText %>
<% } else { %>
<table cellpadding="0" cellspacing="0" border="0">
<% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForDisplay && !ViewData.TemplateInfo.Visited(pm))) { %>
<% if (prop.HideSurroundingHtml) { %>
<%= Html.Display(prop.PropertyName) %>
<% } else { %>
<tr>
<td>
<div class="display-label" style="text-align: right;">
<%= prop.GetDisplayName() %>
</div>
</td>
<td>
<div class="display-field">
<!-- *********** HERE ***************-->
<% if (prop.AdditionalValues.ContainsKey(SomeAttributeIWantToDetectAttribute))
//Do something else.....
else
%>
<%= Html.Display(prop.PropertyName) %>
<% } %>
</div>
</td>
</tr>
<% } %>
<% } %>
</table>
<% } %>
This is not MVC, it's closer to classic ASP templates
Choose which camp you want to be in, and stay there
To use MVC you need to make a ViewModel which expresses the Model in terms of a particular render destination
Your ViewModel should be built using just the logic commands from above. i.e.
if (Model == null)
{
x = ViewData.ModelMetadata.NullDisplayText
}
else
{
foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForDisplay && !ViewData.TemplateInfo.Visited(pm)))
{
if (prop.HideSurroundingHtml)
{
x.Items1.Add(prop.PropertyName));
}
else
{
prop.GetDisplayName()
if (prop.AdditionalValues.ContainsKey(SomeAttributeIWantToDetectAttribute))
{
x.Items2.Add( { Text = prop.zzz, Highlight = true} ));
}
else
{
x.Items2.Add( { Text = prop.PropertyName } ));
}
}
}
}
The code above is obviously wrong, I am just try to show that you should take the complex code and logic and use it to build a ViewModel, it should never be in the view
The ViewModel has a simple construct relating to the rendering technology you are using (like html attributes etc)
The view should just contain simple iterators and layout selectors, fed from the ViewModel, never the actual Model
#UpTheCreek, you're coming across as awfully confrontational for wanting people to help you, but I'll give my 2 cents anyway.
My understanding is that you want to be able to have a child collection on a model. On the child model you're going to be including some [ScaffoldColumn("false")] attributes, but you also want to be able to put an attribute on the parent model that would cause the renderer to ignore the ScaffoldColumn attributes and just show everything. If my understanding is correct, I think you are going about this the wrong way. You should be creating separate view models for cases where you want different properties to be shown.
Also, I'm note quite clear if this is your intent because in your code sample you would only be hiding the input field and not the label itself. Perhaps you are trying to put some sort of notice that the field is hidden?
You've linked http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html several times with the comment "I can't use ViewModels because this is generic templating".
I don't understand why you believe this. TFD and Ryan have it exactly right. Create two different ViewModels to wrap your model, and put the ScaffoldColumn attributes on your ViewModel (or better, omit those fields entirely).
Object.ascx then detects the attribute (or of course the presence or absence of the field) on your ViewModel and displays (or doesn't) the field appropriately.
In fact, the author of the post you linked suggests doing exactly that:-
http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html#comment-6a00e54fbd8c4988340120a6396c7a970b
"Personally, my recommendation for
people who want strict SoC (like I do)
is to use ViewModels and only place
the annotations on the view model.
There are other issues with directly
model binding to things like LINQ to
SQL or LINQ to Entities (for example,
if you're not careful, you can destroy
your associations or inadvertently let
a bad guy bind data into something
that wasn't originally shown in the
editor), so I generally always
recommend view models anyway."
So:-
public class Parent
{
public IList<Child> Children{ get; set; }
}
public class Child
{
public String Name { get; set; }
public String Details { get; set; }
}
// Pass this one to your "Admin" view.
public class ParentAdminViewModel
{
private Parent _parent;
public ParentAdminViewModel(Parent parent) { this._parent = parent; }
public IEnumerable<Child> Children
{
get
{
return _parent.Children.Select(x => new ChildAdminViewModel(x));
}
}
}
public class ChildAdminViewModel
{
private Child _child;
public ChildAdminViewModel(Child child) { this._child = child; }
public String Name { get { return _child.Name; } }
public String Details { get { return _child.Details; } }
}
// Pass this one to your "User" view.
public class ParentUserViewModel
{
private Parent _parent;
public ParentUserViewModel(Parent parent) { this._parent = parent; }
public IEnumerable<Child> Children
{
get
{
return _parent.Children.Select(x => new ChildUserViewModel(x));
}
}
}
public class ChildUserViewModel
{
private Child _child;
public ChildAdminViewModel(Child child) { this._child = child; }
public String Name { get { return _child.Name; } }
// ChildUserViewModel doesn't have a Details property,
// so Object.ascx won't render a field for it.
}
Obviously you'll need to wire up setters as well if you want to edit.
Could you not use reflection like so: http://msdn.microsoft.com/en-us/library/z919e8tw.aspx ?