Manually created input is breaking model posting back values - asp.net-mvc

I have the following ViewModel:
public class DataSyncViewModel
{
public ConfigurableDataSyncOptions DataSyncOptions { get; set; }
public int Id { get; set; }
public string SystemName { get; set; }
}
I then loop through the DataSyncOptions to list some textboxes in my view:
#if (#Model.DataSyncOptions != null)
{
if (Model.DataSyncOptions.TextConfigurableOptions != null)
{
for (int i = 0; i < Model.DataSyncOptions.TextConfigurableOptions.Count; i++)
{
<div class="span6">
<h4>#Model.DataSyncOptions.TextConfigurableOptions[i].OptionText?</h4>
<p>
#Html.EditorFor(m => Model.DataSyncOptions.TextConfigurableOptions[i].OptionValue)
</p>
#Html.HiddenFor(m => Model.DataSyncOptions.TextConfigurableOptions[i].OptionName)
</div>
}
}
}
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.SystemName)
This works and when the form posts back, I can access SystemName and Id from my controller.
However, if I replace
#Html.EditorFor(m => Model.DataSyncOptions.TextConfigurableOptions[i].OptionValue)
with
#if (Model.DataSyncOptions.TextConfigurableOptions[i].OptionType == "")
{
#Html.EditorFor(m => Model.DataSyncOptions.TextConfigurableOptions[i].OptionValue)
}
else
{
//This appears to be causing issues
<input type="#Model.DataSyncOptions.TextConfigurableOptions[i].OptionType" name="Model.DataSyncOptions.TextConfigurableOptions[#i].OptionValue" value="#Model.DataSyncOptions.TextConfigurableOptions[i].OptionValue" />
}
my values stop posting back with the model. I am trying to allow a plugin creator of my app to specify the input type of an option they have added so am creating the input manually.
Any ideas as to why changing the input generation breaks the model binding on postback?

Instead of Model write Model Class Name in name attribute which is DataSyncViewModel to bind:
<input type="#Model.DataSyncOptions.TextConfigurableOptions[i].OptionType"
name="DataSyncOptions.TextConfigurableOptions[#i].OptionValue"
value="#Model.DataSyncOptions.TextConfigurableOptions[i].OptionValue" />
Actually when we write #Model it is in actualy the instance of type to which our View is strongly typed, and in this case Model is simple string not Razor so it will remain as Model and as name is different of input so it will not be binded to Model property in post.

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)})

Updated partial view model values in contoller after form submit

In my sample MVC application I have a model
class SampleModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<Certification> Certifications { get; set; }
}
class Certification
{
public int Id { get; set; }
public string CertificationName { get; set; }
public int DurationInMonths { get; set; }
}
My View (I need the certification details to be shown in a partial view)
#model SampleApplication.Model.SampleModel
<!-- other code... -->
#using (Html.BeginForm("SaveValues","Sample", FormMethod.Post, new { id= "saveForm" }))
{
#Html.HiddenFor(m => m.Id, new { id = "hdnID" })
#Html.TextBoxFor(m => m.Name, new { id = "txtName" })
#{Html.RenderPartial("_CertDetails.cshtml", Model.Certifications);}
<input type="submit" id="btnSubmit" name="btnSubmit" value="Update" />
}
Partial View
#model List<SampleApplication.Model.Certification>
<!-- other code... -->
#if (#Model != null)
{
for (int i = 0; i < #Model.Count; i++)
{
#Html.HiddenFor(m => m[i].Id , new { id = "CId" + i.ToString() })
#Html.TextBoxFor(m => m[i].CertificationName,new{ id ="CName" + i.ToString() })
#Html.TextBoxFor(m => m[i].DurationInMonths,new{ id ="CDur" + i.ToString() })
}
}
Controller
[HttpPost]
public ActionResult SaveValues(SampleModel sm)
{
//Here i am not getting the updated Certification details (in sm)
}
How I get the updated values of partial view in my controller after the form post? I am able to get the updated certification values when I am not using partialview.
Is this the right way or should I follow some other methods?
If sm.Certifications is coming back null, that means that either nothing was posted for that, or the modelbinder was unable to attach the posted data properly.
In your partial, you're defining the fields properly with an indexer, but initially, Certifications is a null list, so this code is never actually be run. That means, elsewhere you have some JavaScript logic that is adding new Certification fields to the page, dynamically, and my guess is that the field names that JavaScript is generating do not follow the indexing convention that the modelbinder expects. All your fields should be in the format of:
ListProperty[index].PropertyName
So in your case, your JS should be generating names like:
Certifications[0].CertificationName
In order for the data to be bound properly.
Oh Nooo... It was my mistake :( . I gave Certification List as my partialview model
#model List<SampleApplication.Model.Certification>
But I should use the same model(Main page model) in the partial view also.
#model SampleApp.Models.SampleModel
In the partial view the coding will be like
#for (int i = 0; i < #Model.Certifications.Count; i++)
{
#Html.HiddenFor(m => m.Certifications[i].Id, new { id = "CId" + i.ToString() })
#Html.TextBoxFor(m => m.Certifications[i].CertificationName, new { id = "CName" + i.ToString() })
#Html.TextBoxFor(m => m.Certifications[i].DurationInMonths, new { id = "CDur" + i.ToString() })<br /><br />
}
Now i am getting the updated values in my controller.
Thanks #Chris Pratt for the hint.

Refer to property within collection in MVC view, using Razor

I have a model that looks somewhat like this:
public class MyClass {
public string Id { get; set; }
public List<SubItem> SubItems { get; set; }
}
public class SubItem {
public string Key { get; set; }
public string Value { get; set; }
}
In my view, I want to submit form data to MyClass, so I can create an object of MyClass. It looks like this:
#model Models.MyClass
#using (Html.BeginForm()){
<div>
#Html.DisplayFor(model => model.Id): #Html.EditorFor(model => model.Id)
</div>
<div>
#Html.DisplayFor(model => ???): #Html.EditorFor( ??? )
</div>
<input type="submit" value="create"/>
}
You see the question marks (???) where I am in doubt. How do I get to add to this collection? I know it is a sub form of sorts, but how do I do it without much complication. If I needed to show the items, I would do a foreach(var item in Model.SubItems) { ... }. But this is different. How do I handle this?
It's really not different than displaying each item individually:
#for (int i=0; i<Model.SubItems.Length; i++)
{
<div>
#Html.DisplayFor(m => m.SubItems[i].Key): #Html.EditorFor(m => m.SubItems[i].Key)
</div>
<div>
#Html.DisplayFor(m => m.SubItems[i].Value): #Html.EditorFor(m => m.SubItems[i].Value)
</div>
}
UPDATE
Changed code above to make sure names and index values are correctly generated. Also, this will now work with scenario of no initial items, as well. Just change the i<Model.SubItems.Length condition to i<3, or whatever number of iterations you'd like.

How to have one RadioButtons group display in EditorTemplates in ASP.Net MVC(3)

I have a list of items from which I want the user to be able to input some value select one
But the radio-buttons generated by the EditorTemplate are named like "Item[x].SelectedItemId" so they are totally independent from each other and I can't get the value...
Let's go show some code.
The model:
public class FormModel
{
public List<ItemModel> Items { get; set; }
public int SelectedItemId { get; set; }
}
public class ItemModel
{
public int ItemId { get; set; }
public string ItemName { get; set; }
public string SomeString { get; set; }
}
The view:
#model FormModel
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.Items)
}
The editor template:
#model ItemModel
#Html.RadioButton("SelectedItemId", Model.ItemId)
#Model.ItemName <br/>
#Html.TextBoxFor(m => m.SomeString) <br/>
UPDATE
This is what I want:
This is what I get:
As a result, FormModel.SelectedItemId never gets the value of any radio-button.
What am I doing wrong?
It appears as though you are aware that setting the names for radio buttons to be the same is necessary to make them work. However, when you do that in an editor template by using the line of code #Html.RadioButton("SelectedItemId", Model.ItemId), MVC 3 will take into consideration that you are in an editor template for Items and prepend items[n].
This would create a name of something like name="Items[0].SelectedIndex". This would be fine if it weren't for the fact that the next radio button would be `name="Items[1].SelectedIndex".
One way to solve this is to not use an editor template and use a foreach loop instead. Here is some code that I was able to get functional. I confirmed that model-binding worked for the SelectedIndex.
#model FormModel
#{
ViewBag.Title = "Index";
}
#using (Html.BeginForm())
{
foreach (var item in Model.Items)
{
#Html.RadioButtonFor(x => x.SelectedItemId, item.ItemId)
#item.ItemName <br/>
#Html.TextBoxFor(m => item.ItemName) <br/>
}
<input type="submit" value = "submit" />
}
I had same problem, and we solved it with this piece of code.
#Html.RadioButton("", Model.Id, Model.Selected, new { Name = "deliveryMethod" })
You need to put Name property explicitly, so it will be used instead name that you get after EditorFor executes.

Resources