MVC dynamic form controls binding to model - asp.net-mvc

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

Related

Problem in showing ViewModel in Create form

I am learning how to use ViewModel to show the fields from 2 different models. I have one model containing the MsgTypeId, MsgType and MsgStatus and the another model OptStatus containing the StatusId, StatusName and StatusValue. The MsgStatus will be shown in form of drop down list and show all the values in OptStatus. Both models have a separate database table to store their values.
namespace theManager.Areas.Settings.Models
{
public class OptStatus
{
[Required]
[Key]
public int StatusId { get; set; }
[Required]
public string StatusName { get; set; }
[Required]
public char StatusValue { get; set; }
}
}
namespace theManager.Areas.Settings.Models
{
public class OptMsgType
{
[Required]
[Key]
public int MsgTypeId { get; set; }
[Required]
public string MsgType { get; set; }
[Required]
public string MsgStatus { get; set; }
}
}
I have created a ViewModel to show these fields in the Create form of OptMsgType. However, when I run the code, I got an error
"System.NullReferenceException: 'Object reference not set to an instance of an object.'"
I would like to ask if there is something wrong with my ViewModel. Thanks!
namespace theManager.Areas.Settings.ViewModels
{
public class OptMsgTypeCreateViewModel
{
public OptMsgType OptMsgType { get; set; }
public IEnumerable<SelectListItem> OptStatuses { get; set; }
}
}
OptMsgTypeController.cs
public IActionResult Create(int id)
{
var OptMsgTypeViewModel = new OptMsgTypeCreateViewModel();
OptMsgTypeViewModel.OptStatuses = _context.OptStatus.ToList().Select(x => new SelectListItem
{
Text = x.StatusName,
Value = x.StatusValue.ToString()
});
OptMsgTypeViewModel.OptMsgType = _context.OptMsgType.Where(a => a.MsgTypeId == id).FirstOrDefault();
//var v = _context.OptMsgType.Where(a => a.MsgTypeId == id).FirstOrDefault();
return View(OptMsgTypeViewModel);
}
I have problems in displaying the Create form which will show the fields declared in the ViewModel.
#model theManager.Areas.Settings.ViewModels.OptMsgTypeCreateViewModel
#{
ViewData["Title"] = "Create";
Layout = null;
}
<h2>Message Type Settings</h2>
#using (Html.BeginForm("Create","OptMsgType", FormMethod.Post, new { id= "popupForm" }))
{
if (Model != null && Model.OptMsgType.MsgTypeId > 0)
{
#Html.HiddenFor(a=>a.OptMsgType.MsgTypeId)
}
<div class="form-group">
<label>Message Type ID</label>
#Html.TextBoxFor(a=>a.OptMsgType.MsgTypeId,new { #class = "form-control" })
#Html.ValidationMessageFor(a=>a.OptMsgType.MsgTypeId)
</div>
<div class="form-group">
<label>Leave Type</label>
#Html.TextBoxFor(a => a.OptMsgType.MsgType, new { #class = "form-control" })
#Html.ValidationMessageFor(a => a.OptMsgType.MsgType)
</div>
<div class="form-group">
<label>Status</label>
#Html.DropDownListFor(model => model.OptStatuses, new SelectList(Model.OptStatuses, "Value", "Text"), htmlAttributes: new { #class = "form-control", id = "OptStatus" })
#Html.ValidationMessageFor(a => a.OptStatuses)
</div>
<div>
<input type="submit" value="Create" />
</div>
}
The System.NullReferenceException indicates that you are using a field without initializing it. It coulbe a problem with your view model or it could be a problem anywere else. For example from the code smaple is not possible to see where you initialize the context you are using to get the data, and that could be the cause of the exception you are getting.
Either way I would advise you to pay attention to yout IDE, it usualy indicates in which line adnd class the exception is being thown. If you navigate to that class at that line you will easily identify which field can be de cause of the exception.
Regarding your view model, its considered a good practice to always initialize the lists on your model on the constructor of your class. This way you can guarantee that they are already initialized when you try to use them.
So my sugestion would be to initialize your list on the constructor of your viewmodel
public OptMsgTypeCreateViewModel()
{
OptStatuses = new List<OptStatus>();
}
#George, thanks for the reply. Please try this then: instantiate your class in the viewmodel.
public class OptMsgTypeCreateViewModel
{
public OptMsgTypeCreateViewModel()
{
OptMsgType = new OptMsgType();
}
public OptMsgType OptMsgType { get; set; }
public IEnumerable<SelectListItem> OptStatuses { get; set; }
}
hi in action controller you should change this code:
OptMsgTypeViewModel.OptStatuses = _context.OptStatus.ToList().Select(x => new SelectListItem
{
Text = x.StatusName,
Value = x.StatusValue.ToString()
});
I think _context.OptStatus.ToList() in null so you get this exception. change code to this:
OptMsgTypeViewModel.OptStatuses =new list<SelectListItem>();
var temp= _context.OptStatus.ToList();
if(temp!=null&&temp.count()>0)
{
OptMsgTypeViewModel.OptStatuses = temp.Select(x => new SelectListItem
{
Text = x.StatusName,
Value = x.StatusValue.ToString()
}).tolist();
}
EDIT:
I think this object "Model.OptMsgType" is null
change code in view like this:
if (Model != null && Model.OptMsgType!=null && Model.OptMsgType.MsgTypeId > 0)
{
#Html.HiddenFor(a=>a.OptMsgType.MsgTypeId)
}

Break line in view from list in MVC 5

I have a List containing string objects that should be displayed in the view. But i have problems getting the output correctly. In the list there are words like Computer, Screen, Mouse. Now it´s displayed like ComputerScreenMouse and want it displayed like
Computer
Screen
Mouse
I´ve checked the object during run-time and it´s adding the strings correctly like
[0] Computer
[1] Screen
[2] Mouse
This is the code that I´m using
#Html.DisplayFor(m => Model.MyParts, new { htmlAttributes = new { #class = "form-control " } })
I've tried a couple different stuff with foeach loops but nothing seems to work. If i use the #Html.Raw and put in a br tag it prints out the list like this:
ComputerScreenMouse
ComputerScreenMouse
ComputerScreenMouse
#foreach (var item in Model.MyParts)
{
#Html.Raw(Html.DisplayFor(m => Model.MyParts, new { htmlAttributes = new { #class = "form-control " } }) + "</br>")
}
The model looks like this. In the view I´m using #modelGlossary_mvc.Models.Parts
public class DoTestModel
{
public string Stuff{ get; set; }
public string OtherStuff { get; set; }
public List<Parts> Parts { get; set; }
}
public class Parts
{
public List<string> MyParts{ get; set; }
public List<string> SpareParts{ get; set; }
public int NumberOfParts { get; set; }
}
Display(For) / Editor(For) really aren't ideal for List<string> properties. They should be reserved for specialized display and editing templates, or properties that have the [Display] attribute.
Your model property MyParts is a simple list of strings.
public List<string> MyParts{ get; set; }
Keep it simple, output each string (e.g. in an un-ordered list):
<ul>
#foreach(var part in Model.MyParts)
{
<li>#part</li>
}
</ul>
Ok, but what if the MyParts property is a list of PartModel objects? This is where the MVC DisplayFor / EditorFor shines.
PartModel
public class PartModel
{
public string Name { get; set; }
public int Quantity { get; set; }
}
View Model
// your view model
public class Parts
{
// now a list of PartModels not strings
public List<PartModel> MyParts{ get; set; }
public List<string> SpareParts{ get; set; }
public int NumberOfParts { get; set; }
}
Razor View
#model Parts
#Html.DisplayFor(m => m.MyParts)
Display Template for PartModel
#model PartModel
<div>
<span>#Html.DisplayFor(m => m.Name)</span>
<span>#Html.DisplayFor(m => m.Quantity)</span>
</div>
This file can be located in couple places. It's up to you based on how much you need to share it throughout the project:
\Views\Shared\DisplayTemplates\PartModel.cshtml
\Views\\DisplayTemplates\PartModel.cshtml
Depending on how you are wrapping your output in HTML will depend on the outcome. Are you using this in a form? as i generally only use form-control for fields.
Since your output is not HTML you need to try this if you need the form-control.
#foreach (var item in Model.MyParts)
{
#Html.DisplayFor(modelItem => item, new { htmlAttributes = new { #class = "form-control " } })
<br />
}
Without form-control to just display the list:
#foreach (var item in Model.MyParts)
{
#Html.DisplayFor(modelItem => item )
<br />
}
And depending how you have your HTML wrapper you may or may not need the <br />

Retrieve an array in viewmodel

I'm trying to create a page for editing my attachments.
The Attachment Model:
public class Attachment
{
...
private IList<JSONI18NText> titles = new List<JSONI18NText>();
private IList<JSONI18NText> descriptions= new List<JSONI18NText>();
...
public virtual IList<JSONI18NText> Titles
{
get { return titles; }
set { this.titles = value; }
}
public virtual IList<JSONI18NText> Descriptions
{
get { return descriptions; }
set { this.descriptions= value; }
}
The JSONI18NText Model:
public class JSONI18NText
{
public int LanguageId { get; set; }
public string Text { get; set; }
}
The Attachment ViewModel:
public class AttachmentModel
{
public AttachmentModel() { }
public AttachmentModel(Attachment at) {
...
this.Titles = new List<JSONI18NTextModel>();
this.Descriptions = new List<JSONI18NTextModel>();
foreach (JSONI18NText title in at.Titles)
{
this.Titles.Add(new JSONI18NTextModel(title, "Title"));
}
foreach (JSONI18NText description in at.Descriptions)
{
this.Descriptions.Add(new JSONI18NTextModel(description, "Description"));
}
}
[Display(Name = "Title", Description = "Title of the file")]
public IList<JSONI18NTextModel> Titles { get; set; }
[Display(Name = "Description", Description = "Description of the attachment file")]
[DataType(DataType.MultilineText)]
public IList<JSONI18NTextModel> Descriptions { get; set; }
The JSONI18NText ViewModel:
public class JSONI18NTextModel
{
public JSONI18NTextModel() { }
public JSONI18NTextModel(JSONI18NText jsonI18nText)
{
this.LanguageId = jsonI18nText.LanguageId;
this.Text = jsonI18nText.Text;
}
[HiddenInput(DisplayValue = false)]
public int LanguageId { get; set; }
public string Text { get; set; }
}
Now, what I'm trying to achieve is an edit form with a tabbed list for the languages: for example two tabs, one for English and one for Italian, if you click on each tab you read the input value of title and description for that particular language.
Everything works like a charm: I used a view script with two partial views, one for the lists and another for the JSONI18NTextModel:
Edit.cshtml:
...
#Html.EditorFor(model => model.Titles, "EditLabels")
#Html.EditorFor(model => model.Descriptions, "EditLabels")
...
EditLabels.cshtml:
#model List<CR2.Web.Areas.Admin.Models.JSONI18NTextModel>
#using CR2.Web.Infrastructure
#using CR2.Web.Areas.Admin.Models
#if(Model.Count() == 1)
{
#Html.EditorFor(model => model[0], "EditLabel");
}
else
{
for(int i = 0; i < Model.Count(); ++i)
{
<div>
<ul>
#Html.EditorFor(model => model[i], "EditLabel")
</ul>
</div>
}
}
EditLabel.cshtml:
#model CR2.Web.Areas.Admin.Models.JSONI18NTextModel
#using CR2.Web.Infrastructure
<li>
#Html.HiddenFor(model => model.LanguageId)
<div>
#Html.LabelWithTooltip(model => model.Text)
</div>
<div>
#Html.EditorFor(model => model.Text)
#Html.ValidationMessageFor(model => model.Text)
</div>
</li>
When I render it, it builds fields with names like "Titles.[0].Text", which is perfect I think...
The problem comes when I submit the form: "Titles" and "Descriptions" aren't populated in the AttachmentModel...(everything else is populated)
Any ideas? What am I doing wrong?
Thanks a lot!!!
Iterating over the list and populating each value should be something like:
#foreach (JSONI18NTextModel item in model)
{
Html.EditorFor(i => item.Titles, "EditLabels");
Html.EditorFor(i => item.Descriptions, "EditLabels");
}
Then I assume you are posting only one model to the controller action:
[HttpPost]
public ActionResult Edit(JSONI18NTextModel model)
{
// The only Model you are posting should be accessible here.
}
If you have a list of models on your page, then you may want to create a Details view where you access only one model at a time and you post only that model to the controller.

How to pass an entire ViewModel back to the controller

I have a ViewModel that contains two objects:
public class LookUpViewModel
{
public Searchable Searchable { get; set; }
public AddToSearchable AddToSearchable { get; set; }
}
The two contained models look something like this:
public class Searchable
{
[Key]
public int SearchableId { get; set; }
public virtual ICollection<AddToSearchable> AddedData { get; set; }
}
public class AddToSearchable
{
[Key]
public int AddToSearchableId { get; set;}
[Required]
public int SearchableId { get; set; }
[Required]
public String Data { get; set; }
[Required]
public virtual Searchable Searchable { get; set; }
}
I have a view that uses my LookUpViewModel and receives input to search for a SearchableId. If the Searchable object is found, a LookUpViewModel object is created and passed to the View. The view then displays editor fields for AddToSearchable.Data. Once submitted, I want the LookUpViewModel to be passed to an action method to handle all the back-end code. The only problem is, the LookUpViewModel passed to my action method contains a null reference to Searchable and a valid reference to AddToSearchable.. i.e. I'm missing half of my data.
Here's an example of what my view looks like:
#model HearingAidTrackingSystem.ViewModels.LookUpViewModel
#using (Html.BeginForm("LookUp", "Controller", "idStr", FormMethod.Post))
{
<input type="text" name="idStr" id="idStr"/>
<input type="submit" value="Search" />
}
#if (Model.Searchable != null && Model.AddToSearchable != null)
{
using (Html.BeginForm("AddMyStuff", "Controller"))
{
Html.HiddenFor(model => model.Searchable.SearchableId);
Html.HiddenFor(model => model.Searchable.AddedData);
Html.HiddenFor(model => model.AddToSearchable.AddToSearchableId);
Html.HiddenFor(model => model.AddToSearchable.SearchableId);
Html.HiddenFor(model => model.AddToSearchable.Searchable);
<div class="editor-field">
#Html.EditorFor(model => model.AddToSearchable.Data)
#Html.ValidationMessageFor(model => model.AddToSearchable.Data);
</div>
<input type="submit" value="Submit" />
}
}
and here are my action methods:
public ActionResult LookUp(LookUpViewModel vm)
{
return View(vm);
}
[HttpPost]
public ActionResult LookUp(string idStr)
{
int id = /*code to parse string to int goes here*/;
Searchable searchable = dal.GetById(id);
LookUpViewModel vm = new LookUpViewModel { Searchable = searchable,
AddToSearchable = new AddToSearchable() };
//When breakpoint is set, vm contains valid references
return View(vm);
}
[HttpPost]
public ActionResult AddMyStuff(LookUpViewModel vm)
{
//**Problem lies here**
//Do backend stuff
}
Sorry for the lengthy post. I tried my best to keep it simple. Any suggestions you may have.. fire away.
Two methods to fix it:
You can add to do HiddenFor() for all properties of Model.Searchable.
You can use serialization to transfer your Model.Searchable into text presentation and repair it from serialized form in controller.
Update: The problem is: You need to use #Html.HiddenFor(), not Html.HiddenFor();.

My view won't bind with my model in 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.

Resources