Posting Ienumerable Values and Saving to M-2-M Relationship - asp.net-mvc

VS'12 KendoUI InternetApplication Template C# asp.net EF Code First
My Question is how to pass both the Regular ( are passing now ) values and the Ienumerable(passing null) into my controller and saving them to the Database using EF Code First in a Many-2-Many Relationship manor.
The Following is what i have tried
Main View
#model OG.Models.UserProfiles
#using (Html.BeginForm())
{
<div class="editor-field">
<div class="Containter">
<div>
#Html.DisplayFor(model => model.UserName)
</div>
<div class="contentContainer">
#foreach (var item in Model.Prospects)
{
<table>
<tr>
<td>
#Html.Label("Current Prospects")
</td>
</tr>
<tr>
<td>
#Html.DisplayNameFor(x=>item.ProspectName)
</td>
</tr>
</table>
}
</div>
</div>
<div class="contentContainer2">
#Html.Partial("_UsersInProspectsDDL", new OG.ModelView.ViewModelUserInProspects() { Users = Model.UserName })
</div>
</div>
}
Partial View
#model OG.ModelView.ViewModelUserInProspects
<label for="prospects">Prospect:</label>
#(Html.Kendo().DropDownListFor(m=>m.Prospects)
.Name("Prospects")
.HtmlAttributes(new { style = "width:300px"}) //, id = "countys"})
.OptionLabel("Select Prospect...")
.DataTextField("ProspectName")
.DataValueField("ProspectID")
.DataSource(source => {
source.Read(read =>
{
read.Action("GetCascadeProspects", "ChangeUsersInfo")
.Data("filterProspects");
})
.ServerFiltering(true);
})
.Enable(false)
.AutoBind(false)
.CascadeFrom("Clients")
</div>
Model for PartialView
public class ViewModelUserInProspects
{
public string Clients { get; set; }
public IEnumerable<dbClient> AvailableClients { get; set; }
public string Prospects { get; set; }
public IEnumerable<dbProspect> AvailableProspects { get; set; }
public string Users { get; set; }
public IEnumerable<UserProfiles> AvailableUsers {get;set;}
}
}
Main Model
Standart SimpleMemberShipUserTable
Post Method
[HttpPost]
public ActionResult UsersInProspect(
[Bind(Include= "ProspectName, ProspectID")]
UserProfiles userprofiles, ViewModelUserInProspects values, FormCollection form)
//<- Trying different things sofar
{
if (ModelState.IsValid)
{
//string something = form["Prospects"];
int prosID = Convert.ToInt16(values.Prospects);
int UserID = userprofiles.UserID; // <- THIS VALUE is null atm.
This is where i need to save both ID's to the EF Generated / Mapped Table. Unsure how.
db.Entry(userprofiles).CurrentValues.SetValues(userprofiles);
db.Entry(userprofiles).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(userprofiles);
}

Please take a look Here
Goes over ViewModels
What EditorTemplate are and how to use them
What the GET Method would look like
What the Edit View would look like
Give you a View Example
What the Post Method would look like

Related

Get Null when Binding List of Object to Controller MVC Model

I am having trouble binding a model that contains list of objects for Editing method. This is the list of Factory which includes list of another object (FactoryHotline).
There is no problem when I get pass data from Controller to View. But when I try to send data from View back to Controller, some model's properties always null.
The Model is:
public class Factory
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<FactoryHotline> FactoryHotlineList { get; set; }
}
public class FactoryHotline
{
public Guid Id { get; set; }
public Guid FactoryId { get; set; }
public string Caption { get; set; }
public string Hotline { get; set; }
}
This is View:
#model List<WebDataLayer.Models.Factory>
<form action="/Factories/Edit" method="POST" enctype="multipart/form-data">
#Html.AntiForgeryToken()
<div class="form-horizontal">
<table id="factoriesTable">
<thead>
<tr>
<th>Name</th>
<th class="Hotline1" >Hotline 1</th>
<th class="Hotline2" >Hotline 2</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Count; i++)
{
#Html.HiddenFor(model => model[i].Id)
<tr>
<td>#Model[i].Name</td>
#for (int h = 0; h < Model[i].FactoryHotlineList.Count; h++)
{
<td>
<div>
<b>Caption: </b>
#Html.EditorFor(model => model[i].FactoryHotlineList[h].Caption, new { htmlAttributes = new { #class = "form-control ShortInput", id = "captionInput", maxlength = "39" } })
</div>
<div>
<b>Hotline:</b>
#Html.EditorFor(model => model[i].FactoryHotlineList[h].Hotline, new { htmlAttributes = new { #class = "form-control ShortInput", id = "hotlineInput", maxlength = "15" } })
#Html.ValidationMessageFor(model => model[i].FactoryHotlineList[h].Hotline)
</div>
</td>
}
</tr>
}
</tbody>
</table>
</form>
In my controller the method for Edit is:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit (List<Factory> factories)
{
}
Only Id has value, the other(Caption, Hotline) are always null in List<Factory> factories
This is how I am passing Data from Controller to View
// GET: Edit
public ActionResult Edit()
{
var factories = _factoryService.All().OrderBy(p => p.Name);
var list = factories.ToList();
return View("Edit", list);
}
I works fine using Entity Framework.
That is because you have used HiddenFor to keep id as hidden field. To have the value in postback, it should be a part of input element(input,select,checkbox,textarea,etc) or as hidden field.
#Html.HiddenFor(model => model[i].Name)
I would suggest using a viewmodel along with automapper in this case.

How to pass different list of data from view to controller MVC

currently I facing a very tricky problem of passing different list of data from view to controller.
I have created two input box to submit my data to controller so that it can be saved into CreateAccountsDB and further display it in the list of
Selected Subcon when Create button is pressed.
The problem I face here is:
when pressing the Create button with entered data from NewCompanyName textbox and NewEmail textbox, those entered data do pass data from View to Controller and save data into CreateAccountDB (not showing in View), but the entered data is not displaying in the list of Selected Subcon.
Create View
Here is the model.
public class Tender
{
public int ID { get; set; }
public string CompanyName { get; set; }
public List<CreateAccount> FrequentCompanyName { get; set; }
public List<CreateAccount> SuggestCompanyName { get; set; }
public List<CreateAccount> SelectedCompanyName { get; set; }
public string CompanyNameNew { get; set; }
public string EmailNew { get; set; }
public int? TradeID { get; set; }
public virtual Trade Trade { get; set; }
public int? CreateAccountID { get; set; }
public virtual CreateAccount CreateAccount { get; set; }
}
Here is the Get Method of Create function in controller:
[httpGet]
public ActionResult Create(int? id)
{
Tender tender = new Tender();
tender.FrequentCompanyName = db.createaccountDB.Include(tm => tm.Trade).Where(td => td.Frequency == 32).ToList();
tender.SuggestCompanyName = db.createaccountDB.Include(tm => tm.Trade).ToList();
if (tender.SelectedCompanyName == null)
{
tender.SelectedCompanyName = new List<CreateAccount>().ToList();
}
return View(tender);
}
and Here is my Post Method of Create function:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,CompanyName,TradeID,FrequentCompanyName,SelectedCompanyName,CreateAccountID")] Tender tender ,string CompanyNameNew, string Emailnew)
{
CreateAccount accnew = new CreateAccount();
accnew.CompanyName = CompanyNameNew;
accnew.Email = Emailnew;
if(ModelState.IsValid)
{
db.createaccountDB.Add(accnew);
db.SaveChanges();
}
if (tender.SelectedCompanyName == null)
{
tender.SelectedCompanyName = new List<CreateAccount>().ToList();
}
tender.FrequentCompanyName = db.createaccountDB.Include(tm => tm.Trade).Where(td => td.Frequency == 32).ToList();
tender.SuggestCompanyName = db.createaccountDB.Include(tm => tm.Trade).ToList();
tender.SelectedCompanyName.ToList().Add(accnew);
return View(tender);
}
and Here is my Create View:
#model Tandelion0.Models.Tender
#{
ViewBag.Title = "Create";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-group">
#*#Html.LabelFor(model => model.ProjectName, htmlAttributes: new { #class = "control-label col-md-3" })*#
<div class="col-md-3">
<h5>New Company Name</h5>
#Html.EditorFor(model => model.CompanyNameNew, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CompanyNameNew, "", new { #class = "text-danger" })
</div>
<div class="col-md-3">
<h5>New Email</h5>
#Html.EditorFor(model => model.EmailNew, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.EmailNew, "", new { #class = "text-danger" })
</div>
<div class="container" align="center">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
<div class="container row">
<!--selected subcon column-->
<div class="container row col-sm-4">
<h4>
Selected Subcon
</h4>
<div style="overflow-y: scroll; height:250px;">
<table class="table table-hover">
#foreach (var item in Model.SelectedCompanyName)
{
<tbody>
<tr>
<td>
#Html.DisplayFor(modelItem => item.CompanyName)
</td>
</tr>
</tbody>
}
</table>
</div>
</div>
</div>
}
So far I manage to save data from view into CreateAccountsDB when create button is pressed, but those data just couldn't pass it from Post method Create function to Get method Create function in Controller. The data and the list become null immediate after come out from post method Create function.
Because of data becomes null, the view couldn't receive any data from controller.
May I know how can i solve the the problem of passing data from controller to view? Is the way I pass data totally wrong?
Any advice is truly appreciated.
In your HttpPost Action method :
Instead of :
tender.SelectedCompanyName.ToList().Add(accnew);
You should be doing:
tender.SelectedCompanyName.Add(accnew);
Calling ToList().Add(object) won't actually add to SelectedCompanyName.Instead it will add to the new list object created by calling ToList() method which you are not assigning back to tender.SelectedCompanyName.
A better approach however would be to use Post/Redirect/Get Pattern.
Instead of returning a view from your post method , do a temorary redirect to your [HttpGet]Create action method passing the id of the tender.

Post Multiple Data from View to Controller MVC

I want to post quantity property to Controller (It's an edit action). I'm editing OrderedProductSet which is connected with ProductSet in my SQL Database (I get the name and price from there). How to pass multiple data from the view to controller? How to write method in controller class to receive the data (I'm asking about method arguments in this specific case).
My view:
#model Shop.Models.ProductViewModel#{
ViewBag.Title = "Edycja zamówienia";
}
<h2>Edycja zamówienie</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<table class="table">
<tr>
<th>
<b>Nazwa produktu</b>
</th>
<th>
<b>Cena</b>
</th>
<th>
<b>Ilość</b>
</th>
<th></th>
</tr>
#foreach (var item in Model.orderedProductSet)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.ProduktSet.name)
</td>
<td>
#Html.DisplayFor(modelItem => item.ProduktSet.price)
</td>
<td>
#Html.EditorFor(model => item.quantity, new { htmlAttributes = new { #class = "form-control" } })
</td>
</tr>
}
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Potwierdź zmiany" class="btn btn-default" />
</div>
</div>
}
<div>
#Html.ActionLink("Powrót", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
My model (in separated classes of course):
public class ProductViewModel
{
public OrderSet orderSet { get; set; }
public IEnumerable<OrderedProductSet> orderedProduktSet { get; set; }
}
public partial class OrderedProduktSet
{
public int orderNumber{ get; set; }
public int productNumber { get; set; }
public int ilosc { get; set; }
public virtual ProduktSet ProduktSet { get; set; }
public virtual OrderSet OrderSet { get; set; }
}
You need to construct controls for you collection in a for loop or use a custum EditorTemplate for OrderedProduktSet so that the controls are correctly named with indexers and can be bound on post back. Note the for loop approach required that the collection be IList.
#model Shop.Models.ProductViewModel
#using(Html.BeginForm())
{
....
for(int i = 0; i < Model.orderedProductSet.Count; i++)
{
#Html.DisplayFor(m => m.orderedProductSet[i].ProduktSet.name)
....
#Html.EditorFor(m => m.orderedProductSet[i].quantity, new { htmlAttributes = new { #class = "form-control" } })
}
<input type="submit" />
}
Controller (the model will be bound, including the collection of OrderedProductSet)
public ActionResult Edit(ProductViewModel model)
{
....
}
Alternatively, you can create an EditorTemplate
/Views/Shared/EditorTemplates/OrderedProduktSet.cshtml
#model OrderedProduktSet
#Html.DisplayFor(m => m.ProduktSet.name)
#Html.EditorFor(m => m.quantity, new { htmlAttributes = new { #class = "form-control" } })
and in the main view
#model Shop.Models.ProductViewModel
#using(Html.BeginForm())
{
....
#Html.EditorFor(m => m.orderedProductSet)
<input type="submit" />
}
Viewbag is your friend here. You normally pass data from View to Controller in MVC. You can access data set in a Viewbag in the controller in your View.
The simplest way to let your controller handle your view is to create an actionresult method in your controller with the same name as your view.
For example, your view is called Index, thus you would have the following method in your controller to handle the view data:
public ActionResult Index()
{
return View();
}
Accessing a list:
Use a Viewbag.
Controller
Viewbag.MyList = myList
View
#foreach (var item in Viewbag.MyList)
Here is good link for more info:
http://www.asp.net/mvc/overview/older-versions/getting-started-with-aspnet-mvc4/adding-a-view

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.

Update Navigation Properties with Entity Framework (MVC3 Razor)

I'm having trouble figuring out how to get my navigation property to update with the Entity Framework. I used a database first approach and set up all appropriate FK relationships. Here's what the two tables I'm working with look like:
Rate Profile
RateProfileID
ProfileName
Rate
RateID
RateProfileID (FK)
Several Other Properties I Want to Update
One RateProfile can/will have many Rates. I built my edit page for RateProfile to display editors for the RateProfile Entity and all of it's associated Rate entities and stuck all of that in a form with a submit button. I can display everything just fine, but my changes will only persist for the model class (RateProfile) and not for its navigation property (Rates).
Below are my views/HttpPost Edit/ Models
In my HttpPost Edit method, you can see my feeble attempt to loop through and Update each record in the navigation property Rates of the model.
#model PDR.Models.RateProfile
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>RateProfile</legend>
#Html.HiddenFor(model => model.RateProfileID)
#Html.HiddenFor(model => model.LoginID)
<div class="editor-label">
#Html.LabelFor(model => model.ProfileName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ProfileName)
#Html.ValidationMessageFor(model => model.ProfileName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.isDefault)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.isDefault)
#Html.ValidationMessageFor(model => model.isDefault)
</div>
<div>
<fieldset>
<legend>Dime</legend>
<table>
<tr>
<th>
Min
</th>
<th>
Max
</th>
<th>
Price
</th>
<th></th>
</tr>
#foreach (var rate in Model.Rates)
{
<tr>
<td>
#Html.EditorFor(modelItem => rate.minCount)
#Html.ValidationMessageFor(model => rate.minCount)
</td>
<td>
#Html.EditorFor(modelItem => rate.maxCount)
#Html.ValidationMessageFor(model => rate.maxCount)
</td>
<td>
#Html.EditorFor(modelItem => rate.Amount)
#Html.ValidationMessageFor(model => rate.Amount)
</td>
</tr>
}
</table>
</fieldset>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
[HttpPost]
public ActionResult Edit(RateProfile rateprofile)
{
if (ModelState.IsValid)
{
db.Entry(rateprofile).State = EntityState.Modified;
foreach (Rate rate in rateprofile.Rates)
{
db.Entry(rate).State = EntityState.Modified;
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(rateprofile);
}
public partial class Rate
{
public int RateID { get; set; }
public int RateProfileID { get; set; }
public string Size { get; set; }
public decimal Amount { get; set; }
public int minCount { get; set; }
public int maxCount { get; set; }
public int PanelID { get; set; }
public virtual Panel Panel { get; set; }
public virtual RateProfile RateProfile { get; set; }
}
public partial class RateProfile
{
public RateProfile()
{
this.Rates = new HashSet<Rate>();
}
public int RateProfileID { get; set; }
public string ProfileName { get; set; }
public int LoginID { get; set; }
public bool isDefault { get; set; }
public virtual Login Login { get; set; }
public virtual ICollection<Rate> Rates { get; set; }
}
You change the foreach into for statement and try whether the model binding is working fine or not.
#for (var int i = 0; i < Model.Rates; i++)
{
<tr>
<td>
#Html.EditorFor(modelItem => Model.Rates[i].minCount)
#Html.ValidationMessageFor(model => Model.Rates[i].minCount)
</td>
<td>
#Html.EditorFor(modelItem => Model.Rates[i].maxCount)
#Html.ValidationMessageFor(model => Model.Rates[i].maxCount)
</td>
<td>
#Html.EditorFor(modelItem => Model.Rates[i].Amount)
#Html.ValidationMessageFor(model => Model.Rates[i].Amount)
</td>
</tr>
}
So what happens when you put a breakpoint on your Edit foreach statement and check what's in rateprofile.Rates ?
Remember that a model returning from a view is not the original object you handed out, it's a disconnected one that was created from scratch in your controller function.
Also, why is Rates a Hashset? Don't mix your database referential constraints with your UI ones, as it only adds to the confusion :)
What Mark suggested here is correct. The fields on the form should be named things like Rates[0].ProfileName, Rates[1].ProfileName etc, but as you have a HashSet, I'm not sure how MVC would regenerate those into the model.
Test with the breakpoint, then follow Mark's suggestion, and I would suggest you replace HashSet with an array or list.

Resources