Reputation: 4692
I have two forms on one View executing two separate Action methods in one Controller.
The first form (frmRefresh) is responsible for getting data and displaying it on the form so the user can pick certain checkboxes. Once submitted, the data is returned just fine in the ViewModel and is properly displayed on the form. 11 records for the Templates and 3 records for the Guarantors are displyaed as checkboxes on the form.
The second form (frmProcess), is responsible for taking the data on the form (that came back from the first post above). The user makes selections on the screen and processes it against some logic in the Controller. I have List objects in the model and don't suppose I can use the FormCollection to process the data because of the complex objects. Basically, they are a collection of checkboxes. I really need to use the data that should be submitted in the Model because of processing in the Controller for that data.
When submitting the second form, I realize that the loanid & ddl will not be available unless I put them in a hidden field (because they are in a separate form) --- that's fine. What I'm having a great deal of difficulty in understanding is when I submit the second form (frmProcess), why doesn't the model view binder take the data from the form, put it in the model and submit it to my GeneratePDF action method.?
Number one, I really need some help in understanding why this is happening and number two, I really need a soltution which takes my model data from the form to the action method and processes it. As you can see in the Controller, at the end of the code, I'm enumerating the Templates in the ViewModel to process the data.
Please help, as I am totally stuck on this at work and they are depending on me for this. I just don't get why the model binder doesn't take the values on the form and submit it to the action method for processing. It appears I'm missing something to allow the data to get back into the Model upon submission.
Below is my pertinent code: ViedwModel
public partial class ViewModelTemplate_Guarantors
{
public int SelectedTemplateId { get; set; }
public IEnumerable<PDFTemplate> Templates { get; set; }
public int SelectedGuarantorId { get; set; }
public IEnumerable<tGuarantor> Guarantors { get; set; }
public string LoanId { get; set; }
public string SelectedDeptText { get; set; }
public string SelectedDeptValue { get; set; }
public string LoanType { get; set; }
public bool ShowTemps { get; set; }
public string Error { get; set; }
public string ErrorT { get; set; }
public string ErrorG { get; set; }
public bool ShowGeneratePDFBtn { get; set; }
}
View
@model PDFConverterModel.ViewModels.ViewModelTemplate_Guarantors
@{
ViewBag.Title = "BHG :: PDF Generator";
}
<h2>@ViewBag.Message</h2>
<div>
<table style="width: 1000px">
<tr>
<td colspan="5">
<img alt="BHG Logo" src="~/Images/logo.gif" />
</td>
</tr>
@using (Html.BeginForm("Refresh", "Home", FormMethod.Post, new { id = "frmRefresh" })) { <tr>
<td>
@*@(Html.Kendo().NumericTextBox<int>()
.Name("txtLoanID")
.Placeholder("Enter numeric value")
)*@
@Html.LabelFor(model => model.LoanId)
@Html.TextBoxFor(model => model.LoanId)
@Html.ValidationMessageFor(model => model.LoanId)
</tr>
<tr>
<td>@Html.LabelFor(model => model.LoanType)
@Html.TextBox("SBA", "SBA")
@Html.ValidationMessageFor(model => model.LoanType)
@*@Html.TextBoxFor(model => model.LoanType)*@
</td>
<td>
<label for="ddlDept">Department:</label>
@(Html.Kendo().DropDownListFor(model => model.SelectedDeptText)
.Name("ddlDept")
.DataTextField("DepartmentName")
.DataValueField("DepartmentID")
.Events(e => e.Change("Refresh"))
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetDepartments", "Home");
});
})
)
@Html.ValidationMessageFor(model => model.SelectedDeptText)
</td>
</tr>
<tr>
<td colspan="3">
<input type="submit" id="btnRefresh" value='Refresh' />
</td>
</tr>
}
@using (Html.BeginForm("GeneratePDF", "Home", FormMethod.Post, new { id = "frmProcess" })) { if (Model.ShowGeneratePDFBtn == true)
{
if (Model.ErrorT != string.Empty)
{
<tr>
<td colspan="5">
<u><b>@Html.Label("Templates:")</b></u>
</td>
</tr>
<tr>
@foreach (var item in Model.Templates)
{
<td>
@Html.CheckBoxFor(model => item.IsChecked)
@Html.DisplayFor(model => item.TemplateName)
</td>
}
</tr>
}
else
{
Model.Error = Model.ErrorT;
}
if (Model.ErrorG != string.Empty)
{
<tr>
<td colspan="5">
<u><b>@Html.Label("Guarantors:")</b></u>
</td>
</tr>
<tr>
@foreach (var item in Model.Guarantors)
{
<td>
@Html.CheckBoxFor(model => item.isChecked)
@Html.DisplayFor(model => item.GuarantorFirstName) @Html.DisplayFor(model => item.GuarantorLastName)
</td>
}
</tr>
}
else
{
Model.Error = Model.ErrorG;
}
<tr>
<td>
<input type="submit" id="btnGeneratePDF" value='Generate PDF' />
</td>
</tr>
<tr>
<td colspan="5">
@Model.Error
</td>
</tr>
}
} </table>
</div>
<script type="text/javascript">
$('btnRefresh').on('click', '#btnRefresh', function () {
Refresh();
});
function Refresh() {
var LoanID = $("#LoanID").val();
if (LoanID != "") {
document.forms["frmTemps"].submit();
}
}
</script>
Controller
public ActionResult Index(ViewModelTemplate_Guarantors model)
{
ViewBag.Error = "";
model.ShowGeneratePDFBtn = false;
return View("Index", model);
}
// used for the first form "frmRefresh" [HttpPost] public ActionResult Refresh(ViewModelTemplate_Guarantors model) {
try
{
model.Error = string.Empty;
bool dbHasRows = db.ChkLoanFields(Convert.ToInt32(model.LoanId));
if (!dbHasRows)
{
model.ShowGeneratePDFBtn = false;
model.Error = "Details not available for this LoanId.";
return View("Index",model);
}
else
{
int TemplateCnt = 0;
int GuarantorCnt = 0;
//todo - modify 2nd & 3rd parms instead of hardcoding
ViewModelTemplate_Guarantors tg = db.SelectViewModelTemplate_Guarantors(Convert.ToInt32(model.LoanId), "All", "All", out TemplateCnt, out GuarantorCnt);
if (TemplateCnt > 0)
model.Templates = tg.Templates;
else
model.ErrorT = "Templates not available for this LoanType.";
if (GuarantorCnt > 0)
model.Guarantors = tg.Guarantors;
else
model.ErrorG = "Guarantors not available for this LoanId.";
model.ShowGeneratePDFBtn = true;
// right before the return here, the model is full of data. return View("Index", model); }
}
catch (Exception ex)
{
throw ex;
}
} [HttpPost] // when I check the data here (via submission from the "frmProcess" form, the model is completely empty, null, etc... WHY???? // i NEED the model data here to perform processing in this action method. public ActionResult GeneratePDF(ViewModelTemplate_Guarantors model) {
try
{
int FolderNo, GuarantorNum = 0;
string Folder, LoanFolder = String.Empty;
string FormId, FormName, GuarantorName = String.Empty;
int LoanId = Convert.ToInt32(model.LoanId);
LoanFolder = LoanId.ToString().PadLeft(8, '0');
//To calculate FolderId based on LoanId
if ((LoanId > 0) && (LoanId < 99000))
{
FolderNo = ((int)(LoanId / 10000) * 10000);
}
else
{
FolderNo = ((int)(LoanId / 1000) * 1000);
}
Folder = ((int)FolderNo).ToString();
Folder = Folder.PadLeft(8, '0');
//todo - 2nd parm SelectedValue of dept
List<sSRPTFundexDocCodes1_Test_Result> sSRPTFundexDocCodes1 = db.GetFormValues(Convert.ToInt32(model.LoanId), (model.SelectedDeptValue));
if (sSRPTFundexDocCodes1 != null)
{
foreach (PDFTemplate template in model.Templates) {
if (template.IsChecked == true) {
TemplateName not showing up in model after post. This works fine... The values (the checkboxes and the corresponding names are displyaed on the form.
However, when posting the GeneratePDF button, all I see in the model is if the Checkbox is checked (which is great). After playing around with many of the following statements: (ValueFor, DisplayFor, LabelFor, EditorFor, etc), the value coming back for the Template name is blank. I need the name of the template that was checked in correspondance with the checkbox.
@Html.ValueFor(model => Model.Templates[i].TemplateName)
How can I accomplish this? Thanks ahead of time... Below is my updated code.
ViewModel public partial class ViewModelTemplate_Guarantors
{
public ViewModelTemplate_Guarantors()
{
Templates = new List<PDFTemplate>();
Guarantors = new List<tGuarantor>();
}
public int SelectedTemplateId { get; set; }
public List<PDFTemplate> Templates { get; set; }
public int SelectedGuarantorId { get; set; }
public List<tGuarantor> Guarantors { get; set; }
public string LoanId { get; set; }
public string SelectedDeptText { get; set; }
public string SelectedDeptValue { get; set; }
public string LoanType { get; set; }
public string Error { get; set; }
public string ErrorT { get; set; }
public string ErrorG { get; set; }
public bool ShowGeneratePDFBtn { get; set; }
}
Pertinet part of View:
if (Model.ShowGeneratePDFBtn == true)
{
if (Model.ErrorT == string.Empty)
{
<tr>
<td colspan="5">
<u><b>@Html.Label("Templates:")</b></u>
</td>
</tr>
<tr>
@for (int i = 0; i < Model.Templates.Count; i++)
{
<td>
@Html.CheckBoxFor(model => Model.Templates[i].IsChecked)
@Html.ValueFor(model => Model.Templates[i].TemplateName) </td>
}
</tr>
}
else
{
<tr>
<td>
<b>@Html.DisplayFor(model => Model.ErrorT)</b>
</td>
</tr>
}
if (Model.ErrorG == string.Empty)
{
<tr>
<td colspan="5">
<u><b>@Html.Label("Guarantors:")</b></u>
</td>
</tr>
<tr>
@for (int i = 0; i < Model.Guarantors.Count; i++)
{
<td>
@Html.CheckBoxFor(model => Model.Guarantors[i].isChecked)
@Html.ValueFor(model => Model.Guarantors[i].GuarantorFirstName) @Html.ValueFor(model => Model.Guarantors[i].GuarantorLastName) </td>
}
</tr>
}
else
{
<tr>
<td>
<b>@Html.DisplayFor(model => Model.ErrorG)</b>
</td>
</tr>
}
}
<tr>
<td colspan="3">
<input type="submit" name="submitbutton" id="btnRefresh" value='Refresh' />
</td>
@if (Model.ShowGeneratePDFBtn == true)
{
<td>
<input type="submit" name="submitbutton" id="btnGeneratePDF" value='Generate PDF' />
</td>
}
</tr>
<tr>
<td colspan="5">
@Model.Error
</td>
</tr>
Controller:
public ActionResult ProcessForm(string submitbutton, ViewModelTemplate_Guarantors model, FormCollection collection)
Basically, again it's working fine. When the form posts using the Generate PDF button, I get the checked value of each checkbox, but not the name of the template in the Model.
Am I missing something here???
The form before I submit is basically like below. It's the name of the checkbox (Form4) that I'm missing as a TemplateID in my Model once I get into the ActionResult.
public ActionResult ProcessForm(string submitbutton, ViewModelTemplate_Guarantors model, FormCollection collection)
checkbox (checked) Form4
@for (int i = 0; i < Model.Templates.Count; i++)
{
<td>
@Html.CheckBoxFor(model => Model.Templates[i].IsChecked)
@Html.DisplayFor(model => Model.Templates[i].TemplateName)
</td>
}
Upvotes: 2
Views: 15539
Reputation: 93434
As I mentioned in my comment. The model binder cannot bind to an IEnumerable.
Your Model should look like this:
public partial class ViewModelTemplate_Guarantors
{
public ViewModelTemplate_Guarantors() {
Templates = new List<PDFTemplate>(); // These are important, the model binder
Guarantors = new List<tGuarantor>(); // will not instantiate nested classes
}
public int SelectedTemplateId { get; set; }
public List<PDFTemplate> Templates { get; set; }
public int SelectedGuarantorId { get; set; }
public List<tGuarantor> Guarantors { get; set; }
...
}
Further, your view should look like this:
...
@for(int i = 0; i < Model.Templates.Count; i++) // should really use label, not display
{
<td>
@Html.CheckBoxFor(model => Model.Templates[i].IsChecked)
@Html.DisplayFor(model => Model.Templates[i].TemplateName)
</td>
}
...
@for(int i = 0; i < Model.Guarantors.Count; i++)
{
<td>
@Html.CheckBoxFor(model => Model.Guarantors[i].isChecked)
@Html.DisplayFor(model => Model.Gurantors[i].GuarantorFirstName) @Html.DisplayFor(model => Model.Guarantors[i].GuarantorLastName)
</td>
}
...
Although a better choice would be to use an EditorTemplate and instead do this:
...
@Html.EditorFor(m => m.Templates)
...
@Html.EditorFor(m => m.Guarantors)
...
Then create a folder in ~/Views/Shared called EditorTemplates, and then create two files called Templates.cshtml
and Guarantors.cshtml
.
In those files you would do this:
@model PDFTemplate
<td>
@Html.CheckBoxFor(model => model.IsChecked)
@Html.DisplayFor(model => model.TemplateName)
</td>
and
@model Guarantors
<td>
@Html.CheckBoxFor(model => model.isChecked)
@Html.DisplayFor(model => model.GuarantorFirstName) @Html.DisplayFor(model => model.GuarantorLastName)
</td>
The editor templates will automatically iterate over the collection and will account for the correct naming format to make the model binder understand it's a collection.
Upvotes: 8