EditorTemplate + Model not being returned on POST - asp.net-mvc-3

I can't seem to figure this out, it is driving me crazy!
Essentially, I have a list of rows I need to display with one drop down list per row.
I have a view model:
public class UserMembershipViewModel:BaseViewModel
{
public List<ProgramMembership> ProgramMembership { get; set; }
}
In my parent view I have, as you can see I am using an editor template which is located in "/Views/Shared/EditorTemplates/ProgramMembership.cshtml":
#using AcnCS.Model
#model AcnCS.Model.ViewModels.User.UserMembershipViewModel
#{
ViewBag.PageHeader = "Membership for " + Model.User.FullName;
ViewBag.PageTitle = "Membership for " + #Model.User.FullName;
ViewBag.HideNav = true;
}
#if (Model.ProgramMembership != null)
{
<div class="row-fluid">
<div class="span12">
<div id="permissions">
#using (Html.BeginForm())
{
<table class="table table-bordered">
<thead>
<tr>
<td>Program</td>
<td>Effective Membership?</td>
<td>Permission Type</td>
</tr>
</thead>
#Html.EditorFor(m => Model.ProgramMembership, "ProgramMembership")
</table>
<input type="submit" class="btn btn-primary" value="Save Changes"/>
}
</div>
</div>
</div>
}
My Editor template (ProgramMembership.cshtml) is:
#using AcnCS.Model
#model List<AcnCS.Model.ProgramMembership>
#foreach(ProgramMembership membership in Model)
{
<tr>
<td>#membership.ProgramName</td>
<td>
#if (membership.IsMember)
{
<span class="label label-success">#membership.IsMember</span>
}
else
{
#membership.IsMember
}
</td>
<td>#Html.DropDownListFor(x => membership.PermissionType, membership.PermissionTypes)</td>
</tr>
}
Everything is being displayed properly, but when I submit, my model object is null, even the ProgramMembership property in the model is null:
[HttpPost]
public ActionResult Membership(UserMembershipViewModel model)
{
// model IS NULL!!
return View(model);
}
Any help would be greatly appreciated!

I would pluralize the Property name since it is a collection, for better readability
public class UserMembershipViewModel:BaseViewModel
{
public List<ProgramMembership> ProgramMemberships { get; set; }
}
and you dont need a Loop inside your EditorTemplate file
#model AcnCS.Model.ProgramMembership
<tr>
<td>#membership.ProgramName</td>
<td>
#if (membership.IsMember)
{
<span class="label label-success">#membership.IsMember</span>
}
else
{
#membership.IsMember
}
</td>
<td>#Html.DropDownListFor(x => membership.PermissionType, membership.PermissionTypes)</td>
</tr>
In your main view,call your EditorTemplate like this
#Html.EditorFor(m=>m.ProgramMemberships)

Related

ASP.NET Core 6 MVC : HTML table to view model collection

Starting up with ASP.NET Core 6 MVC and in my case I have one view which lists few properties of one object and then some other for their children in a editable table (user can edit the values)
View model has the properties and an IEnumerable of the children:
public class MyObjectViewModel
{
public String Id { get; set; }
public String Descr { get; set; }
public IEnumerable<ChildrenObject> Children { get; set; }
public MyObjectViewModel()
{
Children = Enumerable.Empty<ChildrenObject>();
}
public class ChildrenObject
{
public String? Id { get; set; }
public String? Name { get; set; }
}
}
All under the same form:
#using (Html.BeginForm("Save", "Controller", FormMethod.Post))
{
<input type="submit" value="SAVE"/>
<br />
#Html.LabelFor(model => model.Id)
#Html.TextBoxFor(model => model.Id, new { #readonly = "readonly" })
<br />
#Html.LabelFor(model => model.Descr)
#Html.EditorFor(model => model.Descr)
<br />
<table>
<tbody>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
#foreach (var child in Model.Children)
{
<tr>
<td>#child.Id</td>
<td><input class="ef-select" type="text" value="#child.Name"></td>
</tr>
}
</tbody>
</table>
}
So when the button is pressed the data is dumped back to the model to perform the SAVE action in the controller.
All ok for the simple fields (I can get the data in the controller as the view model), but not sure how to accomplish it with the table / children property...
Any simple way without needing to use JS to serialise or pick up the data from the table?
Thanks
If you want to pass id and name of ChildrenObject in table,you can try to add hidden inputs for the Id,and set name attribute for name inputs:
#{var count = 0; }
#foreach (var child in Model.Children)
{
<tr>
<td>
#child.Id
<input value="#child.Id" name="Children[#count].Id" />
</td>
<td><input class="ef-select" type="text" value="#child.Name" name="Children[#count].Name"></td>
</tr>
count++;
}

How can I make a loop to repeat my method for each item?

there is my issue. In a controller, I have this method which export my view in a file when I click a button.
public ActionResult Export(string searchString, int searchOrder = 0)
{
var user = from m in db.Orders select m;
if (!String.IsNullOrEmpty(searchString))
{
user = user.Where(s => s.ClientID.Contains(searchString));
}
Response.AddHeader("Content-Type", "application/vnd.ms-excel");
return this.View(user);
}
My Index view :
#model IEnumerable<MyApp.Models.Order>
#{
ViewBag.Title = "Index";
}
<h2>Orders Historic</h2>
<div id="orderDiv">
#using (Html.BeginForm("Export", "Historic", FormMethod.Get))
{
<p>
Generate Order with ClientID :&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp
#Html.TextBox("searchString")
<input type="submit" value="GENEREMOI" />
</p>
}
</div>
And my Export view :
#model IEnumerable<KrysGroup.Models.Order>
<table cellpadding="3" cellspacing="3">
<tr>
<td width="12%" align="center">
Client Name/ID
</td>
<td width="15%" align="center">
N° Order
</td>
OTHER TD....
</tr>
#foreach (var item in Model)
{
TimeSpan result = DateTime.Now - item.OrderDate;
if (result.Days < 31)
{
<tr border="1" bgcolor="#Odd">
<td> #Html.DisplayFor(modelItem => item.Username) </td>
<td> #Html.DisplayFor(modelItem => item.OrderId) </td>
<td>
<ul style="list-style-type:none; padding:0; margin:0">
#if (item.OrderDetails != null)
{
foreach (var o in item.OrderDetails)
{
if (o.Pack == null)
{
<li> #Html.DisplayFor(modelItem => o.Product.Name) </li>
}
else
{
<li> <text>Pack</text> #Html.DisplayFor(modelItem => o.Pack.Name) </li>
}
}
}
</ul>
</td>
OTHER TD...
</table>
So, in my view, I inform in a textbox a ClientID and when I click the button, it export in a file all the fields in my table with this ClientID.
I would like to automate this action, that is to say I would like write a method or something for, when I click on the button, it executes this export() method for each clientId it meet in my table.
I hope I was clear enough, sorry for my english..
Thanks for your answers, links, tips whatever.
You can write a partial view typed to a list of Id's that loads when you click the button.
So in the controller you have a method converts what you want and returns a view that is typed to the conversion of your model.
In your main view you have a div that will be populated with a partial view after the click-event of the original button.
#Ajax.ActionLink("Name", "NameOfTheAction", "NameOfTheController",
new { id = itemId },
new AjaxOptions { HttpMethod = "Get", UpdateTargetId = "divInMainView", OnSuccess = "Do Something (js)" },
new { html-props })
How to populate is up to you, in your view

How to pass a list of objects instead of one object to a POST action method

I have the following GET and POST action methods:-
public ActionResult Create(int visitid)
{
VisitLabResult vlr = new VisitLabResult();
vlr.DateTaken = DateTime.Now;
ViewBag.LabTestID = new SelectList(repository.FindAllLabTest(), "LabTestID", "Description");
return View();
}
//
// POST: /VisitLabResult/Create
[HttpPost]
public ActionResult Create(VisitLabResult visitlabresult, int visitid)
{
try
{
if (ModelState.IsValid)
{
visitlabresult.VisitID = visitid;
repository.AddVisitLabResult(visitlabresult);
repository.Save();
return RedirectToAction("Edit", "Visit", new { id = visitid });
}
}
catch (DbUpdateException) {
ModelState.AddModelError(string.Empty, "The Same test Type might have been already created,, go back to the Visit page to see the avilalbe Lab Tests");
}
ViewBag.LabTestID = new SelectList(repository.FindAllLabTest(), "LabTestID", "Description", visitlabresult.LabTestID);
return View(visitlabresult);
}
Currently the view display the associated fields to create only one object,, but how i can define list of objects instead of one object to be able to quickly add for example 10 objects at the same “Create” request.
My Create view look like:-
#model Medical.Models.VisitLabResult
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#section scripts{
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>VisitLabResult</legend>
<div class="editor-label">
#Html.LabelFor(model => model.LabTestID, "LabTest")
</div>
<div class="editor-field">
#Html.DropDownList("LabTestID", String.Empty)
Your viewModel
public class LabResult
{
public int ResultId { get; set; }
public string Name { get; set; }
//rest of the properties
}
Your controller
public class LabController : Controller
{
//
// GET: /Lab/ns
public ActionResult Index()
{
var lst = new List<LabResult>();
lst.Add(new LabResult() { Name = "Pravin", ResultId = 1 });
lst.Add(new LabResult() { Name = "Pradeep", ResultId = 2 });
return View(lst);
}
[HttpPost]
public ActionResult EditAll(ICollection<LabResult> results)
{
//savr results here
return RedirectToAction("Index");
}
}
Your view
#model IList<MvcApplication2.Models.LabResult>
#using (Html.BeginForm("EditAll", "Lab", FormMethod.Post))
{
<table>
<tr>
<th>
ResultId
</th>
<th>
Name
</th>
</tr>
#for (int item = 0; item < Model.Count(); item++)
{
<tr>
<td>
#Html.TextBoxFor(modelItem => Model[item].ResultId)
</td>
<td>
#Html.TextBoxFor(modelItem => Model[item].Name)
</td>
</tr>
}
</table>
<input type="submit" value="Edit All" />
}
Your view will be rendered as follows, this array based naming convention makes it possible for Defaultbinder to convert it into ICollection as a first parameter of action EditAll
<tr>
<td>
<input name="[0].ResultId" type="text" value="1" />
</td>
<td>
<input name="[0].Name" type="text" value="Pravin" />
</td>
</tr>
<tr>
<td>
<input name="[1].ResultId" type="text" value="2" />
</td>
<td>
<input name="[1].Name" type="text" value="Pradeep" />
</td>
</tr>
If I understand your question correctly,
you want to change your view to be a list of your model object #model List, then using a loop or however you wish to do it, create however many editors you need to for each object
then in your controller your receiving parameter of create will be a list of your model instead too.

Posting to a list<modeltype> MVC3

I am trying to get my view to post a List back to the action however it keeps coming in as null.
So my Model has a List of WeightEntry objects.
Exercise Model
public class Exercise
{
public List<WeightEntry> Entries { get; set; }
public int ExerciseID { get; set; }
public int ExerciseName { get; set; }
}
WeightEntry Model
public class WeightEntry
{
public int ID { get; set; }
public int Weight { get; set; }
public int Repetition { get; set; }
}
My View contains the ExerciseName and a forloop of WeightEntry objects
#model Mymvc.ViewModels.Exercise
...
<span>#Model.ExerciseName</span>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<table class="left weight-record">
<tr>
<th>Reps</th>
<th>Weight</th>
</tr>
#foreach (var item in Model.Entries)
{
<tr>
<td>
#Html.EditorFor(x => item.Repetition)
</td>
<td>
#Html.EditorFor(x => item.Weight)
</td>
</tr>
}
</table>
<input type="submit" value="Save" />
}
The Controller Action (Post) Does nothing at the moment. I am just trying to get the binding working before I add the save code.
[HttpPost]
public ActionResult WeightEntry(Exercise exercise)
{
try
{
//Add code here to save and check isvalid
return View(exercise);
}
catch
{
return View(exercise);
}
}
I have seen a few little tricks with adding a numerator to the form elements' names used in MVC2 but I was wondering if MVC3 was any different? I was hoping it would all bind nicely with ID's being 0 or null but instead the whole List is null when I inspect it after the form posts. Any help is appreciated.
Thanks.
Replace the following loop:
#foreach (var item in Model.Entries)
{
<tr>
<td>
#Html.EditorFor(x => item.Repetition)
</td>
<td>
#Html.EditorFor(x => item.Weight)
</td>
</tr>
}
with:
#for (var i = 0; i < Model.Entries.Count; i++)
{
<tr>
<td>
#Html.EditorFor(x => x.Entries[i].Repetition)
</td>
<td>
#Html.EditorFor(x => x.Entries[i].Weight)
</td>
</tr>
}
or even better, use editor templates and replace the loop with:
#Html.EditorFor(x => x.Entries)
and then define a custom editor template that will automatically be rendered for each element of the Entries collection (~/Views/Shared/EditorTemplates/WeightEntry.cshtml):
#model WeightEntry
<tr>
<td>
#Html.EditorFor(x => x.Repetition)
</td>
<td>
#Html.EditorFor(x => x.Weight)
</td>
</tr>
The the generated input elements will have correct names and you will be able to successfully fetch them back in your POST action.

MVC3 textbox submits null value?

I am creating a dynamic list of text boxes. when the user submits the value in the fields come back null. I think I'm missing something.
This is my product model:
public class EnqProduct
{
public string Id { get; set; }
public string Product { get; set; }
public string Quantity { get; set; }
}
This is the page model which includes a list of the above.
public IList<EnqProduct> EnqProduct { get; set; }
This is how I am setting the model:
IList<EnqProduct> items2 = Session["enquiry"] as IList<EnqProduct>;
var EnquiryModel = new Enquiry {
EnqProduct = items2
};
return View(EnquiryModel);
and this is how I display the fields:
foreach (var item in Model.EnqProduct)
{
<tr>
<td>
<span class="editor-field">
#Html.TextBox(item.Id, item.Product)
#Html.ValidationMessageFor(m => m.A1_Enquiry)
</span>
<br><br>
</td>
<td>
<span id = "field" class="editor-field">
#Html.TextBox(item.Id, item.Quantity)
</span>
<br><br>
</td>
</tr>
}
When the user submits the fields go back to the controller null?
I would recommend you using editor templates and replace your foreach loop with the following:
#model Enquiry
<table>
<thead>
<tr>
<th>product name</th>
<th>quantity</th>
</tr>
</thead>
<tbody>
#Html.EditorFor(x => x.EnqProduct)
</tbody>
</table>
and then define an editor template which will automatically be rendered for each element of the EnqProduct collection (~/Views/Shared/EditorTemplates/EnqProduct.cshtml):
#model EnqProduct
<tr>
<td>
#* We include the id of the current item as hidden field *#
#Html.HiddenFor(x => x.Id)
<span class="editor-field">
#Html.EditorFor(x => x.Product)
#Html.ValidationMessageFor(x => x.Product)
</span>
</td>
<td>
<span id="field" class="editor-field">
#Html.EditorFor(x => x.Quantity)
</span>
</td>
</tr>
Now when you submit the form you will get correct values:
public class HomeController: Controller
{
public ActionResult Index()
{
var model = new Enquiry();
model.EnqProduct = ...
return View(model);
}
[HttpPost]
public ActionResult Index(Enquiry model)
{
// the model.EnqProduct will be correctly populated here
...
}
}
As far as the correct wire format that the default model binder expects for your input fields I would recommend you taking a look at the following article. It will allow you to more easily debug problems in the feature when some model is not properly populated. It suffice to look with FireBug and the name of the values being POSTed and you will immediately know whether they are OK or KO.

Resources