How to keep a collection of items for dropdown list in MVC model? - asp.net-mvc-3

to keep things simple, I have a model Survey with the following properties:
class SurveyItem {
public string Question { get; set; }
public string SelectedAnswerCode { get; set; }
public List<Answer> Answers { get; set; }
}
where Answer is like:
class Answer {
public int AnswerCode { get; set; }
public string AnswerText { get; set; }
}
Answers is used to build a dropdown listbox of possible answers for (a user selects one)
In my View I use a Model of IEnumerable
where for each question I have a list of answers to choose from.
I prefill this collection and pass to my View. When I click submit, it goes back to the controller for validation. If the model is not valid, I pass it to the same View for a user to fix his answers, like usual.
Question - Answers collection used for dropdown list is not preserved in the model when I submit. I use HiddenFor, EditorFor and DropDownListFor for single value properties, but, how do I keep a collection of possible answers in the Model?
P.S>
Thanks.
P.S. I am using single line code #Html.DropDownListFor to render the dropdown in my EditorTemplate:
#Html.DropDownListFor(model => model.SelectedAnswerCode,
new SelectList(Model.Answers, "AnswerCode", "AnswerText", 0))

You'll need to add virtual to the Answers declaration.
class SurveyItem {
public string Question { get; set; }
public string SelectedAnswerCode { get; set; }
public virtual List<Answer> Answers { get; set; }
}

This seems to do the trick: http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
Basically in your view do something like this:
#for(int i = 0; i < Model.Answers.Count; i++)
{
#Html.Hidden(string.Format("Answers[{0}].AnswerCode", i), Model.Answers[i].AnswerCode)
#Html.Hidden(string.Format("Answers[{0}].AnswerText", i), Model.Answers[i].AnswerText)
#Html.RadioButton("SelectedAnswerCode", Model.Answers[i].AnswerCode)
#Model.Answers[i].AnswerText
}
EDIT:
Alternatively, you can create your own HtmlHelper extension. For example:
public static class CustomHtmlHelperExtensions
{
public static MvcHtmlString HiddenForSurveyAnswers(this HtmlHelper htmlHelper, IEnumerable<Models.Answer> answers)
{
var html = new StringBuilder();
int index = 0;
foreach (var answer in answers)
{
html.AppendLine(htmlHelper.Hidden(string.Format("Answers[{0}].AnswerCode", index), answer.AnswerCode).ToString());
html.AppendLine(htmlHelper.Hidden(string.Format("Answers[{0}].AnswerText", index), answer.AnswerText).ToString());
index++;
}
return MvcHtmlString.Create(html.ToString());
}
}
Then add an #using YourMvcApplicationNamespace to the top of the view and then use the extension like:
#Html.HiddenForSurveyAnswers(Model.Answers)

With MVC, you can write an Editor Template to preserve the Model.Answers as well :)
Save a view named Answer.chtml in \Views\Shared\EditorTemplates.
Add the following code:
#model Answer
#Html.HiddenFor(item => item.AnswerCode)
#Html.HiddenFor(item => item.AnswerText)
Then in you original view, add it:
#Html.EditorFor(model => model.Answers)
#Html.DropDownListFor(model => model.SelectedAnswerCode, new SelectList(Model.Answers, "AnswerCode", "AnswerText", 0))
In this manner you don't have to worry about writing foreach statements or worry about their ids.
Hope it helps.

Related

PartialView or enumerate using foreach in View Model for an inner class using Entity Framework?

I am creating (my first) real simple ASP.NET MVC blog site where I am wondering what is the best approach to sort my comments by the the latest DateTime and how to go about injecting the query results using LINQ into the View or do I need an IQueryable<T> PartialView?
I have been reading up on IEnumerable<T> vs IQueryable<T>, and I would like to think that I wouldn't want the comments in-memory until I have them filtered and sorted.
I was thinking a PartialView using #model Queryable<Models.Blogsite.Comment> where I pass the inner class to the [ChildAction] using the ViewBag or can I just use a foreach loop in the View?
My Article class looks a little like this:
public class Article
{
//...more properties
public virtual IQueryable<Comment> Comments { get; set; }
}
My Comment class looks a little like this:
public class Comment
{
[Key]
public int CommentId { get; set; }
[ForeignKey("Article")]
public int ArticleId { get; set; }
[DataType(DataType.DateTime), Timestamp,ScaffoldColumn(false)]
public DateTime Timestamp { get; set; }
//...more properties
public virtual Article Article { get; set; }
}
And then if I did implement a PartialView it would look a little like this:
#model IQueryable<BlogSite.Models.Comment>
<table>
<tbody>
#foreach (BlogSite.Models.Comment comment in ViewBag.Comments)
{
<tr>
<td>
#...
I changed my public class Article to this:
public class Article
{
//...more properties
public virtual ICollection<Comment> Comments { get; set; }
}
And then I created a method in my ArticleRepository where I call it from UnitOfWOrk which is instantiated within the controller:
public IEnumerable<Comment> GetCommentsByArticleId(int id)
{
List<Comment> tempList = new List<Comment>();
var article = GetArticleById(id);
var comments = article.Comments.Where(c => c.ParentCommentId == null).OrderByDescending(c => c.Timestamp);
foreach (var c in comments)
{
if (c.isRoot)
{
tempList.Add(c);
// Replies
foreach (var r in article.Comments.Where(x => x.ParentCommentId == c.CommentId).OrderBy(x => x.Timestamp))
{
tempList.Add(r);
}
}
}
return tempList;
}
I have a property called bool isRoot in my comment class so I can render all comments from the property int ParentCommentId so users can respond to these comments. My question now is that if my web application has thousands of articles and the querying is done in-memory and not at the database will this eventually turn into a performance issue?
Won't the temporary list go into garbage collection once it is out of scope? What is the advantage of using IQueryable in this scenario?
Using ViewBag is a bad practice. If you need a sorted list - do it in a controller:
var comments = context.GetComments().ToList();
comments.Sort((x, y) => y.Timestamp.CompareTo(x.Timestamp));
return View(comments)
And pass a sorted list in the view:
#model IEnumerable<BlogSite.Models.Comment>
<table>
....

How to update hierarchical ViewModel?

I am stuck with this problem.
I have a model AssessmentModel defined like this:
public class AssessmentModel
{
public Respondent Respondent { get; set; }
public List<CompetencyModel> Competencies { get; set; }
}
public class CompetencyModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<ResultModel> Results { get; set; }
}
public class ResultModel
{
public int Id { get; set; }
public int Score { get; set; }
}
All I need is to set value to the Score property of ResultModel.
Score is the only editable property here.
And I have just 1 View only, this view has a #model List, it displays a list of CompetencyModel items with Edit button for each one.
When I click the Edit button, the Id of CompetencyModel is passed to the same View, and the View draws an Edit form for ResultModel items that belong to the selected CompetencyModel.
However the form for ResultModel items exists on the same View, and the model of the View is still #model List.
How can I get to the Score property by using bindable Html.EditorFor(m=>m.Score) helper for each ResultModel item?
The View is defined like this:
#model List<CompetencyModel>
#foreach(var comp in Model)
{
<p>#comp.Name</p>
Edit
}
In the controller I set ViewBag.CurrentId = comp.Id, and at the bottom of the View:
if(ViewBag.CurrentId != null) //draw a form for ResultModel items
{
// What should I do now?
// how cant I use Html.EditorFor(m=>...) if the Model is still List<CompetencyModel>
}
I need to get to a single ResultModel entity to set a value to a Score property.
Thank you.
You should be able to get this done using Linq. Consider having the following code segment in the your last if statement
var result = Model.Results.FirstOrDefault(r => r.Id == ViewBag.CurrentId);
I dont have a IDE with me, so watchout for syntext errors

select the value to populate html.dropdownlist

I have two classes as follows
public class ODCTE_Major
{
public int ODCTE_MajorId { get; set; }
public string OfficialMajorName { get; set; }
public string MajorCode { get; set; }
... More unrelated code ....
}
AND
public class CareerMajor
{
...lots of unrealted code to this question left out
public int ODCTE_MajorId { get; set; }
public virtual ODCTE_Major ODCTE_Major { get; set; }
}
I added a controller with CRUD methods and in the create.cshtml there is this line
<div class="editor-field">
#Html.DropDownList("ODCTE_MajorId", String.Empty)
#Html.ValidationMessageFor(model => model.ODCTE_MajorId)
</div>
The select list populates it with the OfficialMajorName from ODCTE_Major. I need the select list to populate with the MajorCode or a value that looks like MajorCode - OfficialMajorName.
Could someone provide assistance for how this is done, please?
Thanks.
Add this to ODCTE_Major:
public string MajorDisplayName
{
get { return string.Format("{0} - {1}", MajorCode, OfficialMajorName); }
}
This is just a read only property used to create the display text in the format you want the menu to use.
Then in CareerMajor, add:
public IEnumerable<ODCTE_Major> Majors{ set; get; } // Thank you Shyju!
This will give you a place in your view model to pass the list of Majors you want in your menu to the view.
Then in your action method when you're creating a CareerMajor view model to send to the view, populate the new IEnumberable with the ODCTE_Major entities you'd like displayed in your menu.
On the view page:
#Html.DropDownListFor(m => m.ODCTE_MajorId, new SelectList(Model.Majors, "ODCTE_MajorId", "MajorDisplayName", Model.ODCTE_MajorId), "Select One")
This creates a SelectList to populate the drop down with. The SelectList constructor is saying use ODCTE_MajorId as the value for a SelectListItem in the menu, and to use MajorDisplayName as the text to actually display in the menu. It sets the selected value, if there is one, and adds a null item with the text "Select One" to the top of the menu. Feel free to take that final argument out if you don't want the null text.
Have your ViewModel hold a Collection property to represent all available Majors (for poulating the Dropdown)
public class CareerMajor
{
//other proiperties
public int ODCTE_MajorId { get; set; }
public IEnumerable<ODCTE_Major> Majors{ set; get; }
}
And in your GET Action, fill it and send it to your strongly typed view
pubilc ACtionResult Create()
{
var viewModel=new CareerMajor();
viewModel.Majors=db.GetllAllMajors(); // getting a list of ODCTE_Major objects
return View(viewModel);
}
and in the View, use the DropDownListFor HTML Helper method.
#model CareerMajor
#Html.BeginForm())
{
#Html.DropDownListFor(m=>m.ODCTE_MajorId,
new SelectList(Model.Majors,"ODCTE_MajorId ","MajorCode"),"select one..")
//other elements
}
In your controller action:
ViewBag.ODCTE_MajorId = new SelectList(availableMajors, "ODCTE_MajorId", "MajorCode");
*second and third parameters are the names of the value and text fields respectively
Then in your view:
#Html.DropDownList("ODCTE_MajorId", String.Empty)
where availableMajors is an IEnumerable that contains the majors you want to list.

Html.Editor() helper in ASP.NET MVC 3 does not work as expected with array in model

In my ASP.NET MVC 3 application I have classes like the following:
public class Localization<T>
{
public int VersionID { get; set; }
public T Value { get; set; }
...
}
public class Localizable<T>
{
public Localization<T>[] Name { get; set; }
...
}
Then, I have the following view:
#model dynamic
...
#for (int i = 0; i < VersionCount; i++)
{
...
#Html.Editor(string.Format("Name[{0}].Value", i))
...
}
Now, when I display this view, passing a subclass of Localizable<string> as the model, the textboxes for the strings are rendered, but they are empty. If I replace #Html.Editor(string.Format("Name[{0}].Value", i)) with #InputExtensions.TextBox(Html, string.Format("Name[{0}].Value", i), Model.Name[i].Value), the textboxes are correctly filled with values from the model. However, using TextBox instead of Editor is not an option for me, because I want to use different editor templates for different types of T. So, what am I doing wrong, or is it a bug in MVC, and is there any workaround?
you can use attribute UIHint("MyUIHintName").
public class Localizable<T>
{
[UIHint("MyUIHintName")]
public Localization<T>[] Name { get; set; }
...
}
Then you need to create folder Views/Shared/EditorTemplates/. Next you need create Razor View
Views/Shared/EditorTemplates/MyUIHintName.cshtml
In this view you can write logic for every type, for example:
#model dynamic
#if(ViewData.ModelMetadata.ModelType.Name=="string")
{
//Do something
}
#if(ViewData.ModelMetadata.ModelType.Name=="int")
{
//Do something
}

Populate a DropdownList with composite key records in MVC3.net?

I don't know if I'm missing something obvious, but I really want to grab names of clients associated with a composite key.
Controller Code:
Job job = db.Jobs.Find(id);
ViewBag.jobClientsList = new SelectList(job.JobClients.ToList(), "ClientNumber", "ClientNumber");
View Code:
<%: Html.DropDownList("ClientNumber", ViewData["JobClientsList"] as SelectList)%>
Model:
namespace Sample.CustomerService.Domain {
using System.ComponentModel.DataAnnotations;
public class JobClient {
public JobClient() { }
[Key]
[Column(Order = 1)]
public virtual int JobNumber { get; set; }
[Key]
[Column(Order = 1)]
public virtual int ClientNumber { get; set; }
public virtual Client Client { get; set; }
public virtual Job Job { get; set; }
}
}
This code works, but all I get in the dropdownlist is a bunch of numbers. What I would really like is the client names associated with the numbers but I'm really not sure how to do it! I've been looking around for ages!
After re-reading your question your answer seems simpler then expected.
Check out the Select list class http://msdn.microsoft.com/en-us/library/system.web.mvc.selectlist.aspx
The constructor your using in your controller is wrong, it should be:
ViewBag.jobClientsList = new SelectList(job.JobClients.ToList(), "ClientNumber", "Client");
You were setting the text value of the selectList to be "ClientNumber" which is why you had a list of numbers and not names!
By default the select list is showing you the property that is marked [Key]
<%: Html.DropDownList("ClientNumber",
ViewData["JobClientsList"].Client as SelectList)%>
Should print the client name (assuming the primary Key on the Client object is their name, otherwise You'd need something like ViewData["JobClientsList"].Client.FullName
The best solution would be to use a ViewModel instead of using ViewBag or ViewData for this, it'll help avoid a lot of headaches both now and in the future.
What I have done in the past to get DropDownLists working is save the List to the Session Variable, and then create my SelectList in the actual DropDownList.
Controller:
Job job = db.Jobs.Find(id).ToList();
ViewBag.jobClientList = job;
View:
<%: Html.DropDownList("ClientNumber", new SelectList((If you view is strongly typed, put that here) ViewData["JobClientsList"],"ClientNumber","ClientNumber")%>
This may be poorly worded, so I think I can clarify if need be
Anyone looking for a solution, try the following:
In your controller:
1) Get the list:
var allCountries = countryRepository.GetAllCountries();
2) define a variable:
var items = new List<SelectListItem>();
3)loop each item:
foreach(var country in allCountries)
{
items.Add(new SelectListItem() {
Text = coutry.Name,
Value = Country.Id.ToString(),
// Put all sorts of business logic in here
Selected = country.Id == 13 ? true : false
});
}
model.Countries = items;
In your view:
#Html.ActionLink("StringToDisplay", "actionName", "controllerName", new SelectList(Model.Countries, "Value","Text"),"--Please Select--", new{#class="form-control"})
Not to mention Model should have a property with
public IEnumerable<Country> Countries {get; set;}

Resources