Basic example link - following link is what I am trying to build. But this is only for single value.
Issue Detail After I select multi colors in <select>, Then i click <a> tag, I want to pass the value of what i select in <select>. Currently after clicking on <a> tag, it wont keep values selcted inside <select>
Step#1 Here I am creating Form with 2 filters. 1 search text box and 2 selectlist
<form asp-page="./index" method="get">
<input type="text" asp-for="SearchString" />
<select asp-for="Colors" asp-items="#Model.Colors_SELECT" class="MultiSelect" multiple>...</select>
...
</form>
Step#2 Display data in table grid. <a> is passing filters to url
<table>
....
<a asp-page="./My_Training"
asp-route-SearchString="#Model.SearchString"
asp-route-Colors="#Model.Colors"
asp-route-SortOrder="#Model.Colors_Sort">
#Html.DisplayNameFor(model => model.MyListData[0].Colors)
</a>
.. // more `<a>` tags. 1 for each column
</table>
Step#3 Back-end code: - mainly bind filter values
[BindProperty(SupportsGet = true)]
public string? SearchString { get; set; }
[BindProperty(SupportsGet = true)]
public List<string>? Colors { get; set; }
public SelectList? Colors_SELECT { get; set; }
public async Task OnGetAsync()
{
// everything is auto bind to properties
}
What I tried: according to google, they recommended using asp-all-route-data with Dictionary. I have tried this code and it doesnt work for multi values. Dictionary doesnt allow same key. for example: if I use asp-all-route-data and pass URL like Colors[0]=Red&Colors[1]=Green than it wont keep values selected inside <select>
I also Tried to do this. This works but the code is a mess and hard to maintain if you have too many filters & columns in data grid
<input type="hidden" asp-for="URL_String" />
...
<a href="/Index? #Model.URL_String&SortOrder=#Model.Colors_Sort">
#Html.DisplayNameFor(model => model.CourseTakenList[0].Colors)
</a>
public string? URL_String { get; set; } = "";
public async Task<IActionResult> OnPostAsync()
{
string? createURL = "?";
createURL += $"SearchString={SearchString}&";
foreach (var p in Colors)
{
createURL += $"Colors={p}&";
}
if (createURL.EndsWith("?") || createURL.EndsWith("&"))
{
createURL = createURL.TrimEnd(createURL[createURL.Length - 1]); //remove last '&'
}
string url = $"{HttpContext.Request.Path}{createURL}";
return Redirect(url);
}
public async Task OnGetAsync()
{
URL_String = Request.QueryString.ToString().Replace("??", "?");
CurrentSort = Request.Query["SortOrder"];
}
Because asp-all-route-data can't pass value with the same key, So you need to add index by yourself, Please refer to this simple demo:
#{
var colors = new Dictionary<string, string>();
var i = 0;
foreach (var item in Model.colors)
{
colors.Add($"colors[{i}]", item);
i++;
}
}
<a asp-page="index" asp-all-route-data="colors">Test</a>
It will pass data like:
?colors[0]=red&colors[1]=green&colors[2]=black
Demo:
=========================Update================
<select multiple id="selectone">
<option value="red">red</option>
<option value="black">black</option>
<option value="white">white</option>
<option value="yellow">yellow</option>
</select>
<button onclick="MySelect()">Select color</button>
<a asp-page="index" asp-route-SearchString="Test" id="color">Test</a>
<script>
function MySelect(){
var result = document.getElementById("selectone").selectedOptions;
var arr=[];
for(let i=0,len = result.length;i<len;i++){
if(result[i].selected){
arr.push("colors["+i+"]=" + result[i].value)
}
}
var str=arr.join("&");
var a = document.getElementById("color").getAttribute('href');
var url = a+ "&" + str;
document.getElementById("color").href = url;
}
</script>
Demo:
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.
Following Post method in my ASP.NET MVC Core 1.1 app adds a record to database. The method successfully adds the record as long as user selects exactly 3 order types from a multi-select dropdown. User is supposed to select at most 3 order types from the dropdown. So, if a user selects less than 3 order types it, as expected, throws the well-know error: Index was outside the bounds of the array. Question: How can I avoid the above error if user selects less than 3 order types. I guess I can place the entire var oOrder = new Order{...} statement below inside each block of an if...else.. to avoid the error. But in real scenario there are lots more model properties and hence repeating them 3 times in if...else... blocks would make the code look a more complicated than it really is. Are there any better ways of doing it?
[HttpPost]
public IActionResult AddOrder(OrderViewModel model)
{
if (ModelState.IsValid)
{
var oOrder = new Order
{
orderName = model.orderName,
StartDate = model.StartDate,
EndDate = model.EndDate,
....
....
lkupType_1_ID = model.SelectedTypeIDs[0],
lkupType_2_ID = model.SelectedTypeIDs[1],
lkupType_3_ID = model.SelectedTypeIDs[2],
....
};
_context.Add(oOrder);
}
return RedirectToAction(....);
}
UPDATE:
snapshot of the View
....
<div>....</div>
....
<div class="form-group">
<label asp-for="SelectedOrderTypeIDs"></label>
<div class="col-md-10">
<select asp-for="SelectedOrderTypeIDs" asp-items="Model.lstOrderTypes"></select>
</div>
</div>
<button type="submit" name="submit">Add Order</button>
NOTE: I'm using ASP.NET MVC tag helpers and often use this post from #Shyju for good example of multi-select tag helper.
You can try below, an if as a tenary operator:
[HttpPost]
public IActionResult AddOrder(OrderViewModel model)
{
if (ModelState.IsValid)
{
var oOrder = new Order
{
orderName = model.orderName,
StartDate = model.StartDate,
EndDate = model.EndDate,
....
....
lkupType_1_ID = (model.SelectedTypeIDs.Length > 0) ? model.SelectedTypeIDs[0] : 0, // You can default it to null if it is Int?
lkupType_2_ID = (model.SelectedTypeIDs.Length > 1) ? model.SelectedTypeIDs[1] : 0,
lkupType_3_ID = (model.SelectedTypeIDs.Length > 2) ? model.SelectedTypeIDs[2] : 0,
....
};
_context.Add(oOrder);
}
return RedirectToAction(....);
}
You can use the Length property in order to check the number of items in SelectedTypeIDs list.
if(model.SelectedTypeIDs.Length>3){
//code
}
If the condition is false you can use ModelState.AddModelError method in order to show the error in the View.
if(model.SelectedTypeIDs.Length>3){
ModelState.AddModelError("Dropdown", "Error! You must have maximum of 3 options");
return View();
}
UPDATE
You can create a generic function which returns 0, if the index is out of bound or the list item instead.
public static TValue GetSafe<TItem>(this IList<TItem> list,
int index, TValue defaultValue)
{
if (index < 0 || index >= list.Count)
{
return defaultValue;
}
return list[index];
}
Now you can use this function to implement you functionality.
var oOrder = new Order
{
orderName = model.orderName,
StartDate = model.StartDate,
EndDate = model.EndDate,
....
....
lkupType_1_ID =model.SelectedTypeIDs.GetSafe(0, 0) ,
lkupType_2_ID =model.SelectedTypeIDs.GetSafe(1, 0) ,
lkupType_3_ID =model.SelectedTypeIDs.GetSafe(2, 0) ,
....
};
in an ASP.NET-MVC 5 application I have the following models
class Employee {
int EmployeeID {get;set;}
string FirstName {get;set;}
List<OfficeLocations> OfficeLocations {get;set;}
}
class OfficeLocations {
int OfficeLocationsID {get;set;}
//foreign key
int EmployeeID {get;set;}
string Value1 {get;set;}
string Value2 {get;set;}
}
I have an edit view for modifying or ADDING different office locations that an employee could belong to. It looks something like this:
#model List<Project.Models.OfficeLocations>
#for (int i = 0; i < Model.Count; i++) {
#Html.EditorFor(m => m[i].CitLocation, new { htmlAttributes = new { #class = "my_editor" } })
#Html.HiddenFor(m => m[i].OfficeLocationsID)
#Html.HiddenFor(m => m[i].EmployeeID)
}
//extra editor box for adding a new value
#Html.Editorfor(??????.Value)
I'm a little confused as to how to add new entries to my model (list) in the database table. What do I put in the parameter for the extra Editorfor box (where all the ???? are)
also, what would the controller action method look like?
change your viewmodel to have an officelocation and a list of officelocation... With that you can add the non list officelocation object in you extra editor box... Or you can just retain your viemodel like that and just manually create a model using jquery and pass it using an ajax jquery...
To fix this issue I came up with the following javascript:
<script>
$(document).ready(function () {
index = 0;
});
$('#add_button').click(function () {
var placeHolderNameAttribute = "List2[#].Value1";
var indexedNameAttribute = "List2[" + index + "].Value1";
var placeHolderIdAttribute = "new_lastName_input";
var indexedIdAttribute = "new_lastName_input" + index;
document.getElementById(placeHolderIdAttribute).name = indexedNameAttribute;
document.getElementById(placeHolderIdAttribute).id = indexedIdAttribute;
var clone1 = $("#new_lastName_input" + index).clone();
document.getElementById(indexedIdAttribute).name = placeHolderNameAttribute;
document.getElementById(indexedIdAttribute).id = placeHolderIdAttribute;
if (index == 0) {
$('#nlnPlaceHolder').remove();
}
$('#LN_editor_box').append(clone1);
index += 1;
});
</script>
and the following placeholder input field
<input id="new_lastName_input" class="my_editor" type="text" name="List2[#].Value1" value="New Last Name" />
and now my controller post method accepts two parameters, the original list of updated/edited values, and a new list of only new values.
ActionResult myMethod(List<OfficeLocations> list1, OfficeLocations[] list2)
and if the value is in list1 then it will update in the database, and if it's in list2 it will be added