MVC4: ViewModel (with radiobuttonlist) is empty after HttpPost - asp.net-mvc-3

I'm having a hard time getting the values from a small multiple choice questionnaire posted to the Controller in my MVC4 app:
The model looks like this:
public class Evaluation
{
public int Id { get; set; }
public IEnumerable<MultipleChoiceQuestion> Question { get; set; }
public Remark Rem { get; set; }
}
public class MultipleChoiceQuestion
{
public int Id { get; set; }
public string Question { get; set; }
public MultipleChoiceAnswer Answer { get; set; }
}
public enum MultipleChoiceAnswer
{
DISAGREE,
NEUTRAL,
AGREE,
NA,
}
This is the View (leaving out some markup):
#model Models.Evaluation
#using (Html.BeginForm("EvaluationB", "Evaluation", FormMethod.Post))
{
#foreach (var item in Model.Question)
{
#Html.DisplayFor(model => item.Question)
#Html.EditorFor(model => item.Question, "Enum_RadioButtonList", new { Id = item.Id })
}
#Html.Label("Remark")
#Html.TextAreaFor(model => model.Rem)
<input type="submit" value="Next" />
}
The "Enum_RadioButtonList" is a View a grabbed from here: https://gist.github.com/973482. It seems like the best way to show enum values in a radiobuttonlist (tho their should be an easier way in MVC 4)
The Controller looks like this:
public ActionResult EvaluationA()
{
Models.Evaluation evm = new Models.Evaluation();
evm.Question = db.MultipleChoiceQuestions.ToList(); //feeding the View some predefined questions
return View(evm);
}
public ActionResult EvaluationB(Models.Evaluation ev)
{
if (ModelState.IsValid)
{
// TODO: save model
return View("EvaluationB", evm);
}
return View("EvaluationA", ev);
}
The questions are loaded fine in the View, but for some reason, the model posted to the Controller remains empty after an HttpPost, and i don't understand why.

I did not see the form have mapping for the Id like below:
#Html.HiddenFor(model => model.Id)
Each of the inner collection of question should also have Id so that it is posted along with the form. So inside the foreach loop in your form for each question you can have:
#Html.HiddenFor(model => item.Question.Id)
Also the model has a collection of type MultipleChoiceQuestion. For model binding to the collection the name of the collection elements should have name attribute with ordered numbers as explained in this post http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

Ive done some more research stumbled upon this post:
http://dotnetslackers.com/articles/aspnet/Understanding-ASP-NET-MVC-Model-Binding.aspx
The problem was in the naming of the html fields. Ive added my View like this and now i can read out the values in the Controller correctly:
#for (int i = 0; i < 6; i++)
{
<tr>
<td>
#Html.DisplayFor(m => m.Question[i].Question)
</td>
<td class="mult_question">
#Html.EditorFor(m => m.Question[i].Answer, "Enum_RadioButtonList" )
</td>
</tr>
}
<tr>
<td>
#Html.Label("Remark")
#Html.TextAreaFor(m => m.Remark)

Related

Dropdown list in MVC3

I have a form that wanna to select category and tag from drop down list and bind it to a post , this is my ViewModel:
public class PostViewModel
{
public IList<Category> Category { get; set; }
public IList<Tag> Tag { get; set; }
}
and this is my get action :
public ActionResult Add()
{
ViewBag.CategoryList = new SelectList(_categoryRepository.GetAllCategory());
ViewBag.TagList = new SelectList(_tagRepository.GetAllTag());
return View();
}
now how can I get the Id dropdownlst to send it to the Post action?? :
<div>
#Html.LabelFor(post => post.Category)
#Html.DropDownListFor ????
#Html.ValidationMessageFor(post => post.Category)
</div>
I tried this one it it didn't work
<div>
#Html.LabelFor(post => post.Category)
#Html.DropDownListFor(post => post.Category, ViewBag.CategoryList as SelectList, "--- Select Category ---")
#Html.ValidationMessageFor(post => post.Category)
</div>
please give me a solution about this ,thanks
Try to avoid dynamic stuff like ViewBag and ViewData. Use strongly typed views.
ViewModel is just a POCO class which we will use to transfer data between your view and the action method. It will be specific to the view.
You have a viewmodel but you are not using it properly. Add 2 more properties to your viewmodel for getting the selected item from the dropdown. Also i changed the name of your proprties to pluralized for (Categories ,Tags) because they are for storing a collection.
public class PostViewModel
{
public List<SelectListItem> Categories{ get; set; }
public List<SelectListItem> Tags { get; set; }
public int SelectedCategory { set;get;}
public int SelectedTag { set;get;}
}
Now in your GET Action method, create an object of your view model and set the collection properties and then send that object to your view using View method.
public ActionResult Add()
{
var vm=new PostViewModel();
vm.Categories= GetAllCategories();
vm.Tags= GetAllTags();
return View(vm);
}
Assuming GetAllCategories and GetAllTags are 2 methods which returns a collection of SelectListItem for categories and tags.
public List<SelectListItem> GetAllCategories()
{
List<SelectListItem> categoryList=new List<SelectListItem>();
categoryList.Add(new SelectListItem { Text = "Sample", Value = "1" });
// TO DO : Read from your dB and fill here instead of hardcoding
return categoryList;
}
and in your view,
#model PostViewModel
#using(Html.BeginForm())
{
#Html.DropDownListFor(x => x.SelectedCategory,
new SelectList(Model.Categories,"Value","Text"), "Select")
#Html.DropDownListFor(x => x.SelectedTag,
new SelectList(Model.Tags,"Value","Text"), "Select")
<input type="submit" value="save" />
}
And in your HttpPost action method, you can get the selected items in the 2 properties we added
[HttpPost]
public ActionResult Add(PostViewModel model)
{
if(ModelState.IsValid)
{
//check model.SelectedCategory and model.SelectedTag
//save and redirect
}
//to do :reload the dropdown again.
return View(model);
}
you were close:
public class PostViewModel
{
public int CategoryId { get; set; } // <-- Altered
public int TagId { get; set; } // <-- Altered
}
<div>
#Html.LabelFor(post => post.Category)
#Html.DropDownListFor(post => post.CategoryId,
ViewBag.CategoryList as IEnumerable<SelectListItem>,
"--- Select Category ---")
#Html.ValidationMessageFor(post => post.Category)
</div>

Dropdownlist not selecting the preselected value-mvc3

I am having strange issue, MVC dropdown selected value is not preselected on page Load.
My Models are:
public class DocumentTypesViewModel
{
[Required(ErrorMessage = "DocumentType is required")]
public int OHDocumentTypeId { get; set; }
public string OHDocumentTypeDescription { get; set; }
}
public class ClientAdvancedSearchViewModel
{
[Display(Name = "Name")]
public string Name { get; set; }
[Display(Name = "DocumentType")]
public string DocumentTypeId { get; set; }
public IEnumerable<SelectListItem> DocumentTypes { get; set; }
}
In My Controllers I am populating the ClientAdvancedSearchViewModel like this
[HttpGet]
public ActionResult ClientAdvancedSearch()
{
ClientAdvancedSearchViewModel clientAdvancedSearchViewModel = iClientReferralRecordsRepository.GetDocumentMetadata();
//DocumentTypes Dropdown
var ddlDocumentTypes = iDocumentTypeRepository.GetDocumentTypes();
clientAdvancedSearchViewModel.DocumentTypes = new SelectList(ddlDocumentTypes, "OHDocumentTypeId", "OHDocumentTypeDescription",clientAdvancedSearchViewModel.DocumentTypeId);
return View(clientAdvancedSearchViewModel);
}
Finally in the View:
<td>
<div class="editor-label">
#Html.LabelFor(model => model.DocumentTypes)
</div>
<div class="editor-field">
#Html.DropDownListFor(x => x.DocumentTypeId, Model.DocumentTypes, "Please Select", new { #id = "ddlDocumentType" })
</div>
</td>
I believe the Name of the dropdown is same is x => x.DocumentTypeId, becuase of this I think, my value is not preselected.
This is the ViewSource for generated HTML for the Drop Down
<select id="ddlDocumentType" name="DocumentTypeId">
<option value="">Please Select</option>
<option value="20">records</option>
<option value="21"> record1</option>
..
How can I rename my dropdownlist name or How can I solve my problem?
Thank you
Updated: Added the missed line
ClientAdvancedSearchViewModel clientAdvancedSearchViewModel = iClientReferralRecordsRepository.GetDocumentMetadata();
Your code on your view is just right. You forgot to set the value for DocumentTypeId. This is your code as you posted:
[HttpGet]
public ActionResult ClientAdvancedSearch()
{
//DocumentTypes Dropdown
var ddlDocumentTypes = iDocumentTypeRepository.GetDocumentTypes();
clientAdvancedSearchViewModel.DocumentTypes = new SelectList(ddlDocumentTypes, "OHDocumentTypeId", "OHDocumentTypeDescription",clientAdvancedSearchViewModel.DocumentTypeId);
return View(clientAdvancedSearchViewModel);
}
And you missed this:
clientAdvancedSearchViewModel.DocumentTypeId = some_value;
Also, do you intend to have DocumentTypeId as an int instead of a string?
UPDATE:
You can also check that you set the id like this:
#Html.DropDownListFor(x => x.DocumentTypeId, new SelectList(Model.DocumentTypes, "Id", "Value", Model.DocumentTypeId), new { #id = "ddlDocumentType" })
Notice I used the overload with new SelectList. I don't remember all the overloads and I do it like that all the time, so you might check our the other overloads that suits your need.

Passing data to post method

I have a view model that looks like this
public class ViewModelRound2
{
public Bid Bid { get; set; }
public bool SelectedForRound2 { get; set; }
}
I have a get action method that looks like this
public ActionResult Round2Manager(long id)
{
...
return View(round1Ring3Bids);
}
And a post method that looks like this (not implemented it yet)
[HttpPost]
public ActionResult Round2Manager(IEnumerable<ViewModelRound2> viewModelRound2)
{
return View(viewModelRound2);
}
My view looks like this
#for (var x = 0; x < Model.Count(); x++)
{
ViewModelRound2 viewModelRound2 = Model.ElementAt(x);
Bid bid = viewModelRound2.Bid;
string userName = #bid.User.Invitation.Where(i => i.AuctionId == bid.Lot.Lot_Auction_ID).First().User.User_Username;
<tr>
<td>
#userName
</td>
<td>
#bid.Bid_Value
</td>
<td>
#Html.EditorFor(c => c.ElementAt(x).SelectedForRound2)
</td>
</tr>
}
</table>
<div class="buttonwrapper2">
#Ajax.ActionLink("Select", "Round2Manager", new { viewModelRound2 = Model }, new AjaxOptions() { HttpMethod = "POST"} )
</div>
The page this renders, contains checkboxes per row in the rendered table and I want to be able to pass checked/unchecked values to the post method so that it can process them. The problem is that the viewModelRound2 parameter of the post method is always null. What is going on? How can I write this so that it does what I intend?
You should put all that HTML inside a <form>.

Can the properties of a model be accessed indirectly in a Razor view?

Can the properties of a model be accessed indirectly in a Razor view?
So instead of:
#Html.LabelFor(model => model.ColA)
#Html.LabelFor(model => model.ColB)
#Html.LabelFor(model => model.ColC)
Is something like the following possible?
#foreach (var col in Model.Columns)
{
#Html.LabelFor(model => model[col])
}
I should qualify that the model to be used will be an EF model:
public class Record
{
[Key, Display(Name="Column A")]
public string ColA { get; set; }
[Display(Name="Column B")]
public string ColB { get; set; }
[Display(Name="Column C")]
public string ColC { get; set; }
}
public class RecordDbContext : DbContext
{
public DbSet<Record> Records { get; set; }
}
How might I implement 'Columns' to show the display name for the property?
If Model.Columns is a list that would almost work. You would need to change it to this though:
#foreach (var col in Model.Columns)
{
#Html.LabelFor(model => col)
}
Seems like your view model is not adapted to the requirements of your view (which is to loop through some values and display labels about them). You could use a collection in your view model instead of properties as it will allow you to loop:
public IEnumerable<SomeModel> Columns { get; set; }
Then in your view instead of writing a loop you could use a display template:
#model MyViewModel
#Html.DisplayFor(x => x.Columns)
and then define the corresponding template which will automatically be rendered for each element of the Columns collection (~/Views/Shared/DisplayTemplates/SomeModel.cshtml):
#model SomeModel
#Html.LabelFor(x => x.SomeModelProperty)

How can I safely access key groups in the FormCollection?

I have a table where each tr is grouped by having their input elements name set to a value that is unique for each row.
For example,
<td>
<input data-field="opps" type="text" value="somevalue" name="#item.Code" />
</td>
<td>
<input data-field="asc" type="text" value="somevalue2" name="#item.Code" />
</td>
On POST
[HttpPost]
public ActionResult Update(FormCollection collection)
{
try
{
//doin work on collection...order assumed static
return RedirectToAction("Index");
}
catch
{
return View();
}
}
my System.Web.MVC.FormCollection is grouped in the same order I define the <td>. I don't like to assume order, but without access to my data-field, I'm not sure what else I can do (maybe I could append the data-field value as a prefix to the name and put it all together with a custom collection and Regex..but that seems nutty).
Is there a way to access the data-field? This way I'm not fragile to re-ordering or adding new columns in the View.
Let's say you have a class (model) defined like this:
public class MyModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
In you controller, you might have an action called Create, like so:
[HttpGet]
public ViewResult Create()
{
MyModel sampleModel = new MyModel();
return View(sampleModel);
}
[HttpPost]
public ActionResult Create(MyModel sampleModel)
{
if (ModelState.IsValid)
{
TempData["Error"] = "There were errors. Please correct the problem and submit again";
return View(sampleModel);
}
// At this point everything is fine and you can access data in your sampleModel
if (sampleModel.Age >= 16)
{
return RedirectToAction("LegalAccess");
}
else
{
TempData["Error"] = "You must be 16 or over to access this site";
return RedirectToAction("AgeRestriction");
}
}
When you create a strongly typed view that uses MyModel as model you might define it something like this:
#model MyModel
#{
Layout = "~/Shared/_Layout.cshtml";
}
#using (Html.BeginForm())
{
#Html.LabelFor(m => m.FirstName)
#Html.TextBoxFor(m => m.FirstName)
<br />
#Html.LabelFor(m => m.LastName)
#Html.TextBoxFor(m => m.LastName)
<br />
#Html.LabelFor(m => m.LastName)
#Html.TextBoxFor(m => m.LastName)
<input type="submit" value="Submit" />
}
When you submit this form, the model binder will in the background copy data from this form using Request.Form into an object of type MyModel which it creates in the background. This new object is passed to an action that handles HTTP POST method. With this you get strongly typed object and you don't have to worry about the order of items in FormCollection.
I hope I helped answer your question.
BTW. I wrote this without Visual Studio, so I hope there are not errors. :-)

Resources