Posting all selected rows using Grid.Mvc - c#

I have successfully implemended a new Index view to my application will will render all outstanding emails to a gridview which features a checkbox for each row.
My intention now is to send an email which will include details of each record selected to a designated recipient.
Once a record has been selected and the details mailed for that record, I will flag that row as 'Emailed' and filter all emailed rows out in my controller before passing the list to the view.
I am a little confused as to how I make my submit button capture all selected records that have been checked and post the entire page back to my controller where I can do my email related stuff.
Here's what I have so far, I don't think I have wired up the submit button correctly to the gridview:
#using GridMvc.Html
#Html.Grid(Model).Columns(columns =>
{
columns.Add()
.Encoded(false)
.Sanitized(false)
.Titled("Send Email?")
.SetWidth(30)
.RenderValueAs(o => Html.CheckBox("checked", false));
columns.Add(a => a.Id).Titled("Id").SetWidth(110);
columns.Add(a => a.AddressLineOneOld).Titled("Old Address")
.RenderValueAs(a => a.AddressLineOneOld + " " +
a.AddressLineTwoOld + " " +
a.AddressLineThreeOld + " " +
a.AddressLineFiveOld);
columns.Add(a => a.PostcodeOld).Titled("Old Postcode").Sortable(true);
columns.Add(a => a.AddressLineOneNew).Titled("New Address")
.RenderValueAs(a => a.AddressLineOneNew + " " +
a.AddressLineTwoNew + " " +
a.AddressLineThreeNew + " " +
a.AddressLineFiveNew);
columns.Add(a => a.PostcodeNew).Titled("New Postcode").Sortable(true);
columns.Add(a => a.Emailed).Sortable(true);
}).WithPaging(20)
<br />
<div style="float:right;">
#using (Html.BeginForm("Index", "EmailManager", FormMethod.Post, new { name = "frm", id = "frm" }))
{
<input id="btnSendEmailForSelected" type="submit" name="submitButton" value="Email Selected" class="btn btn-default" />
}
</div>
How do I post all selected rows back to the controller?

it this line Html.CheckBox("checked", false) add a unique name add the value for the checkbox to the Id or the unique key you have as below
Html.CheckBox("checked", false, new {name = "selectedAddressesIds", value = "UNIQUE_KEY_HERE"})
and in the action method try the following, you must write your action method to have FormCollection
var selectedIds = form.GetValues("selectedAddressesIds");
if (selectedIds != null)
{
foreach (var id in selectedIds)
{
// do what you want with the selected id.
}
}

Related

Why is my model's data being cleared at POST

I'm sorry if this ends up simple or stupid question, I'm very new to MVC and Razor, being dropped in deep end with no formal training on anything current...
I don't understand why the data in my model is available in my first view is not available in my second view.
Controller initiates an instance of model using code
Product _Product = new Product();
then the first view is called after a bunch of initial data queries have ran. The view is called using
return View("Attributes", _Product)
to call the view passing it the _Product instance of model.
My first view does many things but relevant to this question it shows a table using foreach row on dataset filled by SQL on controller stored on model.
#foreach(DataRow row in Model.attList.Tables[0].Rows)
The view has multiple buttons, such as add, delete, edit... Each have their own action assigned such as
<input type="submit" class="btn btn-primary" name="action:btnDelete" value="Delete" />
If the user presses one of the buttons on that view that triggers HTTPPOST. One of them calls a new view but if I pass my model to that view again, the data is now blank? I am passing the model to the view, and accepting it in HTTPPOST and passing it to the next view (or so I thought).
I'm using very generic beginning to form
#using (Html.BeginForm()) {
The code in controller for this button is very basic
[HttpPost]
[MultipleButton(Name = "action", Argument = "btnEditValues")]
public ActionResult btnEditValues(Product _Product)
{
return View("btnEditValues", _Product);
}
But then in that 2nd view if I try to re-display the same table, it now says my Model.attList is empty. I added quick script of console.log(#Model.attList); and when I click the button it is showing the 2nd view but the console is logging a blank value (not NULL, just blank)...
I'm sure I'm just missing something simple - or perhaps I'm trying to do something I can't ???
The following was copied/pasted, without edit, into this question from an errantly posted "answer" which was intended to be an edit on the question. The answer has been flagged and should be removed.
Per David's Request.
Here is complete M, V, V, and C (didn't include other 2 views they don't use Model and both work)...
!!! NOTE !!!: This code is being used somewhere everyone has SQL manager and Admin rights to SQL server. As such SQL parameters were not used. This code would be vulnerable to SQL injection if used on open Website.
Model
namespace P21.Rules.Visual.Areas.KCDA_ItemMaint_Attributes.Models
{
public class Product
{
// Variables used between views
public int RowSelected { get; set; }
// Declare datasets to use as list
public DataSet attList { get; set; }
public DataSet lowList { get; set; }
}
}
Controller
namespace P21.Rules.Visual.Areas.KCDA_ItemMaint_Attributes.Controllers
{
#region Multiple Buttons
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MultipleButtonAttribute : ActionNameSelectorAttribute
{
public string Name { get; set; }
public string Argument { get; set; }
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
var isValidName = false;
var keyValue = string.Format("{0}:{1}", Name, Argument);
var value = controllerContext.Controller.ValueProvider.GetValue(keyValue);
if (value != null)
{
controllerContext.Controller.ControllerContext.RouteData.Values[Name] = Argument;
isValidName = true;
}
return isValidName;
}
}
#endregion
public class KCDA_ItemMaint_AttributesController : BaseRuleController
{
#region public variables
// public dataset for loading SQL values
DataSet attload = new DataSet();
DataSet lowload = new DataSet();
#endregion
#region Main Action
// GET: KCDA_ItemMaint_Attributes/KCDA_ItemMaint_Attributes
public ActionResult Attributes()
{
if (Data.Fields["item_id"].FieldValue == string.Empty)
{
// Report error and prevent form pop-up if no product group has been selected
Rule.RuleResult.Success = false;
Rule.RuleResult.Message = "You must first select an Item before listing Attributes";
return RedirectToAction("Close", "Initialize", new { area = "" });
}
else
{
try
{
// Create container and setup Group values
Product _Product = new Product();
//Get Attributes for selected item
LoadAttributes();
_Product.attList = attload.Copy();
//Get ECom Server Side Attribute for selected item
LoadLower();
_Product.lowList = lowload.Copy();
return View("Attributes", _Product);
}
catch (Exception ex)
{
//catch the error and send it to the Error view with the HandleErrorInfo
return View("Error", new HandleErrorInfo(ex, "KCDA_ItemMaint_Attributes", "Attributes"));
}
}
}
#endregion
#region Buttons
[HttpPost]
[MultipleButton(Name = "action", Argument = "btnDelete")]
public ActionResult btnDelete(Product _Product)
{
// create SQL command to delete the
string sqlDelete = "UPDATE BL_ProductAttribute SET DeleteFlag = '1' WHERE [KEY] = '" + _Product.RowSelected + "'";
// Run the sqlDELETE command
SqlCommand cmdDelete = new SqlCommand(sqlDelete, P21SqlConnection);
cmdDelete.ExecuteNonQuery();
SqlDataAdapter daDelete = new SqlDataAdapter(cmdDelete);
return View("btnDelete", _Product);
}
[HttpPost]
[MultipleButton(Name = "action", Argument = "btnAdd")]
public ActionResult btnAdd(Product _Product)
{
// Retrieve selected/loaded drop-down values
string ddGroup = Request["ApplyGroup"];
string ddName = Request["AttributeName"];
string ddValue = Request["AttributeValue"];
if (ddValue == "")
{
ViewBag.msg = "No Value Selected";
}
else
{
// default duplicate count to 0
int duplicate = 0;
// create SQL command to check for duplicate attribute
string sqlDuplicate = "SELECT COUNT(1) FROM BL_ProductAttribute " +
"WHERE SKU = '" + Data.Fields["item_id"].FieldValue + "' " +
"AND AttributeGroupName = '" + ddGroup + "'";
// Run the sqlDuplicate command
SqlCommand cmdDuplicate = new SqlCommand(sqlDuplicate, P21SqlConnection);
cmdDuplicate.CommandType = CommandType.Text;
SqlDataAdapter daDuplicate = new SqlDataAdapter(cmdDuplicate);
// Create dataset from duplicate check
DataTable dupcheck = new DataTable();
daDuplicate.Fill(dupcheck);
// Set count if exists
duplicate = int.Parse(dupcheck.Rows[0][0].ToString());
// if exists update/undelete otherwise insert
if (duplicate > 0)
{
// create SQL command to update the attribute
string sqlAdd = "UPDATE BL_ProductAttribute " +
"SET BL_ProductAttribute.Value = '" + ddValue.Replace("'", "''") + "', " +
"BL_ProductAttribute.AttributeTitle = '" + ddName + "', " +
"BL_ProductAttribute.DeleteFlag = 0, " +
"BL_ProductAttribute.ProductID = '" + Data.Fields["product_group_id"].FieldValue + "' " +
"FROM BL_ProductAttribute " +
"WHERE SKU = '" + Data.Fields["item_id"].FieldValue + "' AND AttributeGroupName = '" + ddGroup + "' ";
// Run the sqlAdd command
SqlCommand cmdAdd = new SqlCommand(sqlAdd, P21SqlConnection);
cmdAdd.ExecuteNonQuery();
SqlDataAdapter daAdd = new SqlDataAdapter(cmdAdd);
ViewBag.msg = "Record Updated";
}
else
{
// If adding determine next key value for unique ID
string newKey = string.Empty;
// create SQL command to get next KEY value for insert reset current maxkey
string sqlMax2 = "SELECT max([key])+1 FROM BL_AttributeEnumValue";
// Run the sqlMax command
SqlCommand cmdKey2 = new SqlCommand(sqlMax2, P21SqlConnection);
cmdKey2.CommandType = CommandType.Text;
SqlDataAdapter daKey2 = new SqlDataAdapter(cmdKey2);
// Create dataset from newKey check and assign to newKey
DataTable KeyCheck2 = new DataTable();
daKey2.Fill(KeyCheck2);
newKey = KeyCheck2.Rows[0][0].ToString();
// create SQL command to update the attribute
string sqlAdd = "INSERT INTO BL_ProductAttribute ([Key], ProductId, SKU, AttributeTitle, " +
"isSKUlevel, isRequired, isDefault, Value, AttributeGroupName, DeleteFlag) " +
"VALUES('" + newKey + "', '" + Data.Fields["product_group_id"].FieldValue + "', '" +
Data.Fields["item_id"].FieldValue + "', '" + ddName + "', 1, 1, 1, '" +
ddValue.Replace("'", "''") + "', '" + ddGroup + "', 0)";
// Run the sqlAdd command
SqlCommand cmdAdd = new SqlCommand(sqlAdd, P21SqlConnection);
cmdAdd.ExecuteNonQuery();
SqlDataAdapter daAdd = new SqlDataAdapter(cmdAdd);
ViewBag.msg = "Record Added";
}
}
return View("btnAdd", _Product);
}
[HttpPost]
[MultipleButton(Name = "action", Argument = "btnEditValues")]
public ActionResult btnEditValues(Product _Product)
{
return View("btnEditValues", _Product);
}
#endregion
#region SQL Loads
private void LoadAttributes()
{
// Define SQL select command
string sqlAttributes = "SELECT * FROM BL_ProductAttribute " +
"WHERE SKU = '" + Data.Fields["item_id"].FieldValue + "' AND DeleteFlag = '0' " +
" AND AttributeGroupName in ('SKU_Color', 'SKU_SelectableAttribute_1', 'SKU_SelectableAttribute_2')";
// Set SQL command type to text and run it
SqlCommand cmdlist = new SqlCommand(sqlAttributes, P21SqlConnection);
cmdlist.CommandType = CommandType.Text;
SqlDataAdapter dalist = new SqlDataAdapter(cmdlist);
// Load results from SQL into DataSet
dalist.Fill(attload);
}
private void LoadLower()
{
string DBconn = "vsldb1";
// Define SQL select command
string sqllist = "SELECT [Key], ProductID, SKU, AttributeTitle, isSKUlevel, isRequired, isDefault, " +
"\"Value\" = Case " +
"when (AttributeTitle = 'KCDASKUStatus' and ltrim(convert(varchar,Value)) = '0') then '0 - Warehouse Regular Item' " +
"when (AttributeTitle = 'KCDASKUStatus' and ltrim(convert(varchar,Value)) = '1') then '1 - Not on Website/Pending' " +
"when (AttributeTitle = 'KCDASKUStatus' and ltrim(convert(varchar,Value)) = '2') then '2 - RFQ' " +
"when (AttributeTitle = 'KCDASKUStatus' and ltrim(convert(varchar,Value)) = '3') then '3 - Limited Quote' " +
"when (AttributeTitle = 'KCDASKUStatus' and ltrim(convert(varchar,Value)) = '4') then '4 - Discontinued/Obsolete' " +
"when (AttributeTitle = 'KCDASKUStatus' and ltrim(convert(varchar,Value)) = '5') then '5 - Specials' " +
"when (AttributeTitle = 'KCDASKUStatus' and ltrim(convert(varchar,Value)) = '6') then '6 - Direct Ship' " +
"when (AttributeTitle = 'KCDASKUStatus' and ltrim(convert(varchar,Value)) = '7') then '7 - Offline' " +
"else value end, AttributeGroupName, UpdateFlag FROM OPENDATASOURCE('SQLOLEDB','Data Source=" + DBconn + ";user " +
"id=sa;password=KCDAAdmin').KCDA.dbo.KCDA_ProductAttribute PA" +
" WHERE PA.SKU = '" + Data.Fields["item_id"].FieldValue + "' AND PA.AttributeGroupName not in " +
"('SKU_Color', 'SKU_SelectableAttribute_1', 'SKU_SelectableAttribute_2')";
// Set SQL command type to text and run it
SqlCommand cmdlist = new SqlCommand(sqllist, P21SqlConnection);
cmdlist.CommandType = CommandType.Text;
SqlDataAdapter dalist = new SqlDataAdapter(cmdlist);
// Load results from SQL into DataSet
dalist.Fill(lowload);
}
#endregion
#region Drop Downs
[HttpPost]
public ActionResult GetAttributeNames(string selectedOption)
{
// Define variables for JSON query to use
JsonResult result = new JsonResult();
List<string> attNames = new List<string>();
List<string> attValues = new List<string>();
if (selectedOption != null)
{
// SQL to get attribute name for the selected attribute group for this product group
string sql = "SELECT Title FROM BL_Attribute (NOLOCK) WHERE BL_Attribute.DeleteFlag = '0' AND BL_Attribute.AttributeGroupName = '" + selectedOption + "'" +
" AND BL_Attribute.Title in (select AD.AttributeTitle from BL_AttributeDept AD where AD.product_group_id = '" + Data.Fields["product_group_id"].FieldValue.Substring(0, 2) +"')";
using (SqlCommand selectAttNames = new SqlCommand(sql, P21SqlConnection))
{
using (SqlDataReader reader = selectAttNames.ExecuteReader())
{
while (reader.Read())
{
attNames.Add(reader["Title"].ToString());
}
}
}
// SQL to get list of current available values for this attribute type
string sql2 = "SELECT Value FROM BL_AttributeEnumValue (NOLOCK) WHERE DeleteFlag = '0' and AttributeTitle = '" +
attNames[0] + "' ORDER BY Value";
using (SqlCommand selectAttValues = new SqlCommand(sql2, P21SqlConnection))
{
using (SqlDataReader reader2 = selectAttValues.ExecuteReader())
{
while (reader2.Read())
{
attValues.Add(reader2["Value"].ToString());
}
}
}
// define return object
var retObj = new
{
retNames = attNames,
retValues = attValues
};
return Json(retObj, JsonRequestBehavior.AllowGet);
}
return Json(new { Success = "false" });
}
#endregion
#region Edit Values
#endregion
#region Close Rule
[HttpPost]
public ActionResult Return()
{
Rule.RuleResult.Success = true;
//IMPORTANT - This is what returns the Visual Rule control back to the server
//DO NOT REMOVE
return RedirectToAction("Close", "Initialize", new { area = "" });
}
#endregion
}
}
Attributes View
#using P21.Rules.Visual.Areas.KCDA_ItemMaint_Attributes.Models
#using System.Data
#model Product
#{
ViewBag.Title = "Attributes";
Layout = "~/Views/Shared/_VisualRuleLayout.cshtml";
var listAttGroups = new List<SelectListItem>
{
new SelectListItem { Text = "SKU_Color", Value = "SKU_Color"},
new SelectListItem { Text = "SKU Select Att 1", Value = "SKU_SelectableAttribute_1"},
new SelectListItem { Text = "SKU Select Att 2", Value = "SKU_SelectableAttribute_2"}
};
}
#section scripts{
<script>
$(function () {
$("#ApplyGroup").change(function () {
var option = $(this).val();
//Clear and activate 2nd and 3rd drop down
$("#AttributeName").empty();
$("#AttributeValue").empty();
$("#AttributeName").prop('disabled', false);
$("#AttributeValue").prop('disabled', false);
var url = "GetAttributeNames?selectedOption=" + option;
$.post(url, function (retObj) {
$.each(retObj.retNames, function (i, attName) {
$("#AttributeName").append($('<option></option>').val(attName).html(attName));
});
$.each(retObj.retValues, function (i, attValue) {
$("#AttributeValue").append($('<option></option>').val(attValue).html(attValue));
});
});
});
});
</script>
}
#using (Html.BeginForm())
{
<div class="container">
<div class="row">
<label class="col-md-3 control-label" for="ApplyGroup" id="lblApplyGroup">Attribute Group</label>
<label class="col-md-3 control-label" for="AttributeName" id="lblAttributeName">Attribute Name</label>
<label class="col-md-2 control-label" for="AttributeValue" id="lblAttributeValue">Attribute Value</label>
<div class="col-md-2">
<input type="submit" class="btn btn-primary" name="action:btnEditValues" value="Edit Values" />
</div>
</div>
<div class="row" style="padding-top:5px">
<div class="col-md-3">
#Html.DropDownList("ApplyGroup", listAttGroups, "Select Group", new { #id = "ApplyGroup", #class = "form-control" })
</div>
<div class="col-md-3">
#Html.DropDownList("AttributeName", new List<SelectListItem>(), new { #id = "AttributeName", #class = "form-control", disabled = "disabled" })
</div>
<div class="col-md-3">
#Html.DropDownList("AttributeValue", new List<SelectListItem>(), new { #id = "AttributeValue", #class = "form-control", disabled = "disabled" })
</div>
<div class="col-md-3">
<span class="pull-right">
<input type="submit" class="btn btn-primary" name="action:btnAdd" value="Add\Update" />
</span>
</div>
</div>
<div class="row" style="padding-top:20px">
<div class="col-md-10">
</div>
<div class="col-md-2">
<span class="pull-right">
<input type="submit" class="btn btn-primary" name="action:btnDelete" value="Delete" />
</span>
</div>
</div>
<div class="row" style="padding-top:10px">
<div class="col-md-12">
<table id="attTable" style="width:100%" cellpadding="0" cellspacing="0" border="1" class="row">
<tbody>
<tr style="background-color: #F0F8FF;">
<th></th>
<th>Dept</th>
<th>Item #</th>
<th>Attribute Name</th>
<th>SKU Level</th>
<th>Required</th>
<th>Default</th>
<th>Attribute Value</th>
<th>Attribute Group</th>
</tr>
#foreach (DataRow row in Model.attList.Tables[0].Rows)
{
<tr class="selectable-row">
<td>#Html.RadioButtonFor(m => Model.RowSelected, row[0])</td>
<td>#row[1]</td>
<td>#row[2]</td>
<td>#row[3]</td>
<td>#row[4]</td>
<td>#row[5]</td>
<td>#row[6]</td>
<td>#row[7]</td>
<td>#row[8]</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<div class="row" style="padding-top:50px">
<div class="col-md-12">
<table id="lowTable" style="width:100%" cellpadding="0" cellspacing="0" border="1" class="row">
<tbody>
<tr style="background-color: #F0F8FF;">
<th>Dept</th>
<th>Item #</th>
<th>Attribute Name</th>
<th>SKU Level</th>
<th>Required</th>
<th>Default</th>
<th>Attribute Value</th>
<th>Attribute Group</th>
</tr>
#foreach (DataRow row in Model.lowList.Tables[0].Rows)
{
<tr class="selectable-row">
<td>#row[1]</td>
<td>#row[2]</td>
<td>#row[3]</td>
<td>#row[4]</td>
<td>#row[5]</td>
<td>#row[6]</td>
<td>#row[7]</td>
<td>#row[8]</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
}
btnEditValues View
#using P21.Rules.Visual.Areas.KCDA_ItemMaint_Attributes.Models
#using System.Data
#model Product
#{
ViewBag.Title = "btnEditValues";
Layout = "~/Views/Shared/_VisualRuleLayout.cshtml";
var listAttGroups = new List<SelectListItem>
{
new SelectListItem { Text = "SKU_Color", Value = "SKU_Color"},
new SelectListItem { Text = "SKU Select Att 1", Value = "SKU_SelectableAttribute_1"},
new SelectListItem { Text = "SKU Select Att 2", Value = "SKU_SelectableAttribute_2"}
};
}
#section scripts{
<script>
console.log(#Model.attList);
</script>
}
<p align="right"><button type="button" class="btn btn-primary" onclick="location.href='#Url.Action("Attributes", "KCDA_ItemMaint_Attributes")'">X</button></p>
<center><h2>Edit Item Attribute Availble Values</h2></center>
#using (Html.BeginForm())
{
<div class="container">
<div class="row">
<label class="col-md-4 control-label" for="EditApplyGroup" id="lblEApplyGroup">Attribute Group</label>
<label class="col-md-4 control-label" for="EditAttributeName" id="lblEAttributeName">Attribute Name</label>
</div>
<div class="row" style="padding-top:5px">
<div class="col-md-4">
#Html.DropDownList("EditApplyGroup", listAttGroups, "Select Group", new { #id = "EditApplyGroup", #class = "form-control" })
</div>
<div class="col-md-4">
#Html.DropDownList("EditAttributeName", new List<SelectListItem>(), "Select Name", new { #id = "EditAttributeName", #class = "form-control", disabled = "disabled" })
</div>
</div>
<div class="row" style="padding-top:10px">
<div class="col-md-12">
<table id="attEditTable" style="width:100%" cellpadding="0" cellspacing="0" border="1" class="row">
<tbody>
<tr style="background-color: #F0F8FF;">
<th></th>
<th>Attribute Value</th>
<th>Item Count</th>
</tr>
</tbody>
</table>
</div>
</div>
</div>
}
If your ultimate question is, "how can I post this model with all of it's data?",
public class Product
{
// Variables used between views
public int RowSelected { get; set; }
// Declare datasets to use as list
public DataSet attList { get; set; }
public DataSet lowList { get; set; }
}
then the only real answer is "with a LOT of work." The answer that follows is probably mostly opinionated, so not really a great SO answer, but reflects my experience.
Basically, this controller action:
[HttpPost]
[MultipleButton(Name = "action", Argument = "btnEditValues")]
public ActionResult btnEditValues(Product _Product)
{
// ... other code
return View("btnEditValues", _Product);
}
expects the model binder to be able to construct an instance of the Product class. To do so, it needs to have every public property be present as a form value in the HTTP request that was made to this controller action. Those form values come from what are called successful controls, which is basically any form element (<input />, <select>, or <textarea>, generally) which has a name (the name="propertynamehere" attribute). If the field's not present, it's value doesn't exist.
Given how complex the DataSet class is, I would not recommend attempting to build enough form fields to successfully get your data back from the model binder. The only thing you really should care about is RowSelected - that lets you fetch the data you need from the database. Given that an SQL connection and query are generally pretty fast, it's probably a better user experience, as well, since posting enough data to repopulate a DataSet requires A LOT of form values (you might even run into a issue with exceeding the maximum allowed number of fields, although that is configurable in ASP.NET). If that makes sense and sounds feasible, I can elaborate on how to refactor at least this action to match how MVC is intended to work.
As a note, if the goal is to take a user to an edit page, that makes more sense as a GET request; loading an edit screen is an idempotent action, which matches the semantics of a GET. POST is normally meant to be used to update values, which you're not doing there.
You might find this useful, as it describes the recommended pattern to follow (PRG, or POST-Redirect-GET): https://en.wikipedia.org/wiki/Post/Redirect/Get
Of course, if the material you're learning from is on the older side, ASP.NET (via WebForms) used POST to transfer something called ViewState, which was/is used to give the illusion of state within a web application - the idea was "Windows Forms for the Web", but it adds a lot of overhead that you really don't want in a modern aplication. Given your background, it (WebForms) might be a better fit, though, since it lets you focus on an event-driven development model.
The form doesn't have any data for constructing the model.
Take a step back and consider the HTTP requests being made. An HTML <form>, when submitted, requests the action URL (as a POST request in this case) and includes whatever form values are present in that form. This likely does not include disabled form elements. (I could be mistaken on that.) And it definitely does not include the HTML of the form.
(You've indicated that you didn't come from a WebForms background, but coincidentally enough this is a very common mistake for people who did.)
With the exception of the buttons, the only form elements I'm seeing are three drop down lists (ApplyGroup, AttributeName, AttributeValue) as well as the radio buttons being emitted in the loop, called RowSelected.
But the model needs more values to be created:
public int RowSelected { get; set; }
// Declare datasets to use as list
public DataSet attList { get; set; }
public DataSet lowList { get; set; }
The model has RowSelected, and you can debug to confirm if that value is being correctly populated. (Your question indicates only that you're trying to debug the presence of the DataSet properties, not the RowSelected property.)
But including all data from two DataSet properties would be much more complex. And, perhaps more to the point, would be entirely unnecessary. Take a step back and consider that you'd be filling the page with data that you don't expect the user to edit and then posting all of that data back to the server that it just came from.
In your action you can get that RowSelected property in a variety of ways. As a method argument, as a model property, or simply as you currently get form values in your other action methods:
string rowSelected = Request["RowSelected"];
You can use int.TryParse() to convert that to an integer value if needed. Though the SQL code you're using is concatenating strings so you may not need to anyway.
(Side note: It's good that you're already aware that SQL injection is a bad thing. Even though in your particular case it's not necessarily a security problem, please be aware that SQL injection is also a very common source of bugs. It's worth learning the correct way of doing things.)
If you can get the fully-populated model from the database using an identifier, that would be ideal. It's certainly easier to just pass around the identifier than it is to pass around an entire complex model, especially if you're not expecting the user to edit that model on that page.
All in all, it looks like you're simply expecting the entire model to be posted to the server when really all you're posting is the identifier for the model. Which, fortunately enough, is all you actually need. Just use that identifier to fetch the intended record.

How to handle repeating form fields in ASP MVC

I have a form which asks users for their personal info and their family members.
fields of the family members section is repeating.
my question is what is best practice to handle these kind of repeating forms?
I currently use AJAX to repeat forms but how to collect data from these repeating fields?
since some one asked for how I repeat form, I do it like this:
AJAX Call
$(document).on('click', '.btn-add-item', function (e) {
e.preventDefault();
var $results = $('#results');
$.ajax({
url: '/AJAX/AddFamilyForm',
type: 'post',
success: function (data) {
$(data).appendTo($results);
afterAJAX();
}
});
});
C# code
[HttpPost]
public PartialViewResult AddFamilyForm()
{
if (!Request.IsAjaxRequest()) return null;
return PartialView("_FamilyForm");
}
This is some skeleton code on how to get this to work with proper model-binding in MVC. You'll need to write some JS to be able to delete/add new rows.
Model
public class MyModel
{
public FamilyMembers[] FamilyMembers { get; set; }
}
View
<button id="addNewFamilyMember" type="button">Add</button>
#if (Model.FamilyMembers != null)
{
for (int i = 0; i < Model.FamilyMembers.Length; i++)
{
<tr>
<td>
<button type="button">Delete</button>
#Html.Hidden("FamilyMembers.Index", i)
</td>
<td>
#Html.TextBoxFor(m => Model.FamilyMembers[i].Relation)
</td>
<td>
#Html.TextBoxFor(m => Model.FamilyMembers[i].FullName)
</td>
</tr>
}
}
Below is the code for adding a new member. It creates html dynamically and is able to bind to the posted model because of naming conventions. time gives each added row a unique id so all the data stays together.
JS (using Jquery)
var hidden = '#Html.Hidden("FamilyMembers.Index", "{id}")';
var relationHtml = '#Html.TextBox("FamilyMembers[{id}].Relation")';
var fullNameHtml = '#Html.TextBox("FamilyMembers[{id}].FullName")';
$("#addNewFamilyMember").on("click", function () {
var time = Date.now();
var deleteHtml = "<button type='button'>Delete</button>";
$("#familyMembers-table").find("tbody")
.append($("<tr><td>" + hidden.replace("{id}", time) + deleteHtml + "</td>" +
"<td>" + relationHtml.replace("{id}", time) + "</td>" +
"<td>" + fullNameHtml.replace("{id}", time) + "</td></tr>"));
});
One of the solution could be combination of hidden field and control name.
Steps:
Use a hidden field to keep the count the number of row.
Create controls with name like text_relation_1 for first row and text_relation_2 for second row and so on
Generate other controls in same way.
Increase and decrease the hidden field value so that when values post you can know the number of rows added by the user
On your action use FormCollection and loop though hidden field number and get the values from FormCollection
Like suppose I created 3 rows then I can create a action like below
public ActionResult SomeActionMethod(FormCollection formCollection, string hid)
{
for(int i=1;i<hid;i++)
{
var relationId="text_relation_"+i;
var firstrealtion=formCollection[relationId];
...
}
}
You don't need any extra Ajax requests for this, since you can use established and standard <form> features.
Just append [] to the name of the added forms and you'll end up with an array rather than a single value in your HTTP request once the form is submitted:
<input type="text" name="relation[]" /><input type="text" name="fullname[]" />
<input type="text" name="relation[]" /><input type="text" name="fullname[]" />
<input type="text" name="relation[]" /><input type="text" name="fullname[]" />
In this example you'd end up with an array relation and an array fullname, both containing your datasets.

how to Prevent Serverside data load to dropdown list in Asp.net MVC

I have dropdown list which was created dynamically like:
#for(int i=0;i<=count;i++)
{
#Html.DropDownListFor(m => m.GetTimeSheetDetails[i].PROJ_ID, (SelectList)ViewBag.ProjectList, "-- Choose a Project --", new { #class = "ddlProjectvalue" })
}
<input type="submit" value="Add Record" name="btn"/>
in Contoller I am loading data to dropdownlist:
[HttpPost]
Public ActionResult Timesheet()
{
TimsheetModel model=new TimesheetModel();
if(btn=="Add Record")
{
var data= Session["ddlData"] as IEnumerable<SelectListItem>;
SelectList list1=new SelectList(data,"Value","Text",model.ProjID);
ViewBag.ProjectList=list1;
count++; // ADDS NEW RECORD
return View();
}
else
{
var result = (from proj in db.PROJECTs where proj.IS_DELETED == "N" select new { Value = proj.ID, Text = proj.NAME })
SelectList list = new SelectList(result, "Value", "Text", tm.PROJ_ID);
ViewBag.ProjectList = list;//Data loaded here for Dropdown list
}
return View();
}
Now My Scenario is if count=5 which means if we have five dropdown lists, when I select item in first dropdown list should not show in second dropdown list and if we have select item in second dropownlist should not show items of first and second in third dropdown list. for that I have written script like:
<script>
$(document).ready(function () {
$('.ddlProjectvalue').change(function () {
var id = $('.ddlProjectvalue').attr('id');
var selector = "#" + id;
var selectedValue = $(this).val();
$.ajax({
url: "#Url.Action("GetDDLData","Employer")",
data: { selectedValue: selectedValue, id: id },
dataType: "json",
type: "GET",
error: function () {
alert(" An error occurred.");
},
success: function (data) {
debugger;
$("" + selector + "").removeClass("ddlProjectvalue");
$('.ddlProjectvalue').empty();
var optionhtml1 = '<option value="' +
0 + '">' + "--Choose a Project--" + '</option>';
$(".ddlProjectvalue").append(optionhtml1);
$.each(data, function (i) {
var optionhtml = '<option value="' +
data[i].Value + '">' + data[i].Text + '</option>';
$(".ddlProjectvalue").append(optionhtml);
});
}
});
});
});
</script>
and when i pass selected value to controller like:
public ActionResult GetDDLData(string selectedValue, string id, string addrecord)
{
int projectid = Convert.ToInt32(selectedValue);
if (id == "GetTimeSheetDetails_0__PROJ_ID")
{
IEnumerable<SelectListItem> projectslist = (from proj in db.PROJECTs where proj.IS_DELETED == "N" && proj.ID != projectid select proj).AsEnumerable().Select(projt => new SelectListItem() { Text = projt.NAME, Value = projt.ID.ToString() });
var result = new SelectList(projectslist, "Value", "Text", tm.PROJ_ID).ToList();
Session["ddlData"] = result;
ViewBag.ProjectList = result;
return Json(result, JsonRequestBehavior.AllowGet);
}
else
{
var result = Session["ddlData"] as IEnumerable<SelectListItem>;
var query = (from data in result where data.Value != selectedValue select data) as IEnumerable<SelectListItem>;
Session["ddlData"] = query;
return Json(result, JsonRequestBehavior.AllowGet);
}
}
Now my problem is when I add new record by clciking on Add button, loading Session["ddldata"] data to total dropdown list instead it should remain selectlist item in first dropdownlist for first time, I need like when i first select a dropdownlist item in first dropdown list it should remain same when add record also. it means i should prevent server side load on first select list item and vice versa.
Note: Due to some issues i should add record on server side only
How I can prevent it, I tried like preventDefault or return false using jquery, but not working, Any Ideas? how can I fix it.
I think you are overcomplicating things here. You don't really need to request new options from server. Why not just filter the option out on the javascript side?
$(document).ready(function() {
$('.ddlProjectvalue').change(function() {
updateDDLValues();
});
updateDDLValues();
});
function updateDDLValues() {
// Display all
$('.ddlProjectvalue option').show();
// Hide all selected options from other selectlists
$('.ddlProjectvalue').each(function(i,element) {
var selectedvalue = $(element).find('option:selected').val();
$('.ddlProjectvalue').not(element).find('option[value="'+selectedvalue+'"]').hide();
});
}
Fiddle:
http://jsfiddle.net/Pt7qV/2/
Update:
As for the serverside part of your question, there are some serious flaws in your code. You increase the count property in your controller and use the variable clientside. First you'd think that's how it's done but nope it doesn't work that way.
You are returning View when Add Record is submitted but you aren't returning any model with it.
Your TimsheetModel would look something like this:
public class TimsheetModel
{
public int Count {get; set;}
}
In your controller you pass this to the view:
TimsheetModel model=new TimesheetModel();
if(btn=="Add Record")
{
var data= Session["ddlData"] as IEnumerable<SelectListItem>;
SelectList list1=new SelectList(data,"Value","Text",model.ProjID);
ViewBag.ProjectList=list1;
model.Count++; // ADDS NEW RECORD
return View(model);
}
And in your view:
#model TimsheetModel
#for(int i=0;i<=Model.Count;i++)
{
#Html.DropDownListFor(m => m.GetTimeSheetDetails[i].PROJ_ID, (SelectList)ViewBag.ProjectList, "-- Choose a Project --", new { #class = "ddlProjectvalue" })
}
<input type="submit" value="Add Record" name="btn"/>
I'd suggest you to go back to tutorials or books a bit, this is quite basic stuff after all. I won't go into how you are going to handle database side etc. since I think this answer would just escalate into explaining basic stuff.

MVC Cascading Drop Down Not Selected When Editing

I am developing an MVC 4 web application. One of the Razor views has two drop down lists. The first drop down list is populated by the ViewModel data which is passed to the view. The secondary drop down list is populated using a JQuery and Ajax call based on the selected ID from the first drop down list (cascading).
I have this working fine, however, whenever a user wishes to edit an existing record I can't get the selected secondary drop down list value to be selected.
This is my Razor code for the two drop down lists
<div class="lbl_a">
Employer:
</div>
<div class="editor-field sepH_b">
#Html.DropDownListFor(model => model.Employer, Model.EmployerList, "Select", new { #class = "inpt_a" })
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DirectorateID, "Directorate/ Service Group")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.DirectorateID, Model.DirectorateList, "Select")
</div>
This is my JQuery code
$(document).ready(function () {
//Pre load on page load
onEmployerChange();
//Hide and show DIVS based on selection
$("#Employer").change(onEmployerChange);
function onEmployerChange() {
var dataPost = { orgID: val };
$.ajax({
type: "POST",
url: '/User/GetDirectorates/',
data: dataPost,
dataType: "json",
error: function () {
alert("An error occurred." + val);
},
success: function (data) {
var items = "";
$.each(data, function (i, item) {
items += "<option value=\"" + item.Value + "\">" + item.Text + "</option>";
});
$("#DirectorateID").html(items);
}
});
}
}
});
When a user selects a value from the first drop down list, the selected ID is passed to the GetDirectorates action within the User Controller.
This is my GetDirectorates action which returns Json data
public ActionResult GetDirectorates(string orgID)
{
if (String.IsNullOrWhiteSpace(orgID))
orgID = "0";
var Directorates = _ListService.GetListItemsByOrganisationID(Convert.ToInt32(orgID));
List<SelectListItem> directorateList = new List<SelectListItem>();
directorateList.Add(new SelectListItem() { Text = "Select", Value = "" });
foreach (var directorate in Directorates)
{
directorateList.Add(new SelectListItem() { Text = directorate.description, Value = directorate.listItemID.ToString(), Selected = false });
}
return Json(new SelectList(directorateList, "Value", "Text"));
}
Whenever the users wishes to edit an existing record I pass both the values for the first and second drop down list. Both drop down lists are populated with the proper data as expected, however, the selected value for the second drop down list is never selected.
This is a shortened version of the Edit action which the user calls when attempting to edit an existing record but shows the two drop down list selected values being passed.
public ActionResult EditNonMember(int id, string feedback, string courseDateID, string courseID)
{
//code to retrieve data here
vm.Employer = UserDetails.Employer;
vm.DirectorateID = UserDetails.DirectorateID;
return View(vm);
}
Would anyone be able to help me with this?
Thanks.
You need to get the directorate list for the saved employer id and set the DirectorateList collection and then the DirectorateID (from saved record);
public ActionResult EditNonMember(int id, string feedback, string courseDateID,
string courseID)
{
//code to retrieve data here
var userDetails=repositary.GetUserFromSomeId(id);
vm.Employers=GetEmployers();
vm.Employer = userDetails.Employer;
vm.DirectorateList=GetDirectorateListForEmployer(userDetails.Employer);
vm.DirectorateID = userDetails.DirectorateID;
return View(vm);
}
private List<SelectListItem> GetEmployers()
{
// to do : Return all employers here in List<SelectListItem>
}
private List<SelectListItem> GetDirectorateListForEmployer(int employerId)
{
// to do : Return all Directorates for the selected employer
}
This should do the trick:
var subSelect = $("#DirectorateID");
// clear the selection
subSelect.empty();
//append each option to the list
$.each(data, function (i, item) {
subSelect.append($('<option/>', {
value: item.Value,
text: item.Text
}));
});
Rather than setting it via the html method, I'm simply appending an option.
This is the method I use for cascading dropdown lists using ajax.

C# DropDownList postback

I am using MVC ASP to create a series of dropdownlist's that are populated by SQL stored procedures. However, each successive dropdownlist needs to be populated by passing the selection of the previous list as a parameter to the procedure call. How can I POST the selection of the list created using:
#Html.DropDownListFor(x => x.environmentName, new SelectList(Model.environmentName))
?
I was attempting to save it to a modelView and then pass the view to the controller again, but I feel this is a poor way to go about it.
Here is a JQuery solution I wrote for 3 cascading drop-downs with ajax callbacks to the controller to fill the next list based on the previous choices. This might get you going in the right direction.
Select: <select id="category" style="width: 150px">
<option></option>
#foreach (string cat in ViewBag.Categories)
{
<option>#cat</option>
}
</select><span id="errorforcategory" style="color: red"></span>
<select id="subcategory1" disabled="disabled" style="width: 150px"><option></option> </select>
<select id="subcategory2" disabled="disabled" style="width: 150px"><option></option></select>
<script type="text/javascript">
$("#category").change(function () {
$("#subcategory1").load('#Url.Action("GetSubCategory")' + "?category=" + $("#category").val());
$('#subcategory2').empty();
$('#subcategory2').append($("<option></option>"));
$('#subcategory2').attr('disabled', 'disabled');
}).ajaxStop(function () {
if ($('#subcategory1 option').size() > 2) {
$('#subcategory1').attr('disabled', '');
} else {
$('#subcategory1').attr('disabled', 'disabled');
}
});
$("#subcategory1").change(function() {
if ($("#subcategory1").val().trim()) {
$("#subcategory2").load('#Url.Action("GetSubCategory")' + "?category=" + $("#category").val() + "&subcategory=" + $("#subcategory1").val());
} else {
$('#subcategory2').empty();
$('#subcategory2').attr('disabled', 'disabled');
}
}).ajaxStop(function() {
if ($('#subcategory2 option').size() > 2) {
$('#subcategory2').attr('disabled', '');
} else {
$('#subcategory2').attr('disabled', 'disabled');
}
});
And then in your controller you can call your Stored Proc using whatever method you like then build out your result option text.
public string GetSubCategory(string category, string subcategory)
{
string returnval = "<option></option>";
if (!string.IsNullOrEmpty(subcategory))
{
foreach (
var cat in
db.Categories.Where(c => c.category1 == category && c.subcategory1 == subcategory)
.Select(c => c.subcategory2)
.Distinct())
{
if (!string.IsNullOrEmpty(cat.Trim()))
returnval += "<option>" + cat + "</option>";
}
return returnval;
}
return Enumerable.Aggregate(db.Categories.Where(c => c.category1 == category).Select(c => c.subcategory1).Distinct().Where(cat => !string.IsNullOrEmpty(cat.Trim())), returnval, (current, cat) => current + ("<option>" + cat + "</option>"));
}

Categories

Resources