Saving an IEnumerable Model - asp.net-mvc

I have a view that has 2 date pickers, one of which is a start date and the other of which is end date. I am able to get the user to select the start and end dates, redirect to a view that will have a list of Time worked where the user could change the date, enter time, and once they are done they should be able to click the save button and it should create a new instance of each time worked to the database. I got the view displaying with no problem, but when the form posts my enumerable model is null, the form collection only has one accessible item, and I am not sure why. Please take a look at the view I am creating below to get an idea of what I am doing and let me know if you are able to help me in any way.
#model IEnumerable<TimeCollection.Models.TimeWorked>
#{
ViewBag.Title = "Create TimeWorked Range";
}
<h2>Create TimeWorked Range</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.WorkDate)
</th>
<th>
#Html.DisplayNameFor(model => model.HoursWorked)
</th>
</tr>
#foreach (var item in Model) {
<tr>
#Html.HiddenFor(modelItem => item.EmployeeId)
<td>
#Html.EditorFor(modelItem => item.WorkDate)
</td>
<td>
#Html.EditorFor(modelItem => item.HoursWorked)
</td>
<td>
#Html.HiddenFor(modelItem => item.BeenSubmitted)
</td>
</tr>
}
</table>
<p>
<input type="submit" value="Save" />
</p>
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace TimeCollection.Models
{
public class TimeWorked
{
public int TimeWorkedId { get; set; }
public int EmployeeId { get; set; }
public DateTime WorkDate { get; set; }
public int HoursWorked { get; set; }
public bool BeenSubmitted { get; set; }
public static void CreateTimeWorked(TimeWorked timeWorkedToCreate)
{
string insertQuery = string.Format("insert into time_worked (employee_id, work_date, work_hours, been_submitted) values ('{0}', '{1}', '{2}', '{3}')", timeWorkedToCreate.EmployeeId, timeWorkedToCreate.WorkDate.ToString("yyyy/MM/dd"), timeWorkedToCreate.HoursWorked, (timeWorkedToCreate.BeenSubmitted == true ? "1" : "0"));
SpectrumData.Utility.ExecuteMySqlCommand(SpectrumData.Properties.Resources.SpectrumTSDatabaseConnectionString, insertQuery);
}
public static TimeWorked ReadTimeWorked(int timeWorkedId)
{
string selectQuery = string.Format("select * from time_worked where time_worked_id = '{0}'", timeWorkedId);
return ConvertDataRowIntoTimeWorked(SpectrumData.Utility.FillDataSet(SpectrumData.Properties.Resources.SpectrumTSDatabaseConnectionString, selectQuery).Tables[0].Rows[0]);
}
public static void UpdateTimeWorked(TimeWorked timeWorkedToUpdate)
{
string updateQuery = string.Format("update time_worked set work_date = '{0}', work_hours = '{1}', been_submitted = '{2}' where time_worked_id = '{2}'", timeWorkedToUpdate.WorkDate, timeWorkedToUpdate.HoursWorked, timeWorkedToUpdate.BeenSubmitted, timeWorkedToUpdate.TimeWorkedId);
SpectrumData.Utility.ExecuteMySqlCommand(SpectrumData.Properties.Resources.SpectrumTSDatabaseConnectionString, updateQuery);
}
public static void DeleteTimeWorked(int timeWorkedId)
{
string deleteQuery = string.Format("delete from time_worked where time_worked_id = '{0}'", timeWorkedId);
SpectrumData.Utility.ExecuteMySqlCommand(SpectrumData.Properties.Resources.SpectrumTSDatabaseConnectionString, deleteQuery);
}
private static TimeWorked ConvertDataRowIntoTimeWorked(System.Data.DataRow timeWorkedDataRow)
{
TimeWorked timeWorked = new TimeWorked();
timeWorked.BeenSubmitted = (timeWorkedDataRow["been_submitted"].ToString() == "1" ? true : false);
timeWorked.EmployeeId = int.Parse(timeWorkedDataRow["employee_id"].ToString());
timeWorked.HoursWorked = int.Parse(timeWorkedDataRow["work_hours"].ToString());
timeWorked.TimeWorkedId = int.Parse(timeWorkedDataRow["time_worked_id"].ToString());
timeWorked.WorkDate = DateTime.Parse(timeWorkedDataRow["work_date"].ToString());
return timeWorked;
}
}
}
public ActionResult CreateTimeWorkedRange(DateTime startDate, DateTime endDate)
{
List<Models.TimeWorked> listOfTimeWorked = new List<Models.TimeWorked>();
DateTime beginning = startDate;
DateTime ending = endDate;
while (beginning <= ending)
{
Models.TimeWorked dayWorked = new Models.TimeWorked()
{
EmployeeId = (Session["InventoryReviewUser"] as SpectrumData.SpectrumTS.InventoryReviewUser).EmployeeId,
WorkDate = beginning
};
if (listOfTimeWorked.Contains(dayWorked) == false)
{
listOfTimeWorked.Add(dayWorked);
}
beginning = beginning.AddDays(1);
}
return View(listOfTimeWorked);
}
[HttpPost]
public ActionResult CreateTimeWorkedRange(List<Models.TimeWorked> modelList)
{
foreach (Models.TimeWorked timeWorked in modelList)
{
Models.TimeWorked.CreateTimeWorked(timeWorked);
}
}

I don't know if there's a better way, but one way to do this is to bind your items using the index of the array as follows:
#for (int i = 0; i < Model.Count; ++i) {
<tr>
#Html.HiddenFor(modelItem => Model[i].EmployeeId)
<td>
#Html.TextBoxFor(modelItem => Model[i].WorkDate)
</td>
<td>
#Html.TextBoxFor(modelItem => Model[i].HoursWorked)
</td>
<td>
#Html.HiddenFor(modelItem => Model[i].BeenSubmitted)
</td>
</tr>
}
You'd need a different collection type for your model (IList<TimeWorked> should be fine) to be able to use indexers, but that shouldn't be a problem I'd imagine. When you post, the collection should fill properly.

Related

Pass multiple parameters to partial view

I have a model structured like so:
public int JobID { get; set; }
public int SiteID { get; set; }
public List<AModel> ListAModel { get; set; }
In my main view, I am iterating through the List using a for loop with i as an index. I want to call a partial view from within this main page to avoid repeat code across the system but this partial view needs to be aware of the index number as well as the job id and site id.
I cannot just pass in in Model.ListAModel[i] as this will not be aware of job or site id, and likewise with the other way round.
Any help would be appreciated.
You can use the ExpandoObject Class that represents an object whose members can be dynamically added and removed at run time. Code below demonstrates how it can be used:
Data model:
public class AModel
{
public string Name { get; set; }
}
public class ViewModel
{
public int JobID { get; set; }
public int SiteID { get; set; }
public List<AModel> ListAModel { get; set; }
}
.cshtml:
#model Models.ViewModel
#using System.Dynamic
<div>
#for (int i=0; i < Model.ListAModel.Count; i++)
{
dynamic item = new ExpandoObject();
item.job = Model.JobID;
item.site = Model.SiteID;
item.amodel = Model.ListAModel[i];
Html.RenderPartial(#"PartialView", (object) item);
}
</div>
In the partial view:
#using System.Dynamic
#model dynamic
#{
Models.AModel am = #Model.amodel;
<div>job id = #Model.job, site id = #Model.site, amodel = #am.Name </div>
}
There are several posts about how to pass parameters to a view dynamically For example:
Dynamic Anonymous type in Razor causes RuntimeBinderException
In my main view, I am iterating through the List using a for loop with
i as an index. I want to call a partial view from within this main
page to avoid repeat code across the system but this partial view
needs to be aware of the index number as well as the job id and site
id.
I cannot just pass in in Model.ListAModel[i] as this will not be aware
of job or site id, and likewise with the other way round.
From your description, I assume you want to filter data based on the Job id and site id, then display the AModel via the partial view.
To pass parameters from main page to the partial view, you can use the view-data attribute. Please refer the following sample:
Model:
public class JobViewModel
{
public int JobID { get; set; }
public int SiteID { get; set; }
public List<AModel> ListAModel { get; set; }
}
public class AModel
{
public int ID { get; set; }
public string Name { get; set; }
public int JobID { get; set; }
public int SiteID { get; set; }
}
Controller:
public IActionResult Index4()
{
var initialdata = new List<JobViewModel>()
{
new JobViewModel(){
JobID = 1001,
SiteID = 102,
ListAModel = new List<AModel>()
{
new AModel(){ ID=1, Name="Joe", JobID=1001, SiteID=101},
new AModel(){ ID=2, Name="Merry", JobID=1001, SiteID=102},
new AModel(){ ID=3, Name="Henry", JobID=1001, SiteID=103},
new AModel(){ ID=4, Name="Cody", JobID=1001, SiteID=101},
new AModel(){ ID=5, Name="Simon", JobID=1001, SiteID=102},
new AModel(){ ID=6, Name="Leena", JobID=1001, SiteID=103},
new AModel(){ ID=7, Name="Ode", JobID=1001, SiteID=101},
new AModel(){ ID=8, Name="Nicky", JobID=1001, SiteID=102},
}
}
};
return View(initialdata.FirstOrDefault());
}
Main page: using ViewData and view-data attribute to pass parameters.
#model MVCWebApplication.Models.JobViewModel
#{
ViewData["JobID"] = Model.JobID.ToString();
ViewData["SiteID"] = Model.SiteID.ToString();
}
<div>
<h4>JobViewModel</h4>
<hr />
<dl class="row">
<dt class="col-sm-2">
#Html.DisplayNameFor(model => model.JobID)
</dt>
<dd class="col-sm-10">
#Html.DisplayFor(model => model.JobID)
</dd>
<dt class="col-sm-2">
#Html.DisplayNameFor(model => model.SiteID)
</dt>
<dd class="col-sm-10">
#Html.DisplayFor(model => model.SiteID)
</dd>
</dl>
<div class="form-group">
<partial name="_AModelPV.cshtml" model="#Model.ListAModel" view-data="ViewData"/>
</div>
</div>
Partial View (_AModelPV.cshtml): In the partial view, you could also check whether the ViewData exists and contains the value.
#model IEnumerable<MVCWebApplication.Models.AModel>
#{
var jobid = Convert.ToInt32(ViewData["JobID"]);
var siteid = Convert.ToInt32(ViewData["SiteID"]);
}
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.ID)
</th>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.JobID)
</th>
<th>
#Html.DisplayNameFor(model => model.SiteID)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Where(c=>c.JobID == jobid && c.SiteID == siteid).ToList()) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.ID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.JobID)
</td>
<td>
#Html.DisplayFor(modelItem => item.SiteID)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
#Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |
#Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
</td>
</tr>
}
</tbody>
</table>
The result like this:
Besides, you could also consider filtering the data before sending data to the main page. For example, in the above action method, filter the data based on the JobID and SiteID, then return the result to the main page. In this scenario, the ListAmodel contains the filtered data, there is no need to do the filter action in the partial view.
var result = initialdata.Select(c =>
new JobViewModel {
JobID = c.JobID, SiteID = c.SiteID,
ListAModel = c.ListAModel.Where(d => d.JobID == c.JobID && d.SiteID == c.SiteID).ToList() })
.FirstOrDefault();
return View(result);

partial view in mvc core

I would like to sum table column for view model is
public class ColumnTotal {
public int ID { get; set; }
public decimal ColumnOne { get; set; }
public decimal ColumnTwo { get; set; }
public decimal ColumnThree { get; set; }
}
My home controller of columntotal method looks like this
public IActionResult ColumnTotal()
{
var query = _context.REHPData.Include(r => r.BudgetYear).GroupBy(r => r.BudgetYearlyAllocation.ID).Select(s => new ColumnTotal
{
ID = s.Key,
ColumnOne = s.Sum(x => x.BudgetYearlyAllocation.AdminThaTeen),
ColumnTwo = s.Sum(x => x.BudgetYearlyAllocation.AdminThaTwo),
ColumnThree = s.Sum(x => x.BudgetYearlyAllocation.AdminThaFour)
}).ToList();
return View(query);
}
MY partial view _test.cshtml looks like this
#model RenewableEnergyProjcet.Models.CalculationViewModels.ColumnTotal
<tr>
<td> #Html.DisplayFor(m=>m.ColumnOne)</td>
<td> #Html.DisplayFor(m=>m.ColumnTwo)</td>
<td> #Html.DisplayFor(m=>m.ColumnThree)</td>
</tr>
my report.chtml looks like this
#{
ViewData["Title"] = "Budget Result Table ";
}
<h2>Budgets Result Table </h2>
<table class="table table-bordered table-condensed table-responsive table-hover">
<tr>
<td>Some Columns -----</td>
</tr>
#foreach (var item in Model)
{
<tr>
some data----
</tr>
}
<tr>
#await Html.PartialAsync("_test"); //how to call this partial _test.chtml
</tr>
</table>
please how to call partial _test.chtml from in method.
In Action that fill report.chtml call too the query that you use in ColumnTotal() action and send it to the view and in the view use RenderPartial() to call the view.
public IActionResult report()
{
ViewData["ColumnTotal"] = _context.REHPData.Include(r => r.BudgetYear).GroupBy(r => r.BudgetYearlyAllocation.ID).Select(s => new ColumnTotal
{
ID = s.Key,
ColumnOne = s.Sum(x => x.BudgetYearlyAllocation.AdminThaTeen),
ColumnTwo = s.Sum(x => x.BudgetYearlyAllocation.AdminThaTwo),
ColumnThree = s.Sum(x => x.BudgetYearlyAllocation.AdminThaFour)
}).ToList();
return View("report");
}
An in the view do something like this.
#Html.RenderPartial("_test",ViewData["ColumnTotal"])

IEnumerable' does not contain a definition for 'Type'

I am trying to use this #Html.DropDownListFor for filtering the results.
Controller:
[HttpGet]
public ActionResult Leading()
{
ReportClassHandle ReportClassHandle = new ReportClassHandle();
return View(ReportClassHandle.LeadingAll());
}
And LeadingAll method is:
public List<Leading> LeadingAll(string Type)
{
clsUtilities clsUtilities = new clsUtilities();
DataSet ds;
List<Leading> leading = new List<Leading>();
string sSQL;
SqlParameter[] prms = new SqlParameter[1];
sSQL = "exec GetLeading #Type";
prms[0] = new SqlParameter("#Type", SqlDbType.VarChar);
prms[0].Value = Type;
ds = clsUtilities.CreateCommandwithParams(sSQL, prms);
DataTable dataTable = ds.Tables[0];
foreach(DataRow dr in dataTable.Rows)
{
leading.Add(new Leading
{
RankId = Convert.ToInt32(dr["RankId"]),
Name = Convert.ToString(dr["Name"]),
});
}
My complete Leading View is:
#model IEnumerable<ReportProject.Models.Leading>
#using (Html.BeginForm("Leading", "Home", FormMethod.Post))
{
#Html.DisplayFor(m => m.Type)
#Html.DropDownListFor(m => m.Type, new List<SelectListItem> {
new SelectListItem{ Text="General", Value="1" },
new SelectListItem{Text="Advance", Value="2"}}, "Please select")
#Html.ValidationMessageFor(m => m.Type, "", new { #class = "error" })
}
<table class="table table-striped">
<tr>
<th>#Html.DisplayName("Rank")</th>
<th>#Html.DisplayName("Name")</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>#Html.DisplayFor(modelItem => item.RankId)</td>
<td>#Html.DisplayFor(modelItem => item.Name)</td>
</tr>
}
</table>
My Leading Model:
public class Leading
{
public int RankId { get; set; }
public string Name { get; set; }
[Required(ErrorMessage = "Type is Required")]
public string Type { get; set; }
}
When I run this code, it is throwing error:
Compiler Error Message: CS1061: 'IEnumerable<Leading>' does not contain a definition for 'Type' and no extension method 'Type' accepting a first argument of type 'IEnumerable<Leading>' could be found (are you missing a using directive or an assembly reference?)
Please guide me.
The error occurs because the model in your view is IEnumerable<Leading> but you attempting to create controls for a single instance of Leading. Since you wanting to use the value of the dropdownlist to filter your results, then you should start with a view model to represent what you want in the view
public class LeadingFilterVM
{
public int? Type { get; set; }
public IEnumerable<SelectListItem> TypeList { get; set; }
public IEnumerable<Leading> Leadings { get; set; }
}
Then you GET method will be
[HttpGet]
public ActionResult Leading(int? type)
{
IEnumerable<Leading> data;
ReportClassHandle ReportClassHandle = new ReportClassHandle();
if (type.HasValue)
{
data = ReportClassHandle.LeadingAll(type.Value.ToString())
}
else
{
data = ReportClassHandle.LeadingAll();
}
LeadingFilterVM model = new LeadingFilterVM
{
Type = type,
TypeList = new List<SelectListItem>
{
new SelectListItem{ Text = "General", Value = "1" },
new SelectListItem{ Text = "Advance", Value = "2" }
},
Leadings = data
};
return View(model);
}
And change the view to
#model LeadingFilterVM
// note the FormMethod.Get
#using (Html.BeginForm("Leading", "Home", FormMethod.Get))
{
#Html.DisplayFor(m => m.Type)
#Html.DropDownListFor(m => m.Type, Model.TypeList "All types")
<input type="submit" value="search" />
}
<table class="table table-striped">
<thead>
<tr>
<th>Rank</th>
<th>Name</th>
<th></th>
</tr>
<thead>
<tbody>
#foreach (var item in Model.Leadings)
{
<tr>
<td>#Html.DisplayFor(m => item.RankId)</td>
<td>#Html.DisplayFor(m => item.Name)</td>
</tr>
}
</tbody>
</table>

Avoid duplicating the results on viewpage in mvc

I'm creating banking application in MVC 5.
Load table by bank and Load table rows by Bank,
But I cannot figure out do this exactly like above image,
This what I'm getting , it has 10 tables, instead just 2 tables
this is cshtml code for above view
#model IEnumerable<albaraka.Models.ProductApprovals>
#{
ViewBag.Title = "Product_Approvals";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>sadfasf</h2>
<h2>Product Approvals</h2>
#using (Html.BeginForm("Index", "Student", FormMethod.Get))
{
<div> Filter by : Date #Html.TextBox("SearchString", ViewBag.CurrentFilter as string) Filter by : Country #Html.TextBox("SearchString", ViewBag.CurrentFilter as string) <input type="submit" value="Search" /> </div>
}
#foreach (var item in Model)
{
#Html.DisplayNameFor(model => model.SubsidaryName); <p &nbsp /> #Html.DisplayFor(modelItem => item.SubsidaryName);
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.ProductNameEn)
</th>
<th>
#Html.DisplayNameFor(model => model.ProdcutNameAr)
</th>
<th>
#Html.DisplayNameFor(model => model.CurrentStatus)
</th>
<th>
Actions
</th>
</tr>
#foreach (var inside in Model)
{
if (inside.Subsidary_ID == item.Subsidary_ID)
{
<tr>
<td>
#Html.DisplayFor(modelItem => inside.ProductNameEn)
</td>
<td>
#Html.DisplayFor(modelItem => inside.ProdcutNameAr)
</td>
<td>
#Html.DisplayFor(modelItem => inside.CurrentStatus)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
#Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |
#Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
</td>
</tr>
}
}
</table>
}
This is Model class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace albaraka.Models
{
public class ProductApprovals
{
public DateTime SearchDate { get; set; }
public string Country { get; set; }
public string Product_ID { get; set; }
public string Subsidary_ID { get; set; }
public string SubsidaryName { get; set; }
public string ProductNameEn { get; set; }
public string ProdcutNameAr { get; set; }
public string CurrentStatus { get; set; }
}
}
This is Controller class
public ActionResult Product_Approvals()
{
var productApproveResult =(from p in db.AB_Product
join s in db.AB_Subsidary on p.Subsidary_ID equals s.SubsidaryID
select new ProductApprovals
{
Product_ID = p.ProductID,
Subsidary_ID = s.SubsidaryID,
SubsidaryName = s.SubsidaryNameEn,
ProductNameEn = p.ProductTitleEn,
ProdcutNameAr = p.ProductTitleAr,
CurrentStatus = p.Status
}).ToList();
return View(productApproveResult);
}
How to stop this repeating ,appricate if can show a way to this in view page
You need a view model(s) to represent what you want to display in the view. It should be something like
public class BankVM
{
public string SubsidaryName { get; set; }
public IEnumerable<ProductVM> Products { get; set; }
}
public class ProductVM
{
public string ProductNameEn { get; set; }
public string ProdcutNameAr { get; set; }
public string CurrentStatus { get; set; }
}
and pass a collection of BankVM to the view
#model IEnumerable<yourAssembly.BankVM>
#foreach(var bank in Model)
{
<h3>#Html.DisplayFor(m => bank.SubsidaryName)</h3>
<table>
foreach(var product in bank.Products)
{
<tr>
<td>#Html.DisplayFor(m => product.ProductNameEn)</td>
<td>#Html.DisplayFor(m => product.ProductNameAr)</td>
....
</tr>
}
</table>
}
In the controller, you can use a Linq .GroupBy() method to populate your collection of BankVM

MVC 4 Model is empty when I try to submit a form

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

Resources