I have an ASP.NET MVC program with an order/odc request form. I have a customer, order and order item model. What I want to do is allow the user to place an order for a list of items to be approved. I have the viewmodel being passed to the form/view with a few fields including a list of order item objects. I am able to dynamically add rows to the table which shows the list of order items but on submit there is nothing in the viewmodel list. What am I doing wrong? How do I pass the items entered into the table to the view so that I can submit to the database?
Controller
public ActionResult NewOdc()
{
var viewModel = new NewOdcViewModel()
{
OdcItems = new List<tblOdcItem>()
};
viewModel.OdcItems.Add(new tblOdcItem());
return View(viewModel);
}
I call this code from jQuery to add a new item to the list:
public ActionResult GetView(string rowCount)
{
tblOdcItem item = new tblOdcItem();
return PartialView("_OdcItemEditor", item);
}
And on submit I call this code:
[HttpPost]
public ActionResult NewOdcSubmit(NewOdcViewModel viewModel)
{
_context.tblOdcs.Add(new tblOdc());
...
I'm using a foreach to go through the list and create a partial for each item.
View:
#using (Html.BeginForm("NewOdcSubmit", "Odc", FormMethod.Post))
{
if (Model != null)
{
#Html.HiddenFor(m => m.OdcItems);
}
<div class="panel panel-info">
<div class="panel-heading">
<h2 class="panel-title">Enter New ODC</h2>
</div>
<div class="panel-body">
#Html.AntiForgeryToken()
<div class="form-group">
#Html.LabelFor(model => model.User.UserName, new { #class = "col-md-2 col-sm-1 control-label" })
<div class="col-md-2 col-sm-3">
#Html.TextBoxFor(model => model.User.UserName, new { #Value = ((PM_Portal2020.Models.tblUser)Session["User"]).UserName, #readonly = true })
</div>
#Html.LabelFor(model => model.User.Phone, new { #class = "col-md-2 col-sm-1 control-label" })
<div class="col-md-2 col-sm-3">
#Html.TextBoxFor(model => model.User.Phone, new { #Value = ((PM_Portal2020.Models.tblUser)Session["User"]).Phone })
</div>
</div>
<div class="form-group col-md-10 col-sm-12">
<label>Expenses</label>
<table id="submissionTable" class="table table-bordered">
<thead>
<tr>
<th>Qty</th>
<th>Description</th>
<th>Estimated Cost</th>
</tr>
</thead>
<tbody>
#foreach (PM_Portal2020.Models.tblOdcItem item in Model.OdcItems)
{
#Html.Partial("_OdcItemEditor", item)
}
</tbody>
</table>
<p>
<button id="add" type="button" class="btn btn-primary">Add</button>
</p>
</div>
<div class="form-group col-lg-10 col-sm-12">
#Html.LabelFor(model => model.Details, new { #class = "col-md-2 col-sm-1 control-label" })
<div class="">
#Html.TextAreaFor(model => model.Details, new { #class = "form-control" })
</div>
</div>
</div>
<div class="panel-footer">
<div class="">
<button type="submit" class="btn btn-success">Save</button>
#Html.ActionLink("Back", "Index")
</div>
</div>
</div>
}
PartialView in Shared folder:
#model PM_Portal2020.Models.tblOdcItem
<tr #Html.Id("tablerow" + Model.ID)>
<td>
<div class="editor-field">
#Html.TextBoxFor(model => model.Quantity, new { #class = "text-box single-line", name = "Quantity[" + Model.ID + "]", type = "text", value = "", required = "required" })
</div>
</td>
<td>
<div class="editor-field">
#Html.TextBoxFor(model => model.Description, new { #class = "text-box single-line", name = "Description[" + Model.ID + "]", type = "text", value = "", required = "required", id = "itemDesc" })
</div>
</td>
<td>
<div class="editor-field">
#Html.TextBoxFor(model => model.EstimatedCost, new { #class = "text-box single-line", name = "EstimatedCost[" + Model.ID + "]", type = "text", value = "", required = "required" })
</div>
</td>
<td>
<button type="button" class="btn btn-primary" onclick="removeTr(this);">
<span class="glyphicon glyphicon-trash"></span>
</button>
</td>
</tr>
View Model
public class NewOdcViewModel
{
public NewOdcViewModel()
{
}
public IList<tblOdcItem> OdcItems { get; set; }
public string Details { get; set; }
public int OdcId { get; set; }
public tblUser User { get; set; }
}
It submits to the controller but the odcitems list is always count = 0. Any help would be great. Thanks
Here is the javascript example, just use this function on add/delete operation to re-arrange name.
function RearangeName(){
var i = 0;
$("#submissionTable>tbody>tr").each(function () {
$(this).find("input").each(function () {
if ($(this).prop("name").indexOf('Quantity') > 0) {
$(this).attr('name', "OdcItems[" + i + "].Quantity");
}
if ($(this).prop("name").indexOf('Description') > 0) {
$(this).attr('name', "OdcItems[" + i + "].Description");
}
if ($(this).prop("name").indexOf('EstimatedCost') > 0) {
$(this).attr('name', "OdcItems[" + i + "].EstimatedCost");
}
});
i++;
});
}
the name should be matched with model property, so in partial view, you have set name as OdcItems[0].Quantity instead of Quantity[" + Model.ID + "].
#Html.TextBoxFor(model => model.Quantity, new { #class = "text-box single-line", name = "OdcItems[0].Quantity", type = "text", value = "", required = "required" })
eg.
OdcItems[0].Quantity
OdcItems[1].Quantity
OdcItems[2].Quantity
....
OdcItems[n].Quantity
Related
I am trying to create an expandable input table and I am running into some issues. Both seem to be related to the fact that the scripts are not running, but I am not sure. None of the buttons are working even though they have in the past. I don't know what changed, but I did not change the add or delete functions but they don't work anymore. The submit button doesn't seem to trigger the corresponding function in the controller.
I have tried looking for syntax errors and there don't seem to be any. The scripts seem to be referencing the correct portion of the view.
Here is the controller:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Promotion_Generator.Models.DataModels;
namespace Promotion_Generator.Controllers
{
public class PromotionController : Controller
{
private PromotionGeneratorEntities db = new PromotionGeneratorEntities();
public ActionResult BOGO()
{
return View();
}
[HttpPost]
public JsonResult BOGOSave(string BuyMemberData, string GetMemberData)
{
try
{
var BuySerializeData = JsonConvert.DeserializeObject<List<String>>(BuyMemberData);
foreach (var data in BuySerializeData)
{
}
var GetSerializeData = JsonConvert.DeserializeObject<List<String>>(GetMemberData);
foreach (var data in GetSerializeData)
{
}
db.SaveChanges();
}
catch (Exception)
{
return Json("fail");
}
return Json("success");
}
}
}
Here is the View
#model Promotion_Generator.Models.BOGOModel
#{
ViewBag.Title = "BOGO";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2><b>Buy One Get One Free</b></h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<p class = "control-label col-md-2"><b>Promotion Code</b></p>
<div class="col-md-10">
#Html.EditorFor(model => model.PROMOTION_CODE, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PROMOTION_CODE, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<p class="control-label col-md-2"><b>Description</b></p>
<div class="col-md-10">
#Html.EditorFor(model => model.DESCRIPTION, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.DESCRIPTION, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<p class="control-label col-md-2"><b>Start Date Time</b></p>
<div class="col-md-10">
#Html.EditorFor(model => model.START_DATE_TIME, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.START_DATE_TIME, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<p class="control-label col-md-2"><b>End Date Time</b></p>
<div class="col-md-10">
#Html.EditorFor(model => model.END_DATE_TIME, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.END_DATE_TIME, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<p class="control-label col-md-2"><b>Percent Off</b></p>
<div class="col-md-10">
#Html.EditorFor(model => model.PERCENT_OFF, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PERCENT_OFF, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<p class="control-label col-md-2"><b>Percent Off</b></p>
<div class="col-md-10">
#Html.EditorFor(model => model.BUY_MEMBERS, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.PERCENT_OFF, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group col-md-10">
<h3><b>Buy Products</b></h3>
<table id="buyProducts" class="table">
<thread>
<tr>
<th>
<p>Product UPC</p>
</th>
</tr>
</thread>
<tbody>
<tr>
<td>
#Html.EditorFor(modelItem => modelItem.BUY_MEMBERS, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
<input type="button" value="Remove" onclick="T1Remove(this)" />
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>
<input class="btn btn-default" id="btnAdd1" type="button" name="name" value="Add" />
</td>
</tr>
</tfoot>
</table>
</div>
<div class="form-group col-md-10">
<h3><b>Get Products</b></h3>
<table id="getProducts" class="table">
<thread>
<tr>
<th>
<p>Product UPC</p>
</th>
</tr>
</thread>
<tbody>
<tr>
<td>
#Html.EditorFor(modelItem => modelItem.GET_MEMBERS, new { htmlAttributes = new { #class = "form-control" } })
</td>
<td>
<input type="button" value="Remove" onclick="T2Remove(this)" />
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>
<input class="btn btn-default" id="btnAdd2" type="button" name="name" value="Add" />
</td>
</tr>
</tfoot>
</table>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" id="btnSubmit" value="Submit" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to Home", "Index", "Home")
</div>
#section scripts{
<script>
$(".btnAdd1").click(function () {
//Get the reference of the Table's TBODY element.
var tBody = $("#buyProducts > TBODY")[0];
//Add Row.
var row = tBody.insertRow(-1);
//Add Editor cell.
var cell = $(row.insertCell(-1));
cell.append("#Html.EditorFor(modelItem => modelItem.GET_MEMBERS, new { htmlAttributes = new { #class = "form-control" } })");
//Add Button cell.
cell = $(row.insertCell(-1));
var btnRemove = $("<input />");
btnRemove.attr("type", "button");
btnRemove.attr("onclick", "T1Remove(this);");
btnRemove.val("Remove");
cell.append(btnRemove);
//Clear the TextBoxes.
txtName.val("");
txtCountry.val("");
});
$(".btnAdd2").click(function () {
//Get the reference of the Table's TBODY element.
var tBody = $("#getProducts > TBODY")[0];
//Add Row.
var row = tBody.insertRow(-1);
//Add Editor cell.
var cell = $(row.insertCell(-1));
cell.append("#Html.EditorFor(modelItem => modelItem.GET_MEMBERS, new { htmlAttributes = new { #class = "form-control" } })");
//Add Button cell.
cell = $(row.insertCell(-1));
var btnRemove = $("<input />");
btnRemove.attr("type", "button");
btnRemove.attr("onclick", "T2Remove(this);");
btnRemove.val("Remove");
cell.append(btnRemove);
//Clear the TextBoxes.
txtName.val("");
txtCountry.val("");
});
function T1Remove(button) {
//Determine the reference of the Row using the Button.
var row = $(button).closest("TR");
var name = $("TD", row).eq(0).html();
if (confirm("Do you want to delete: " + name)) {
//Get the reference of the Table.
var table = $("#buyProducts")[0];
//Delete the Table row using it's Index.
table.deleteRow(row[0].rowIndex);
}
};
function T2Remove(button) {
//Determine the reference of the Row using the Button.
var row = $(button).closest("TR");
var name = $("TD", row).eq(0).html();
if (confirm("Do you want to delete: " + name)) {
//Get the reference of the Table.
var table = $("#getProducts")[0];
//Delete the Table row using it's Index.
table.deleteRow(row[0].rowIndex);
}
};
function BuyMembers() {
var BuyMembers = $(this).find('#BuyMembers').val();
return BuyMembers;
};
function GetMembers() {
var GetMembers = $(this).find('#GetMembers').val();
return GetMembers;
};
$('.btnSubmit').click(function () {
$.ajax({
url: '/Promotion/SaveData',
type: 'POST',
dataType: 'json',
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(BuyMembers),
data: JSON.stringify(GetMembers),
success: function () {
alert("Data Added Successfully");
},
error: function () {
alert("Error while inserting data");
}
});
});
</script>
}
When I hit the Add, Remove, and Submit buttons the script doesn't seem to run and no columns are added or deleted.
You're currently assigning the jQuery events to the classes ($(".btnAdd1")) instead of ids ($("#btnAdd1")). You can either change your events to link to the id values:
$("#btnAdd1").click(function () {
//...
}
$("#btnAdd2").click(function () {
//...
});
$('#btnSubmit').click(function () {
//...
});
or change the inputs to have the classes of the events. This will fix your issue with the click events not triggering.
I've put some Regex expression on the properties of my model that only accept numbers.
public class MaterialsViewModel
{
[Display(Name = "Material")]
public string MaterialName { get; set; }
[Range(typeof(int), "0", "999")]
[RegularExpression(#"^\d+$", ErrorMessage = "Please enter proper value")]
public int? Quantity { get; set; }
[RegularExpression(#"^\d+$", ErrorMessage = "Please enter proper value")]
public double? Cost { get; set; }
public IEnumerable<SelectListItem> CategoryList { get; set; }
public int SelectedCategory { get; set; }
public string SelectedCategoryName { get; set; }
}
I have my View in which I can add controls dynamically
Dynamically add ScopeOfWork and Materials<br />
<div id="scopes">
<h3>Scopes</h3>
Add Scope of Work
#for (int i = 0; i < Model.ScopeOfWork.Count; i++)
{
<div class="scope">
<div class="form-group">
#Html.LabelFor(m => m.ScopeOfWork[i].ScopeOfWorkName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.ScopeOfWork[i].ScopeOfWorkName, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.ScopeOfWork[i].ScopeOfWorkName)
</div>
</div>
<input type="hidden" class="scopeindex" name="ScopeOfWork.Index" value="#i" />
<div class="indent materials">
<h4>Material</h4>
Add Material
#for (int j = 0; j < Model.ScopeOfWork[i].Materials.Count; j++)
{
<div class="material">
<div class="form-group">
#Html.LabelFor(m => m.ScopeOfWork[i].Materials[j].MaterialName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-2">
#Html.TextBoxFor(m => m.ScopeOfWork[i].Materials[j].MaterialName, new { #class = "form-control" })
</div>
<div class="col-md-10 col-md-offset-2">
#Html.ValidationMessageFor(m => m.ScopeOfWork[i].Materials[j].MaterialName)
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.ScopeOfWork[i].Materials[j].Quantity, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-1">
#Html.TextBoxFor(m => m.ScopeOfWork[i].Materials[j].Quantity, new { #class = "form-control" })
</div>
<div class="col-md-10 col-md-offset-2">
#Html.ValidationMessageFor(m => m.ScopeOfWork[i].Materials[j].Quantity)
</div>
</div>
<div class="form-group">
#Html.LabelFor(m => m.ScopeOfWork[i].Materials[j].Cost, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-1">
#Html.TextBoxFor(m => m.ScopeOfWork[i].Materials[j].Cost, new { #class = "form-control" })
</div>
<div class="col-md-10 col-md-offset-2">
#Html.ValidationMessageFor(m => m.ScopeOfWork[i].Materials[j].Cost)
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2">Category</label>
<div class="col-xs-2">
#Html.DropDownListFor(m => m.ScopeOfWork[i].Materials[j].SelectedCategory, Model.ScopeOfWork[i].Materials[j].CategoryList, "Please select", htmlAttributes: new { #class = "form-control" })
</div>
</div>
<input type="hidden" class="materialindex" name="ScopeOfWork[#i].Materials.Index" value="#j" />
</div>
}
</div>
</div>
}
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-success" />
</div>
</div>
</div>
}
<div id="newScope" style="display:none">
<div class="scope">
<div class="form-group">
<label for="_#__ScopeOfWorkName" class="control-label col-md-2">Scope Of Work</label>
<div class="col-md-10">
<input class="form-control" type="text" id="_#__ScopeOfWorkName" name="ScopeOfWork[#].ScopeOfWorkName" value="">
<span class="field-validation-valid text-danger" data-valmsg-for="ScopeOfWork[#].ScopeOfWorkName" data-valmsg-replace="true"></span>
</div>
</div>
<input type="hidden" class="scopeindex" name="ScopeOfWork.Index" value="#" />
<div class="materials">
<h4>Material</h4>
Add Material
</div>
<hr />
</div>
</div>
<div id="newMaterial" style="display:none">
<div class="form-group">
<label for="_#__Materials_%__MaterialName" class="control-label col-md-2">Material</label>
<div class="col-md-2">
<input class="form-control" type="text" id="_#__Materials_%__MaterialName" name="ScopeOfWork[#].Materials[%].MaterialName" value="">
<span class="field-validation-valid text-danger" data-valmsg-for="ScopeOfWork[#].Materials[%].MaterialName" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label for="_#__Materials_%__Quantity" class="control-label col-md-2">Quantity</label>
<div class="col-md-1">
<input class="form-control" type="text" id="_#__Materials_%__Quantity" name="ScopeOfWork[#].Materials[%].Quantity" value="">
<span class="field-validation-valid text-danger" data-valmsg-for="ScopeOfWork[#].Materials[%].Quantity" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label for="_#__Materials_%__Cost" class="control-label col-md-2">Cost</label>
<div class="col-md-1">
<input class="form-control" type="text" id="_#__Materials_%__Cost" name="ScopeOfWork[#].Materials[%].Cost" value="">
<span class="field-validation-valid text-danger" data-valmsg-for="ScopeOfWork[#].Materials[%].Cost" data-valmsg-replace="true"></span>
</div>
</div>
#*Drop down*#
<div class="form-group">
<label for="_#__Materials_%__SelectedCategory" class="control-label col-md-2">Category</label>
<div class="col-xs-2">
<select class="form-control category" id="_#__Materials_%__SelectedCategory" name="ScopeOfWork[#].Materials[%].SelectedCategory">
<option value="">--Select--</option>
</select>
</div>
</div>
<input type="hidden" class="materialindex" name="ScopeOfWork[#].Materials.Index" value="%" />
</div>
<script>
var form = $('form');
var scope = $('#newScope');
var material = $('#newMaterial');
var categories = #Html.Raw(Json.Encode(Model.CategoryList));
form.on('click', '.addmaterial', function () {
var clone = material.clone();
var scopeIndex = $(this).closest('.scope').find('.scopeindex').val();
clone.html($(clone).html().replace(/#/g, scopeIndex));
var materialIndex = new Date().getTime();
clone.html($(clone).html().replace(/%/g, materialIndex));
// drop down list
var select = clone.find('.category');
$.each(categories, function(index, item) {
select.append($('<option></option>').val(item.Value).text(item.Text));
});
$(this).closest('.materials').append(clone.html());
form.data('validator', null);
$.validator.unobtrusive.parse(form);
});
$('#addScope').click(function () {
var clone = scope.clone();
var scopeIndex = new Date().getTime();
clone.html($(clone).html().replace(/#/g, scopeIndex));
$('#scopes').append(clone.html());
form.data('validator', null);
$.validator.unobtrusive.parse(form);
});
</script>
Controller:
[HttpPost]
public ActionResult _CreateProject(ProjectViewModel project)
{
tblProject projectModel = new tblProject();
tblScopeOfWork scopeModel = new tblScopeOfWork();
tblMaterial materialModel = new tblMaterial();
if (ModelState.IsValid)
{
projectModel.ProjectName = project.ProjectName;
projectModel.ProjectLocation = project.ProjectLocation;
projectModel.ProjectDescription = project.ProjectDescription;
projectModel.WorkArea = project.WorkArea;
projectModel.ModeOfPayment = project.ModeOfPayment;
projectModel.Duration = project.Duration;
projectModel.StartDate = project.StartDate;
projectModel.EndDate = project.EndDate;
projectModel.ProfitSupervision = project.ProfitSupervision;
projectModel.ProjectStatus = project.ProjectStatus;
projectModel.ForemanId = project.ForemanId;
projectModel.ClientId = project.ClientId;
db.tblProjects.Add(projectModel);
db.SaveChanges();
//Get the recently created ProjectId
var recentProjectId = db.tblProjects.OrderByDescending(x => x.ProjectId).FirstOrDefault().ProjectId;
//Get all values from List of ScopeOfWork
//Add each ScopeOfWork to the database
for (int scopeIndex = 0; scopeIndex < project.ScopeOfWork.Count; scopeIndex++)
{
scopeModel = new tblScopeOfWork();
scopeModel.ScopeOfWork = project.ScopeOfWork[scopeIndex].ScopeOfWorkName;
scopeModel.ProjectId = recentProjectId;
db.tblScopeOfWorks.Add(scopeModel);
db.SaveChanges();
//Get the recently created ScopeOfWorkId
var recentScopeOfWorkId = db.tblScopeOfWorks.OrderByDescending(x => x.ScopeOfWorkId).FirstOrDefault().ScopeOfWorkId;
//Get all materials from its corresponding ScopeOfWork and save to database
for (int materialIndex = 0; materialIndex < project.ScopeOfWork[scopeIndex].Materials.Count; materialIndex++)
{
materialModel = new tblMaterial();
materialModel.Description = project.ScopeOfWork[scopeIndex].Materials[materialIndex].MaterialName;
materialModel.Quantity = project.ScopeOfWork[scopeIndex].Materials[materialIndex].Quantity;
materialModel.Cost = project.ScopeOfWork[scopeIndex].Materials[materialIndex].Cost;
materialModel.ScopeOfWorkId = recentScopeOfWorkId;
materialModel.CategoryId = project.ScopeOfWork[scopeIndex].Materials[materialIndex].SelectedCategory;
db.tblMaterials.Add(materialModel);
}
}
db.SaveChanges();
}
project.ScopeOfWork = new List<ScopeOfWorkViewModel>
{
new ScopeOfWorkViewModel()
{
Materials = new List<MaterialsViewModel>
{
new MaterialsViewModel()
{
CategoryList = new SelectList(db.tblCategories, "CategoryId", "CategoryName")
}
}
}
};
return View(project);
}
}
The problem is I tried to put some string ('ss') on the textbox of quantity and cost to see if the validation will work and this appears The value 'ss' is not valid for Quantity and The value 'ss is not valid for Cost.but it should be Please enter proper value. And also whenever the validation occurs, the Add Material link cannot add another newMaterial.
I have tried and check these answer and also DotNetFiddle but still it has some errors.
You do not need your regex (its an int so it can only accept a number anyway). And ditto for the cost property (but why have you made it decimal if you don't allow fractions?).
The validation for the type will be performed first, and because its invalid, no further validation is performed.
Change the attribute to to a [Required]
[Range(typeof(int), "0", "999")]
[Required(ErrorMessage = "Please enter proper value")]
public int? Quantity { get; set; }
Refer also this question/answer for changing the default error message for an invalid value for a type.
As a side note, your dynamically added items will not give client side validation because you have not added the necessary data-val-* attributes. You need to inspect the html generated for the elements in the for loops and copy the html exactly, except replacing the collection indexers.
You also have an issue with your POST method because you set the ScopeOfWork property to a new collection, and wipe out all the data the user has entered before you return the view. The basic structure of your POST method should be
[HttpPost]
public ActionResult _CreateProject(ProjectViewModel project)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(project);
return View(project);
}
// code to initialize your data models, save and redirect
}
private void ConfigureViewModel(ProjectViewModel model)
{
var categories = db.tblCategories;
model.CategoryList = new SelectList(categories , "CategoryId", "CategoryName");
foreach (var scope in model.ScopeOfWork)
{
foreach (var material in scope.Materials)
{
material.CategoryList = new SelectList(categories , "CategoryId", "CategoryName");
}
}
}
Note the ConfigureviewModel() method is also called from your GET method to populate the SelectLists
I have an Index page on which there is a section to write a project name and select from a dropdownlist a project type.
Below that I have a submit button that directs to the ActionResult method Create in the Projects controller.
Code:
[UPDATE]
index.cshtml:
#using reqcoll.ViewModels
#model myViewModel
#{
ViewBag.Title = "ReqColl - project";
}
#* First half *#
#using (Html.BeginForm("CreateProject", "Projects"))
{
#Html.AntiForgeryToken()
<div class="top-spacing col-md-12 col-lg-12 col-sm-12">
#RenderTopHalf(Model.modelProject)
</div>
}
#* Second half *#
#using (Html.BeginForm("CreateRequirement", "Projects"))
{
#Html.AntiForgeryToken()
<div class="form-group" id="pnSecondHalf">
#* Requirements list *#
<div class=" col-md-6 col-lg-6 col-sm-12">
#RenderBottomLeftHalf(Model.modelRequirement)
</div>
#* New/Edit requirements panel *#
<div class="col-md-6 col-lg-6 col-sm-12">
#RenderBottomRightHalf(Model.modelRequirement)
</div>
</div>
}
#* ================================================================================= ============= *#
#* Helpers *#
#helper RenderTopHalf(reqcoll.Models.Project project)
{
<div class=" well">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="row">
#Html.LabelFor(model => project.projectName, htmlAttributes: new { #class = "control-label col-md-2 col-lg-2 col-sm-12" })
<div class="col-md-10 col-lg-10 col-sm-12">
#Html.TextBoxFor(model => project.projectName, htmlAttributes: new { #class = "ProjectNameInput" })
#Html.ValidationMessageFor(model => project.projectName)
</div>
</div>
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="row row-spacing">
#Html.LabelFor(model => project.projectType, htmlAttributes: new { #class = "control-label col-md-2 col-lg-2 col-sm-12" })
<div class="col-md-10 col-lg-10 col-sm-12">
#Html.DropDownListFor(model => project.projectType, new SelectList(
new List<Object>{
new { value = 0 , text = "...Select..." },
new { value = 1 , text = "Windows application" },
new { value = 2 , text = "Web application" },
new { value = 3 , text = "Device application"}
},
"value",
"text",
project.projectType), htmlAttributes: new { #class = "DropDownList" })
#Html.ValidationMessageFor(model => project.projectType)
</div>
<input type="hidden" value="" id="hdProjectID" />
</div>
<div class="row top-spacing col-md-offset-5 col-sm-offset-5">
<div id="pnCreate" class=" col-sm-4 col-md-4 col-lg-4">
<input type="submit" class="btn btn-default" value="Create" />
</div>
<div id="pnEdit" class=" col-sm-4 col-md-4 col-lg-4">
<input type="submit" class="btn btn-default" value="Edit" />
|
<input type="submit" class="btn btn-default" value="Delete" />
</div>
</div>
</div>
}
#helper RenderBottomLeftHalf(reqcoll.Models.Requirement requirement)
{
<div class=" well">
<table class="table">
<tr>
<th>
#if (Model.modelProject.Requirements != null)
{
var m = Model.modelProject;
if (m.Requirements.Count > 0)
{
#Html.DisplayNameFor(model => model.modelProject.Requirements[0].shortDesc)
}
}
else
{
<label class="label label-primary col-sm-12 col-md-6 col-lg-6">No requirements available</label>
}
</th>
<th></th>
</tr>
#if (Model.modelProject.Requirements != null)
{
var m = Model.modelProject;
if (m.Requirements.Count > 0)
{
foreach (var item in Model.modelProject.Requirements)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.shortDesc)
</td>
<td>
#* buttons here*#
#*#Html.ActionLink("E", "Edit", new { id = item.requirementID }) |
#Html.ActionLink("D", "Delete", new { id = item.requirementID })*#
</td>
</tr>
}
}
}
</table>
</div>
}
#helper RenderBottomRightHalf(reqcoll.Models.Requirement requirement)
{
<div class=" well">
#Html.ValidationSummary(true)
<div class="row">
#Html.LabelFor(model => requirement.shortDesc, htmlAttributes: new { #class = "control-label col-md-4 col-lg-4 col-sm-12" })
<div class="col-md-8 col-lg-8 col-sm-12">
#Html.TextBoxFor(model => requirement.shortDesc, htmlAttributes: new { #class = "RequirementShortDesc" })
#Html.ValidationMessageFor(model => requirement.shortDesc)
</div>
</div>
#Html.ValidationSummary(true)
<div class="row row-spacing">
#Html.LabelFor(model => requirement.longDesc, htmlAttributes: new { #class = "control-label col-md-4 col-lg-4 col-sm-12" })
<div class="col-md-8 col-lg-8 col-sm-12 RequirementLongDesc">
#Html.EditorFor(model => requirement.longDesc)
#Html.ValidationMessageFor(model => requirement.longDesc)
</div>
</div>
#Html.ValidationSummary(true)
<div class="row row-spacing">
#Html.LabelFor(model => requirement.priorityCode, htmlAttributes: new { #class = "control-label col-md-4 col-lg-4 col-sm-12" })
<div class="col-md-8 col-lg-8 col-sm-12">
#foreach (var value in Enum.GetValues(requirement.priorityCode.GetType()))
{
<div class="control-label col-sm-5 col-md-5 col-lg-5">
#Html.RadioButtonFor(m => requirement.priorityCode, value)
#Html.Label(value.ToString())
</div>
}
#Html.ValidationMessageFor(model => requirement.priorityCode)
</div>
</div>
<input type="hidden" value="" id="hdRequirementID" />
<div class="row top-spacing col-md-offset-5 col-sm-offset-5">
<div id="pnReqCreate" class=" col-sm-12 col-md-6 col-lg-6">
#* submit button here *#
#*#Html.ActionLink("Add", "Add", "Requirement", new { #class = "btn btn-default btnSize" })*#
</div>
<div id="pnReqEdit" class=" col-sm-12 col-md-6 col-lg-6">
#* submit buttons here *#
#*#Html.ActionLink("Edit", "Edit", "Requirement", new { #class = "btn btn-default btnSize" })
#Html.ActionLink("Delete", "Delete", "Requirement", new { #class = "btn btn-default btnSize" })*#
</div>
</div>
</div>
}
#section Scripts {
<script>
$(function () {
var pID = $('#hdProjectID').val();
if (pID != null) {
if (pID.length > 0) {
$('#pnEdit').show();
$('#pnCreate').hide();
$('#pnSecondHalf').show();
} else {
$('#pnEdit').hide();
$('#pnCreate').show();
$('#pnSecondHalf').hide();
}
} else {
$('#pnEdit').hide();
$('#pnCreate').show();
$('#pnSecondHalf').hide();
}
var rID = $('#hdRequirementID').val();
if (rID != null) {
if (rID.length > 0) {
$('#pnReqEdit').show();
$('#pnReqCreate').hide();
} else {
$('#pnReqEdit').hide();
$('#pnReqCreate').show();
}
} else {
$('#pnReqEdit').hide();
$('#pnReqCreate').show();
}
});
</script>
#Scripts.Render("~/bundles/jqueryval")
}
ViewModel:
using reqcoll.Models;
namespace reqcoll.ViewModels
{
public class myViewModel
{
public Project modelProject;
public Requirement modelRequirement;
}
}
Controller:
using System.Web.Mvc;
using reqcoll.Models;
using reqcoll.ViewModels;
namespace reqcoll.Controllers
{
public class ProjectsController : Controller
{
private myContext db = new myContext();
// GET: Projects
public ActionResult Index()
{
// allow more than one model to be used in the view
var vm = new myViewModel()
{
modelProject = new Project() { projectName = "test", projectType = 1 },
modelRequirement = new Requirement() { requirementID = -1 },
};
return View(vm);
}
[HttpPost]
[ValidateAntiForgeryToken]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreateProject(myViewModel vm)
{
if (vm != null)
{
var ab = Request.Form;
// key 1: __RequestVerificationToken
// key 2: project.projectName
// key 3: project.projectType
if (ModelState.IsValid)
{
Project project = vm.modelProject;
// db.Project.Add(project.Item1);
// db.SaveChanges();
// return RedirectToAction("Index");
}
}
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}
[ORIGINAL]
#using (Html.BeginForm("Create", "Projects"))
{
#Html.AntiForgeryToken()
<div class="top-spacing col-md-12 col-lg-12 col-sm-12">
<div class=" well">
#Html.ValidationSummary(true)
<div class="row">
#Html.LabelFor(model => model.Item1.projectName, htmlAttributes: new { #class = "control-label col-md-2 col-lg-2 col-sm-12" })
<div class="col-md-10 col-lg-10 col-sm-12">
#Html.TextBoxFor(model => model.Item1.projectName, htmlAttributes: new { #class = "ProjectNameInput" })
#Html.ValidationMessageFor(model => model.Item1.projectName)
</div>
</div>
#Html.ValidationSummary(true)
<div class="row row-spacing">
#Html.LabelFor(model => model.Item1.projectType, htmlAttributes: new { #class = "control-label col-md-2 col-lg-2 col-sm-12" })
<div class="col-md-10 col-lg-10 col-sm-12">
#Html.DropDownListFor(model => model.Item1.projectType, new SelectList(
new List<Object>{
new { value = 0 , text = "...Select..." },
new { value = 1 , text = "Windows application" },
new { value = 2 , text = "Web application" },
new { value = 3 , text = "Device application"}
},
"value",
"text",
0), htmlAttributes: new { #class = "DropDownList" })
#Html.ValidationMessageFor(model => model.Item1.projectType)
</div>
<input type="hidden" value="" id="hdProjectID" />
</div>
<div class="row top-spacing col-md-offset-5 col-sm-offset-5">
<div id="pnCreate" class=" col-sm-4 col-md-4 col-lg-4">
<input type="submit" class="btn btn-default" value="Create" />
</div>
<div id="pnEdit" class=" col-sm-4 col-md-4 col-lg-4">
<input type="submit" class="btn btn-default" value="Edit" />
|
<input type="submit" class="btn btn-default" value="Delete" />
</div>
</div>
</div>
</div>
}
ProjectsController:
private myContext db = new myContext();
// GET: Projects
public ActionResult Index()
{
// allow more than one model to be used in the view
return View(new Tuple<Project, Requirement, Priority>(new Project(), new Requirement(), new Priority()));
}
[HttpPost]
[ValidateAntiForgeryToken]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Include = "projectName,projectType")] Project project)
{
if (ModelState.IsValid)
{
db.Project.Add(project);
db.SaveChanges();
return RedirectToAction("Index");
}
return RedirectToAction("Index");
}
So when the submit button is clicked, the ActionResult Create is called, but the ModelState is not valid and does not have the information enterd by the user.
What am I doing wrong?
Your model is looking like complex object as you are using model.Item1.projectName and model.Item1.projectType, but in action method you are trying to get values directly which is wrong.
[Updated code]
With the new code posted, this quick correction to your model will allow it to bind correctly from your view:
namespace reqcoll.ViewModels
{
public class myViewModel
{
public Project project;
public Requirement requirement;
}
}
[Original]
Despite the fact of using a Tuple<> type instead of defining a class that would encapsulate the data to pass to the view. You can still achieve what you want by creating a helper in your view.
#helper RenderMyProject(Project project) {
...
#Html.TextBoxFor(x=> project.projectType)
...
}
Then, you will call this helper
#RenderMyProject(model.Item1)
Whats the difference?
The name of the input will change. Instead of posting [Item1.projectType] Inside the response object to your controller, it will look like [project.projectType] which will be mapped to your project parameter automatically.
Found the problem.
I added {get; set;} in the myViewModel to both the models and then it worked.
so:
using reqcoll.Models;
namespace reqcoll.ViewModels
{
public class myViewModel
{
public Project Project { get; set; }
public Requirement Requirement { get; set; }
}
}
I'm making a website using asp.net mvc 4 & EF6 where admins can add new client information. So far everything is working fine but whenever I try to save the data by pressing Add button I get this error,
The model item passed into the dictionary is of type 'MyMvc.Models.UserInfo', but this dictionary requires a model item of type 'MyMvc.Models.BrManagement'
Here are my codes,
Controller
[HttpPost]
public ActionResult ClientManager(BrManagement ClTable)
{
if (Session["AdminNAME"] != null)
{
if (ModelState.IsValid)
{
var AddClient = ClTable.AddUserInfo;
abdb.UserInfoes.Add(AddClient);
abdb.SaveChanges();
return RedirectToAction("ClientManager", new { ClPanelId = "AllCl" });
}
return View(ClTable.AddUserInfo);
}
else
{
return RedirectToAction("AdminLogin");
}
}
Model
public class BrManagement
{
public Branch Branches { get; set; }
public IEnumerable<Branch> BrCollection { get; set; }
public UserInfo AddUserInfo { get; set; }
public IEnumerable<UserInfo> UserCollection { get; set; }
}
View
#using (Html.BeginForm("ClientManager", "Home", FormMethod.Post))
{
#Html.ValidationSummary(true)
<div class="editor-label">
<strong>Client USER ID</strong>
</div>
<div class="editor-field">
#Html.TextBoxFor(a => a.AddUserInfo.UserId)
#Html.ValidationMessageFor(a => a.AddUserInfo.UserId)
</div>
<div class="editor-label">
<strong>Client Password</strong>
</div>
<div class="editor-field">
#Html.TextBoxFor(a => a.AddUserInfo.Password)
#Html.ValidationMessageFor(a => a.AddUserInfo.Password)
</div>
<div class="editor-label">
<strong>Full Name</strong>
</div>
<div class="editor-field">
#Html.TextBoxFor(a => a.AddUserInfo.Name, new { size = 30 })
#Html.ValidationMessageFor(a => a.AddUserInfo.Name)
</div>
<div class="editor-label">
<strong>Address</strong>
</div>
<div class="editor-field">
#Html.TextBoxFor(a => a.AddUserInfo.AddressLine1, new { size = 30 })
#Html.ValidationMessageFor(a => a.AddUserInfo.AddressLine1)
</div>
#Html.HiddenFor(a => a.AddUserInfo.CreatedDate, new { #Value = System.DateTime.Now })
#Html.HiddenFor(a => a.AddUserInfo.IsActive, new { #Value = "N" })
#Html.HiddenFor(a => a.AddUserInfo.IsApproved, new { #Value = "N" })
#Html.HiddenFor(a => a.AddUserInfo.IsinfoMatched, new { #Value = "N" })
#Html.HiddenFor(a => a.AddUserInfo.IsReportView, new { #Value = "N" })
#Html.HiddenFor(a => a.AddUserInfo.IsVarified, new { #Value = "N" })
<br />
<p><input type="submit" class="btn btn-info" value="Add" /></p>
}
I've used the same code for Branches & BrCollection model, both of them are working fine. UserCollection model is also working fine. Why is this happening for AddUserInfo model? I've searched a lot but couldn't find any solution similar to mine. Need this help really bad. Your help will be appreciated! Tnx.
UPDATE
View(FULL)
#model ABCoLtd.Models.BrManagement
#{
ViewBag.Title = "ClientManager";
string active = ViewBag.ClActive.ToString();
Layout = "~/Views/Shared/_ALayout.cshtml";
}
<link href="~/Content/DataTables-1.10.4/css/jquery.dataTables.min.css" rel="stylesheet" />
<body>
<script src="~/Scripts/DataTables-1.10.4/jquery.dataTables.min.js"></script>
<script>
$(document).ready(function () {
var tabpane = '#active';
$("#" + tabpane).addClass("active");
$('#BrTable').DataTable({
"aoColumns": [
{ "bSortable": true },
{ "bSortable": true },
{ "bSortable": true },
{ "bSortable": true }
]
});
});
</script>
<br /><br /><br /><br />
<div class="container well" style="min-width: 100%; padding-right: 5px;">
<h3>Client Manager</h3><hr style="border-top: 2px solid #096596;" />
<ul class="nav nav-tabs">
<li>All Clients </li>
<li>Add Clients</li>
</ul>
<div class="tab-content">
<div class="tab-pane" id="AllCl" style="padding-top: 10px; padding-left: 10px;">
<h4>Manage Clients</h4><hr style="border-top: 2px solid #096596;" />
<div class="table-responsive">
<table id="BrTable" class="table table-striped">
<thead>
<tr><th>BOID</th><th>Name</th><th>Email</th><th>Phone</th></tr>
</thead>
<tbody>
#foreach(var item in Model.UserCollection)
{
<tr>
<td>#Html.DisplayFor(modelItem => item.UserId)</td>
<td>#Html.DisplayFor(modelItem => item.Name)</td>
<td>#Html.DisplayFor(modelItem => item.Email)</td>
<td>#Html.DisplayFor(modelItem => item.Phone1)</td></tr>
}
</tbody>
</table>
</div>
</div>
<div class="tab-pane" id="AddCl" style="padding-top: 10px; padding-left: 10px;">
<h4>Add Client</h4><hr style="border-top: 2px solid #096596;" />
#using (Html.BeginForm("ClientManager", "Home", FormMethod.Post))
{
#Html.ValidationSummary(true)
<div class="editor-label">
<strong>Client BO Account No.</strong>
</div>
<div class="editor-field">
#Html.TextBoxFor(a => a.AddUserInfo.BOAccountNo, new { size = 30 })
#Html.ValidationMessageFor(a => a.AddUserInfo.BOAccountNo)
</div>
<div class="editor-label">
<strong>Client USER ID</strong>
</div>
<div class="editor-field">
#Html.TextBoxFor(a => a.AddUserInfo.UserId)
#Html.ValidationMessageFor(a => a.AddUserInfo.UserId)
</div>
<div class="editor-label">
<strong>Client Password</strong>
</div>
<div class="editor-field">
#Html.TextBoxFor(a => a.AddUserInfo.Password)
#Html.ValidationMessageFor(a => a.AddUserInfo.Password)
</div>
<div class="editor-label">
<strong>Full Name</strong>
</div>
<div class="editor-field">
#Html.TextBoxFor(a => a.AddUserInfo.Name, new { size = 30 })
#Html.ValidationMessageFor(a => a.AddUserInfo.Name)
</div>
<div class="editor-label">
<strong>Address</strong>
</div>
<div class="editor-field">
#Html.TextBoxFor(a => a.AddUserInfo.AddressLine1, new { size = 30 })
#Html.ValidationMessageFor(a => a.AddUserInfo.AddressLine1)
</div>
#Html.HiddenFor(a => a.AddUserInfo.CreatedDate, new { #Value = System.DateTime.Now })
#Html.HiddenFor(a => a.AddUserInfo.IsActive, new { #Value = "N" })
#Html.HiddenFor(a => a.AddUserInfo.IsApproved, new { #Value = "N" })
#Html.HiddenFor(a => a.AddUserInfo.IsinfoMatched, new { #Value = "N" })
#Html.HiddenFor(a => a.AddUserInfo.IsReportView, new { #Value = "N" })
#Html.HiddenFor(a => a.AddUserInfo.IsVarified, new { #Value = "N" })
#Html.HiddenFor(a => a.AddUserInfo.UserType, new { #Value = "C" })
<br />
<p><input type="submit" class="btn btn-info" value="Add" /></p>
}
</div>
</div>
</div>
</body>
Your view expect BrManagement
#model MyMvc.Models.BrManagement
But you returned ClTable.AddUserInfo which is type of UserInfo, that is why you got that error.
To fix this, change return View(ClTable.AddUserInfo); to return View(ClTable); in your controller.
I need to get the selected dropdown value from partial view and included in the CategoryID in the Book class...
[Authorize]
public PartialViewResult GetAllCategory()
{
ProcessSVC.Category newCategory = new ProcessSVC.Category();
newCategory.ChildCategories = obj1.GetAllCategories(String.Empty);
return PartialView(newCategory);
}
GetAllCategory.cshtml (PartialView)
#model MvcAdminTemplate.ProcessSVC.Category
#Html.DropDownListFor(m => m.ParentID, new SelectList(Model.ChildCategories, "ID", "DisplayName"), new { #class = "form-control" })
Create View:
[Authorize]
[HttpPost]
public ActionResult Create()
{
MvcAdminTemplate.ProcessSVC.Book oBook = new ProcessSVC.Book();
return View(oBook);
}
Create.cshtml
#model MvcAdminTemplate.ProcessSVC.Book
#{
ViewBag.Title = "Create";
}
#Html.Partial("_LeftMenu")
<!-- content -->
<h2>Create</h2>
<div class="col-md-10">
#using (Html.BeginForm("Create", "Books", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="row">
<div class="bootstrap-admin-no-table-panel-content bootstrap-admin-panel-content collapse in">
<form class="form-horizontal">
<fieldset>
<legend>Add Book</legend>
<div class="form-group">
<div class="col-lg-2">
#Html.LabelFor(m => m.BookName, new { #class = "control-label" })
</div>
<div class="col-lg-10">
#Html.TextBoxFor(m => m.BookName, new { #class = "form-control", type = "text" })
<p class="help-block"> </p>
</div>
</div>
<div class="form-group">
<div class="col-lg-2">
#Html.LabelFor(m => m.Description, new { #class = " control-label" })
</div>
<div class="col-lg-10">
#Html.TextBoxFor(m => m.Description, new { #class = "form-control ", type = "text" })
<p class="help-block"> </p>
</div>
</div>
<div class="form-group divCategory">
<div class="col-lg-2">
#Html.Label("Parent Category", new { #class = " control-label" })
</div>
<div class="col-lg-10">
#Html.HiddenFor(m => m.BookId, new { #class = "hdn-id" })
#Html.Action("GetAllCategory","Books")
<span class="help-block"> </span>
</div>
<button type="submit" class="btn btn-primary">Save changes</button>
<button type="reset" class="btn btn-default">Cancel</button>
</div>
</fieldset>
</form>
</div>
</div>
}
After submission of the above form i need to get Book attributes and Category "Selected Category".
[Authorize]
[HttpPost]
public ActionResult Create(ProcessSVC.Book oBook)
{
oBook.LanguageID = 1;
_service.AddBooks(oBook.ActualPrice,oBook.ActualPriceString, oBook.Author, oBook.BookId, oBook.BookName, oBook.CategoryID, oBook.Currency, oBook.CurrentPrice, oBook.CurrentPriceString,
oBook.Description, oBook.DiscountPercentage, oBook.DiscountValue, oBook.LanguageID,
oBook.NativeLanguageName, oBook.Publisher);
TempData.Add("SuccessMessage", " New book " + oBook.BookName + " Added !");
return RedirectToAction("Index");
}
Please suggest me.
To correctly bind your model, you need to create the dropdown in the main view. This line in you partial view
#Html.DropDownListFor(m => m.ParentID, ....
will render a select
<select name="ParentID" ...
but you model is expecting a property named CategoryID
In you Create() method, generate the SelectList and assign to a view model property or to ViewBag and then (instead or #Html.Action("GetAllCategory","Books") use
#Html.DropDownListFor(m => m.CategoryID, ....
use this line in your GetAllCategory.cshtml file
#Html.DropDownList("CategoryID", new SelectList(Model.ChildCategories, "ID", "DisplayName"), new { #class = "form-control" })