My view won't bind with my model in MVC - asp.net-mvc

I have the following viewmodel:
public class CityViewModel
{
public CityViewModel() {
CityDetails = Enumerable.Range(1,2).Select(x => new CityDetail()).ToList();
}
public City City { get; set; }
public IList<CityDetail> CityDetails { get; set; }
public class CityDetail
{
public CityDetail() {
Correct = true;
}
public bool Correct { get; set; }
}
In my view I have the following:
#foreach (int index in Enumerable.Range(0, Model.CityDetails.Count()))
{
<input type="hidden" name="CityDetails[#index]_Correct)" value="false" />
<input type="checkbox" id="CityValid" name="CityDetails[#index]_Correct" value='#Model.CityDetails[index].Correct' />
I can't get any binding from the view to the model for the field named "Correct" so I am wondering about the naming that I used. Does anyone have an idea what might be wrong?

The easy and best way to map collection is using the indexer. Use the following code snippet
#for (int i = 0; i < Model.CityDetails.Count; i++)
{
#Html.EditorFor(x => Model.CityDetails[i].Correct)
}

There's no need for your view to look like that. Create an editor template, and then use Html.EditorFor(x => x.CityDetails). That will handle enumerating the collection and properly writing out the elements so that they map back into the list.

Related

MVC dynamic form controls binding to model

I'm trying to dynamically create a form with different types of fields. Then simply pass the user inputted data back to the controller and bind back to my model. I'm using a custom editor template for each control and was hoping it would bind properly in the controller. However, the property is NULL each time so I cannot retrieve the input values.
Model
public class ReceiptModel : ClassBase
{
public int ReceiptId { get; set; }
public List<CustomControlModel> CustomControlList { get; set; }
}
public class CustomControlModel
{
public string CustomControlName { get; set; }
public CustomControlType CustomControlType { get; set; }
}
View
#foreach (CustomControlModel ccm in #Model.CustomControlList)
{
if (!string.IsNullOrEmpty(ccm.PropertyName))
{
#Html.EditorFor(model => ccm, "CustomControlModel")
}
}
Custom Template
#Html.HiddenFor(model => model.CustomControlId)
<label>#Model.LabelCaption</label>
#switch (#Model.CustomControlType)
{
case CustomControlType.TEXTBOX:
if (#Model.ReadOnly)
{
#Html.TextBoxFor(model => model.CustomControlId, new { #readonly = "readonly", #Value = #Model.Value })
}
else
{
<input id="#Model.CustomControlName" name="#Model.CustomControlName" type="text" value="#Model.Value" />
}
Any help would be much appreciated. Thanks in advance.
Don't use foreach. It does not result in the correct property names in the rendered html and so the properties will not be picked up by the model binder. Use a for loop instead:
#for (int i = p; I < #Model.CustomControlList.Count; i++)
{
if (!string.IsNullOrEmpty(Model.CustomControlList[i].PropertyName))
{
#Html.EditorFor(model => model.CustomControlList[i], "CustomControlModel")
}
}

Better way of creating repeating HTML section

I've asked this once before but without any code to look at, here I have an implementation and I'm wondering if there is a better way to accomplish this.
I want a repeating html section like so:
<div>
<input id=id1 name=id1 type=text/>
</div>
<div>
<input id=id2 name=id2 type=text/>
</div
etc
This could contain any number of input boxes which map to the List of 'something' classes I have in the model, I presently do this with a View
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Somethings.Count; i++)
{
Model.Index = i;
#Html.Action("Index", "HtmlSection", Model);
}
// other stuff
}
and a partial view
#{
int index = Model.Index;
}
#Html.TextBoxFor(x => x.Somethings[index].TheProperty)
Where the model looks like this
public class HtmlSectionModel
{
public List<Something> Somethings { get; set; }
public int Index { get; set; }
}
Finally the action looks like this
public ActionResult Index(HtmlSectionModel model)
{
// do stuff
}
To me this works but isn't ideal
The partial view can now only be used within this context, it uses the top level model rather than just the 'Something' class
I have to pass an index in the model in order to get unique name's for binding, if I didn't do this then textbox would have the same name/id
This seems to me to be a common pattern so others must have solved it in other ways?
I guess what I'm after here is the MVC equivalent of Asp.Net UserControls/Webcontrols (which seem to be child actions/partial views), but, combined with model binding which seems to require unique names
What I wanted can be accomplished with editor templates
Controller
public class UsesEditorController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View(new SomeModel());
}
[HttpPost]
public ActionResult Index(SomeModel model)
{
return View(model);
}
}
Model
public class Blob
{
public string Name { get; set; }
public string Address { get; set; }
public Blob()
{
Name = string.Empty;
Address = string.Empty;
}
}
public class SomeModel
{
public List<Blob> Blobs { get; set; }
public SomeModel()
{
int count = 5;
this.Blobs = new List<Blob>(count);
for (int i = 0; i < count; i++)
{
this.Blobs.Add(new Blob());
}
}
}
View
#model MyProject.Areas.EditorTemplates.Models.SomeModel
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Blobs.Count; i++)
{
#Html.EditorFor(m => m.Blobs[i], "CustomEditorForBlob");
}
<input type="submit" value="Send data back" />
}
And Editor, which can be anywhere in the view folder as I'm referring to it directly
#model MyProject.Areas.EditorTemplates.Models.Blob
#Html.TextBoxFor(m => m.Name)
#Html.TextBoxFor(m => m.Address)
This renders with ids like:
<input class="k-textbox" id="Blobs_1__Name" name="Blobs[1].Name" ...
So this gives me
List item
a repeating structure, just like UserControls in Asp.Net
The editor template only refers to the Blob class, it has no knowledge of the SomeModel class
Binding works (tested it)
It looks to me like what you are trying to accomplish is unique IDs for your inputs, and you certainly don't need a partial to do this. You can output your text box inside your for loop like the following:
#Html.TextBoxFor(x => x.Somethings[i].TheProperty)
This will generate a unique id something like id="Somethings_1_TheProperty". If you don't like that id, you can certainly make your own with something like this:
#Html.TextBoxFor(x => x.Somethings[i].TheProperty, new {id="id" + (i+1)})

How do I bind checkboxes to the List<int> property of a view model?

I've been reading the various posts on view models and check boxes, but my brain is starting to lock up and I need a little push in the right direction.
Here's my simplified view model. I have checkboxes that need to populate the lists with their values. I don't think this can happen automagically. I'm not sure how to bridge the gap between an array of string values and a List correctly. Suggestions?
public int AlertId { get; set; }
public List<int> UserChannelIds { get; set; }
public List<int> SharedChannelIds { get; set; }
public List<int> SelectedDays { get; set; }
Have your View Model like this to represent the CheckBox item
public class ChannelViewModel
{
public string Name { set;get;}
public int Id { set;get;}
public bool IsSelected { set;get;}
}
Now your main ViewModel will be like this
public class AlertViewModel
{
public int AlertId { get; set; }
public List<ChannelViewModel> UserChannelIds { get; set; }
//Other Properties also her
public AlertViewModel()
{
UserChannelIds=new List<ChannelViewModel>();
}
}
Now in your GET Action, you will fill the values of the ViewModel and sent it to the view.
public ActionResult AddAlert()
{
var vm = new ChannelViewModel();
//The below code is hardcoded for demo. you mat replace with DB data.
vm.UserChannelIds.Add(new ChannelViewModel{ Name = "Test1" , Id=1});
vm.UserChannelIds.Add(new ChannelViewModel{ Name = "Test2", Id=2 });
return View(vm);
}
Now Let's create an EditorTemplate. Go to Views/YourControllerName and Crete a Folder called "EditorTemplates" and Create a new View there with the same name as of the Property Name(ChannelViewModel.cshtml)
Add this code ro your new editor template.
#model ChannelViewModel
<p>
<b>#Model.Name</b> :
#Html.CheckBoxFor(x => x.IsSelected) <br />
#Html.HiddenFor(x=>x.Id)
</p>
Now in your Main View, Call your Editor template using the EditorFor Html Helper method.
#model AlertViewModel
<h2>AddTag</h2>
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(m => m.AlertId)
#Html.TextBoxFor(m => m.AlertId)
</div>
<div>
#Html.EditorFor(m=>m.UserChannelIds)
</div>
<input type="submit" value="Submit" />
}
Now when You Post the Form, Your Model will have the UserChannelIds Collection where the Selected Checkboxes will be having a True value for the IsSelected Property.
[HttpPost]
public ActionResult AddAlert(AlertViewModel model)
{
if(ModelState.IsValid)
{
//Check for model.UserChannelIds collection and Each items
// IsSelected property value.
//Save and Redirect(PRG pattern)
}
return View(model);
}
Part of My View Model:
public List<int> UserChannelIds { get; set; }
public List<int> SharedChannelIds { get; set; }
public List<int> Weekdays { get; set; }
public MyViewModel()
{
UserChannelIds = new List<int>();
SharedChannelIds = new List<int>();
Weekdays = new List<int>();
}
I used partial views to display my reusable checkboxes (I didn't know about editor templates at this point):
#using AlertsProcessor
#using WngAlertingPortal.Code
#model List<int>
#{
var sChannels = new List<uv_SharedChannels>();
Utility.LoadSharedChannels(sChannels);
}
<p><strong>Shared Channels:</strong></p>
<ul class="channel-list">
#{
foreach (var c in sChannels)
{
string chk = (Model.Contains(c.SharedChannelId)) ? "checked=\"checked\"" : "";
<li><input type="checkbox" name="SharedChannelIds" value="#c.SharedChannelId" #chk /> #c.Description (#c.Channel)</li>
}
}
All three checkbox partial views are similar to each other. The values of the checkboxes are integers, so by lining up my view model List names with the checkbox names, the binding works.
Because I am working in int values, I don't feel like I need the extra class to represent the checkboxes. Only checked checkboxes get sent, so I don't need to verify they are checked; I just want the sent values. By initializing the List in the constructor, I should be avoiding null exceptions.
Is this better, worse or just as good as the other solution? Is the other solution (involving an extra class) best practice?
The following articles were helpful to me:
http://forums.asp.net/t/1779915.aspx/1?Checkbox+in+MVC3
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Binding list with view model
This site handles it very nicely
https://www.exceptionnotfound.net/simple-checkboxlist-in-asp-net-mvc/
public class AddMovieVM
{
[DisplayName("Title: ")]
public string Title { get; set; }
public List<CheckBoxListItem> Genres { get; set; }
public AddMovieVM()
{
Genres = new List<CheckBoxListItem>();
}
}
public class MembershipViewData
{
public MembershipViewData()
{
GroupedRoles = new List<GroupedRoles>();
RolesToPurchase = new List<uint>();
}
public IList<GroupedRoles> GroupedRoles { get; set; }
public IList<uint> RolesToPurchase { get; set; }
}
//view
#model VCNRS.Web.MVC.Models.MembershipViewData
#{
ViewBag.Title = "MembershipViewData";
Layout = "~/Views/Shared/_Layout.cshtml";
int i = 0;
}
#using (Html.BeginForm("Membership", "Account", FormMethod.Post, new { id = "membershipForm" }))
{
<div class="dyndata" style="clear: left;">
<table width="100%" cellpadding="0" cellspacing="0" class="table-view list-view">
foreach (var kvp2 in Model.GroupedRoles)
{
string checkBoxId = "RolesToPurchase" + kvp2.RoleType;
<tr>
<td width="240px">
<label class="checkbox-label" for="#checkBoxId">
<input type="checkbox" class="checkbox" name="RolesToPurchase[#i]"
id="#checkBoxId" value="#kvp2.RoleType" />
#kvp2.Key
</label>
</td>
</tr>
i++;
}
<tr style="background-color: #ededed; height: 15px;">
<td colspan="5" style="text-align: right; vertical-align: bottom;">
#Html.SubmitButton(Resources.MyStrings.Views_Account_Next)
</td>
</tr>
</table>
</div>
}
//Post Action
[HttpPost]
public ActionResult Membership(MembershipViewData viewData)
{
..........................
}
}

What is a better way to display data from multiple view models

I have two view models
public class DataImportViewModel
{
public WccrViewModel wccrVM { get; set; }
public string Status { get; set; }
public string ValidationResult { get; set; }
…
}
public class WccrViewModel
{
public double? WccrId { get; set; }
public string WccrDesc { get; set; }
}
and my view
#model List<Mass.ViewModels.DataImportViewModel>
#using (Html.BeginForm("Validation", "DataImport", FormMethod.Post))
{
<input id="ProcessReshop" type="submit" value="Reshop" />
for (int i = 0; i < 1; i++)
{
<div class="display-field">
#Html.DisplayFor(model=>model[i].wccrVM.WccrId)
</div>
<div class="display-field">
#Html.DisplayFor(model => model[i].wccrVM.WccrId)
</div>
}
A table (not shown) is populated with the DataImportViewModel. I want to have the WccrViewModel displayed on top of the Table. I want to be able to access the WccrViewModel without the for loop...but it shows as a good example. How can I use wccrVM.WccrId without the for loop? Thanks in advance...cheers
How about another viewmodel class that contains both a DataImportViewModel and a WccrViewModel?
Your viewmodel should be tailored to the view, and if the view calls for both those objects, why not create a view model with what it expects?

Validating MVC Data Collections

I have a simple Question data model:
public class Question {
int QuestionId { get; set; }
string Text { get; set; }
string Answer { get; set; }
string ValidationMessage { get; set; }
};
Using this class I have built a view Model:
public class QuestionViewModel {
string Introduction { get; set; }
IEnumerable<Question> Questions { get; set; }
};
My Controller the builds the view model (from a data source) and renders the view:
#model QuestionViewModel
#using (Html.BeginForm()) {
if (Model.Questions != null) {
<ol>
#Html.EditorFor(m => Model.Questions)
</ol>
}
#Html.ValidationSummary("Unable to process answers...")
<input type="submit" value="submit" />
}
This view utilises an EditorTemplate:
#model Question
<li>
#Html.HiddenFor(m => m.Questionid)
#Html.TextBoxFor(m => m.Answer)
#Html.ValidationMessageFor(m => m.Answer)
</li>
For now, when the page is posted back, the controller validates the response:
[HttpPost]
public ActionResult Response(QuestionViewModel model) {
if (ModelState.IsValid) {
for (int i = 0; i < model.Questions.Count(); i++) {
Question q = model.Questions[i];
string questionId = String.Format("Questions[{0}]", i);
if (String.IsNullOrWhiteSpace(q.Answer)) {
ModelState.AddModelError(questionId, q.ValidationMessage);
}
}
}
}
The problem I'm having is that most of this works fine - the validates and the Validation Summary shows the correct validation messages. The problem is that I can't get individual field validators to render the error:
<span class="field-validation-valid" data-valmsg-replace="true" data-valmsg-for="Questions[0].StringValue"></span>
As you can see, when I call the ModelState.AddModelError() method, I am currently using key value of the format "Questions[0]", but I have also tried "Questions_0" and various other combinations.
Any help/guidance would be much appreciated.
[Apologies for the overly long post]
I have found the answer - as with so many things, it was obvious once I broke the problem down - the ModelState.AddModelError() just needed a fully qualified key!
Modify the HttpPost Controller as follows:
[HttpPost]
public ActionResult Response(QuestionViewModel model) {
if (ModelState.IsValid) {
for (int i = 0; i < model.Questions.Count(); i++) {
Question q = model.Questions[i];
/*
** The key must specify a fully qualified element name including
** the name of the property value, e.g.
** "Questions[0].Answer"
*/
string questionId = String.Format("Questions[{0}].Answer", i);
if (String.IsNullOrWhiteSpace(q.Answer)) {
ModelState.AddModelError(questionId, q.ValidationMessage);
}
}
}
}

Resources