MVC Binding Dictionary - asp.net-mvc

I am following this tutorial. Can anyone please explain to me how I can make the textboxes bind back to the Dictionary? Right now all that happens is the data is being displayed in the textboxes - but if I change the textboxes, how do I bind back to the object?. What am I missing? Below is my code:
<input type="text" name="#Model.ExpirimentToRemove[i].Value.Title" value="#Model.ExpirimentToRemove[i].Value.Title"/>
<input type="text" name="#Model.ExpirimentToRemove[i].Value.PreviewDescription" value="#Model.ExpirimentToRemove[i].Value.PreviewDescription"/>
<input type="text" name="#Model.ExpirimentToRemove[i].Value.FullDescription" value="#Model.ExpirimentToRemove[i].Value.FullDescription"/>

The name of your text input field is wrong. You have put the value of the model instead of naming it appropriately as explained in the Hanselman's blog post.
So let's assume that you have some view model:
public class ItemViewModel
{
public string Title { get; set; }
public string PreviewDescription { get; set; }
public string FullDescription { get; set; }
}
and a main view model containing the dictionary:
public class MyViewModel
{
public Dictionary<string, ItemViewModel> ExpirimentToRemove { get; set; }
}
and your POST controller action takes this view model as parameter:
[HttpPost]
public ActionResult Remove(MyViewModel model)
{
...
}
In order to correctly bind to this view model you could have the following:
#model MyViewModel
#using (Html.BeginForm())
{
#for (var i = 0; i < Model.ExpirimentToRemove.Count; i++)
{
<div>
#Html.TextBox(
"ExpirimentToRemove[" + i + "].Key",
Model.ExpirimentToRemove[i].Key
)
#Html.TextBox(
"ExpirimentToRemove[" + i + "].Value.Title",
Model.ExpirimentToRemove[i].Value.Title
)
#Html.TextBox(
"ExpirimentToRemove[" + i + "].Value.PreviewDescription",
Model.ExpirimentToRemove[i].Value.PreviewDescription
)
#Html.TextBox(
"ExpirimentToRemove[" + i + "].Value.FullDescription",
Model.ExpirimentToRemove[i].Value.FullDescription
)
</div>
}
<p><button type="submit">OK</button></p>
}
In this example the key of the dictionary is a simple string value but you could also use a complex type.

Related

MVC 5 EditorTemplate for Model renders with same id and name

I have Parent model "Receipt" and it has dictonary of objects as a property called CustomControlDictionary of class CustomControlModel.
In my view i loop throuh each object in CustomControlDictionary property and call EditorTemplate. In the template i simply add label and textbox for my CustomControlModel object.
When code renders html controls have same id and name attributes : id="key_Value" name="key.Value" .
I did try using regualr arrays or lists instead of dictionary ,but with same result. Looking forward for any advise.
Model:
public class ReceiptModel
{
public ReceiptModel(){}
public int ReceiptId { get; set; }
public string ReceiptNumber { get; set; }
public Dictionary<string, CustomControlModel> CustomControlDictionary{get; set; }
// public List<CustomControlModel> CustomControlList { get; set; }
}
public class CustomControlModel
{
public CustomControlModel(){}
public int CustomControlId{ get; set; }
public string CustomControlName{ get; set; }
public string LabelCaption{ get; set; }
public string Value{ get; set; }
}
View:
//View (#ReceiptModel):
#foreach (var key in Model.CustomControlDictionary )
{
#Html.EditorFor(m => key.Value,"CustomControlModel")
}
Editor Template:
#model EWMS_MVC.Classes.CustomControlModel
#Html.HiddenFor(model => model.CustomControlId)
#Html.LabelFor(model => model.CustomControlId, Model.LabelCaption)
#Html.TextBoxFor(model => model.CustomControlId, new { #Value = #Model.Value })
Rendered html has same id and name for all obects in CustomControlDictionary property of ReceiptModel:
<input id="key_Value_CustomControlId" name="key.Value.CustomControlId" type="hidden" value="201922" />//value here should be "id" of the input field
<label for="key_Value_CustomControlId">Receipt number:</label>
<input id="key_Value_CustomControlId" name="key.Value.CustomControlId" type="text" value="201922" />//value here should be "id" of the input field
I think the best approach for this situation would be to avoid the HtmlHelper methods in your template and use html controls instead:
#model EWMS_MVC.Classes.CustomControlModel
<label>#Model.LabelCaption</label>
<input id="#Model.CustomControlId" name="#Model.CustomControlName" type="text" value="#Model.Value" />

BeginForm in ChildAction uses wrong id

There is something simple I don't understand with ChildActions.
I've created a simple View for a model, that loads a child action with a form.
The child action has another model than its parent, with a different id property.
Html.HiddenFor(m => m.Id) still outputs the parents id, although #Model.id outputs the correct value!
Can't I reliably use the Helper methods in ChildActions, or is this a known bug?
HomeController
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new Models.HomeModel { id = 1, message = "bugmodel" };
return View(model);
}
[HttpGet]
[ChildActionOnly]
public ActionResult Child(int id)
{
var model = new Models.HomeChildModel { id = 100, parentId = id, childMessage = "My Child message" };
return PartialView(model);
}
[HttpPost]
[ActionName("Child")]
[ValidateAntiForgeryToken()]
public ActionResult ChildPost(Models.HomeChildModel model)
{
return RedirectToAction("Index");
}
}
Models
public class HomeModel
{
public int id { get; set; }
public string message { get; set; }
}
public class HomeChildModel
{
public int id { get; set; }
public int parentId { get; set; }
public string childMessage { get; set; }
}
Home view
#model ChildActionBug.Models.HomeModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Html.DisplayFor(m=>m.id)
#Html.DisplayFor(m=>m.message)
#Html.Action("Child", new { id = Model.id })
**Child view**
#model ChildActionBug.Models.HomeChildModel
<h3>Child here</h3>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m=>m.id)
#Html.HiddenFor(m=>m.parentId)
#Html.EditorFor(m=>m.childMessage)
<div>Child Model ID: #Model.id</div>
<button type="submit">Save</button>
}
Based on the answer given in the SO question I posted in the comment, you're better off explicitly creating the hidden fields
ASP.Net MVC Html.HiddenFor with wrong value
That's normal and it is how HTML helpers work. They first use the
value of the POST request and after that the value in the model. This
means that even if you modify the value of the model in your
controller action if there is the same variable in the POST request
your modification will be ignored and the POSTed value will be used.
So instead, hand craft the hidden fields:
<input type="hidden" name="Id" value="#Model.Id" />
<input type="hidden" name="ParentId" value="#Model.ParentId" />
<input type="hidden" name="ChildMessage" value="#Model.ChildMessage" />

Auto generate hidden inputs for all viewModel fields

Is it real to auto generate inputs for all hidden fields. I want something like this extestion method Html.AutoGenerateHiddenFor(viewmodel)
And output:
<input type="hidden" name="field1" value="123" />
<input type="hidden" name="field2" value="1234" />
<input type="hidden" name="field3" value="1235" />
You could use the MvcContrib's Html.Serialize method:
#using (Html.BeginForm())
{
#Html.Serialize(Model)
<button type="submit">OK</button>
}
and then inside your controller action that is receiving the postback:
[HttpPost]
public ActionResult SomeAction([Deserialize] MyViewModel model)
{
...
}
It uses classic WebForms's ViewState to serialize the model and emits a single hidden input field which will contain the serialized model. It kinda emulates the legacy ViewState.
An alternative solution would be to persist your model to your backend and then simply have a single hidden input field inside your form containing an unique id that will allow to retrieve the model back from this backend.
public class ModelToPersistBetweenFormSubmits()
{
public string field1 { get; set;}
public string field2 { get; set;}
public string field3 { get; set;}
public string field4 { get; set;}
public string GetHiddenFields(string excludeFields = "")
{
string[] excludeFieldList = excludeFields.Split(',');
string val = string.Empty;
System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (System.Reflection.PropertyInfo property in this.GetType().GetProperties())
{
if (excludeFieldList.Contains(property.Name))
{
continue;
}
else if (property.GetIndexParameters().Length > 0)
{
val = string.Empty; //sb.Append("Indexed Property cannot be used");
}
else
{
val = (property.GetValue(this, null) ?? "").ToString();
}
sb.Append(string.Format("<input type='hidden' id='{0}' name='{0}' value='{1}'/>", property.Name, val));
sb.Append(System.Environment.NewLine);
}
return sb.ToString();
}
}
//render hidden fields except for current inputs
#Html.Raw(Model.GetHiddenFields("field4,field3"))

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

MVC Radio Button Lists are not grouped when using the HtmlHelper class

Having trouble creating a list of radio buttons that are grouped together, in MVC 3 specifically, but this also applies to MVC 2.
The problem arises when radio buttons are generated using Html helpers and the model is part of an array.
Here is the cut down version of my code.
public class CollectionOfStuff {
public MVCModel[] Things { get; set }
}
/*This model is larger and represents a Person*/
public class MVCModel {
[UIHint("Hidden")]
public string Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
/*Assigned to new CollectionOfStuff property Things*/
var items = new[] {
new MVCModel() { Id="0" Name = "Name here" }, new MVCModel() { Id="1" Name = "Name there" }
}
My parent view
#model CollectionOfStuff
#for (int i = 0; i < Model.Things.Length; i++) {
#Html.EditorFor(m => m.Things[i]);
}
My view rendering individual MVCModel objects
#Model MVCModel
#{
var attr = new {
Checked = Model.IsSelected ? "checked=checked" : ""
};
}
#Html.RadioButtonFor(model => model, Model.Id, attr)
Produces this output:
<input type="radio" value="0" name="MVCModel[0]" id="MVCModel_0_" data-val-required="You need to choose" data-val="true" />
<input type="radio" value="1" name="MVCModel[1]" id="MVCModel_1_" data-val-required="You need to choose" data-val="true" />
The radio buttons are not grouped, however it has the obvious advantage of writing out the meta data for validation.
The other way is by calling:
#Html.RadioButton(name: "GroupName", value: Model.Id, isChecked: Model.IsSelected)
Produces:
<input type="radio" value="0" name="MVCModel[0].GroupName" id="MVCModel_0__GroupName">
<input type="radio" value="1" name="MVCModel[1].GroupName" id="MVCModel_1__GroupName">
Again, this doesn't produce the desired result. It's also missing the validation meta data.
Another other option is creating a custom template, but the problem with this approach is that all the meta data required for validation is not present.
Any ideas on how I can create grouped radio buttons or obtain meta data so I can create a template myself?
You haven't shown how does your view model look like but you could group them by some property. So let's take an example:
public class MyViewModel
{
[Required]
public string SomeProperty { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
View:
#model AppName.Models.MyViewModel
#using (Html.BeginForm())
{
<div>A: #Html.RadioButtonFor(x => x.SomeProperty, "a")</div>
<div>B: #Html.RadioButtonFor(x => x.SomeProperty, "b")</div>
#Html.ValidationMessageFor(x => x.SomeProperty)
<input type="submit" value="OK" />
}
Now if you want to preselect some radio simply set the property of the view model to the corresponding value of the radio instead of writing some ugly C# code in your views:
public ActionResult Index()
{
var model = new MyViewModel
{
SomeProperty = "a" // select the first radio
};
return View(model);
}
Obviously this technique works with any simple property type (not only strings) and with any number of radio buttons that could be associated to this property.

Resources