ASP.NET List<SelectListItem> is passing null to controller - asp.net

I have a form, with radiobuttons. After selecting one of them, in [POST] method, the 'Companies' List Count is still 0. Do you have any idea what causes the problem?
Controller:
// GET: Commission/Create
public ActionResult Create()
{
CommissionMVCCreateModel commission = new CommissionMVCCreateModel();
List<CompanyMVCModel> companies = SQLiteDataAccess.LoadCompanies();
commission.Companies = companies.Select(x => new SelectListItem { Text = x.CompanyName, Value = x.Id.ToString() }).ToList();
return View(commission);
}
// POST: Commission/Create
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult Create(CommissionMVCCreateModel commissionModel)
{
try
{
if(ModelState.IsValid)
{
}
// TODO: Add insert logic here
return View();
}
catch
{
return View();
}
}
Model:
public class CommissionMVCCreateModel
{
public List<SelectListItem> Companies { get; set; } = new List<SelectListItem>();
...
...
}
View:
<form>
...
...
#Html.LabelFor(model => model.Companies, htmlAttributes: new { #class = "control-label" })
<div class="btn-group-vertical">
#foreach (var company in Model.Companies)
{
<label class="btn btn-primary col-md-offset-2 col-md-5 ">
<input type="radio" name="company" id="#company.Value" value="#company.Value"/>
#company.Text
</label>
}
</div>
...
...
</form>
In the form i have also part for creating new Company, and it's values are passed correctly. The only problem i have is with this SelectListItem. I tried also using SelectList, but the result was the same.

Related

Incorrect values being posted from View after dynamically removing rows

I have the option of dynamically adding and removing rows in my form. Before submitting the form if I remove the last row it works fine but if I remove any other row not in a sequence then add a row and then submit the form it posts the wrong value in my model (i.e. it changes the data of the new row to the data of previous row)
I am using hidden fields but that did not help. The main view renders a partial view which contains the data for the rows. I have tried to use EditorFor but that didn't help either.
Main View:
#using (Html.BeginForm("Submit", "TimeEntry", FormMethod.Post, new {
#class= "form-container" }))
{
<div id="times">
#{Html.RenderPartial("TimeTable", Model);}
</div>
<input name="Submit" class="form-control btn btn-primary" type="submit"
id="submit" value="Submit" />
}
View Model:
public class TimeFilter
{
public List<TimeItemWeekly> TimeItemWeekly { get; set; }
}
public class TimeItemWeekly
{
public string SelectedJob { get; set; }
public List<SelectListItem> Job { get; set; }
public string SelectedServiceItem { get; set; }
public List<SelectListItem> ServiceItem { get; set; }
}
Partial View:
#model NWwebappCS.Models.TimeFilter
#if (Model.TimeItemWeekly != null)
{
for (int i = 0; i < Model.TimeItemWeekly.Count(); i++)
{
<div class="row-container #(Model.TimeItemWeekly[i].HasError ?
<div class="row">
<input type="hidden" name="TimeItemWeekly.Index" value="#i" />
<div class="customer-details">
#Html.DropDownListFor(x => x.TimeItemWeekly[i].SelectedJob,
Model.TimeItemWeekly[i].Job, new { #class = "jobs select
form-control" })
</div>
<div class="service-details">
#if (Model.TimeItemWeekly[i].ServiceItem != null)
{
#Html.DropDownListFor(x =>
x.TimeItemWeekly[i].SelectedServiceItem,
Model.TimeItemWeekly[i].ServiceItem, new { #class =
"service-items select" })
}
else
{
<select name="TimeItemWeekly[#i].SelectedServiceItem"
class="service-items select">
<option></option>
</select>
}
</div>
</div>
</div>
}
}
Controller:
[System.Web.Http.HttpPost]
public ActionResult Submit(Models.TimeFilter Model, string submit)
{
switch (submit)
{
case "Submit":
string errorMessage = ValidateTime(Model);
FillLists(Model);
if (errorMessage == "")
{
DataTable timeRows = GetWeekData(Model);
DeleteTime(Model, timeRows);
SaveTime(Model, timeRows);
if (Model.TimeItemWeekly != null)
{
Model.TimeItemWeekly =
Model.TimeItemWeekly.OrderBy(x => x.Job.Where(y
=>
y.Selected).First().Text).ThenBy(x => x.ServiceItem.Where(y
=> y.Selected).First().Text).ToList();
}
GetUserInfo(Model);
HasPrivilege(Model);
TempData["SuccessMessage"] = "Changes Saved!";
return View("~/Views/TimeEntry/Index.cshtml",
Model);
}
else
{
TempData["ErrorMessage"] = errorMessage;
return View("~/Views/TimeEntry/Index.cshtml",
Model);
}
default:
return View("~/Views/TimeEntry/Index.cshtml", Model);
}
}
}
This happens because the indices get out of order and the mvc model binder can't bind the list properly because the name values which contain the index position are no longer sequential.
One way to solve this is to create an ajax call that returns your partial view view with an updated data set that doesn't contain the row you removed, and replacing the old partial HTML with a new one that has correct indices.
It would look something like this:
JS event for the remove button:
$(document).on("click", ".red-box", function () {
var id = 1; // this would be the id of the thing you are removing, if needed
$.get('/path/to/remove', { id: id }, function(data){
$("#times").html(data);
}
});
Action
public ActionResult Remove(int id) {
// Get data based on id and make any dB updates you need to make
var model = GetData(); // Get new NWwebappCS.Models.TimeFilter without removed row
return PartialView("TimeTable", model);
}
Something along those lines....

ASP.NET code snippit to query database for unique field value

I'm trying to create custom remote data annotation to check for unique values.
So far I have:
[Remote("checkForUniqueSpeciesName", "Create", ErrorMessage = "A Species by that name already exists.")]
public string SpeciesName { get; set; }
and
public ActionResult checkForUniqueSpeciesName(string species_name)
{
bool is_unique = ........
return Json(is_unique, JsonRequestBehavior.AllowGet);
}
To be honest, I don't really understand how this works, I'm just trying to follow examples found on the web. I guess checkForUniqueSpeciesName is called when the form is submitted, and the method returns true or false. Is there something I need to put in the view to make the validation message come up, such as?
#Html.ValidationMessageFor(model => model.SpeciesName, "", new { #class = "text-danger" })
Do I need that?
Model Species.cs:
public class Species
{
[Key]
public int SpeciesId { get; set; }
[Display(Name = "Species")]
[Required(ErrorMessage = "You must enter a species name.")]
[Remote("CheckForUniqueSpeciesName", "Create", ErrorMessage = "A Species by that name already exists.")]
public string SpeciesName { get; set; }
}
Controller SpeciesController.cs:
namespace Gators3.Controllers
{
public class SpeciesController : Controller
{
private GatorsContext db = new GatorsContext();
// GET: Species
public ActionResult Index()
{
return View(db.Species.ToList());
}
// GET: Species/Create
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "SpeciesId,SpeciesName")] Species species)
{
if (ModelState.IsValid)
{
db.Species.Add(species);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(species);
}
public ActionResult CheckForUniqueSpeciesName(string speciesName)
{
using (GatorsContext ctx = new GatorsContext())
{
bool isUnique = !ctx.Species.Any(s => s.SpeciesName == speciesName);
return Json(isUnique, JsonRequestBehavior.AllowGet);
}
}
.
.
.
.
View Views->Species->Create.cshtml:
#model Gators3.Models.Species
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Species</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.SpeciesName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.SpeciesName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.SpeciesName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
I guess checkForUniqueSpeciesName is called when the form is
submitted, and the method returns true or false.
No, that is not the case. The [RemoteAttribute] adds some JavaScript to your page automatically that will call a method on your Controller to do some server side validation and display the result on the page without the user needing to submit the whole HTML form. i.e. The validation is invoked when you tab out of the text box, not when you click submit.
With your code, I assume your controller is named CreateController?
I'm guessing you're just missing your data access code to actually check uniqueness?
So something like this would be required:
public ActionResult CheckForUniqueSpeciesName(string speciesName)
{
using (YourEntityFrameworkDbContext ctx = new YourEntityFrameworkDbContext())
{
bool isUnique = !ctx.Species.Any(s => s.SpeciesName == speciesName);
return Json(isUnique , JsonRequestBehavior.AllowGet);
}
}
Then in your view, you just need something like this:
#Html.ValidationMessageFor(x => x.SpeciesName)
Which will display the validation message you specified in your [Remote] attribute.
By the way, just as a side note - the coding conventions/casing you've applied to some of your code won't be popular with most C# programmers (unless your team are abiding by an unusual standard) so note the formatting I've applied.
Update - I think your code needs to have the following:
[Remote("CheckForUniqueSpeciesName", "Species", ErrorMessage="A Species by that name already exists.")]

ASP.NET MVC passing data to a controller

I am facing a problem for passing data to a controller.
I have a class that contains a list. I pass an instance of my object in my controller to retrieve my view. Which is actually a form. And when I submit my form, the object becomes empty, then there's that he depart at least two entries in the list. I have used the same method name to retrieve my data via Form.Method.
This is my code :
Model
public class XMLRecord
{
public string TypeDoc { get; set; }
public string Type { get; set; }
public string Contenu { get; set; }
public string DocName { get; set; }
public IEnumerable<XMLRecord> Records { get; set; }
}
View
#model ManageXML.Models.XMLRecord
<body>
#using (Html.BeginForm("HandleForm", "XMLRecord", FormMethod.Post, new { #class = "form-horizontal", #role = "form", #id = "FormCreateXML" }))
{
<fieldset>
<legend> XML Editor</legend>
#if (Model.Records == null)
{
<p>None</p>
}
else
{
<ul id="XmlEditor" style="list-style-type: none">
#foreach (var record in Model.Records)
{
Html.RenderPartial("XmlEditor", record);
}
</ul>
<button type="button" class="btn btn-default" id="addAnother">Add another</button>
}
</fieldset>
<p>
<button type="submit" class="btn btn-default">Save</button>
<button type="button" class="btn btn-default">Cancel</button>
</p>
}
</body>
Partial View
#model ManageXML.Models.XMLRecord
<li style="padding-bottom:15px" >
#using (Html.BeginCollectionItem("XmlRecords")) {
<img src="#Url.Content("~/Content/images/draggable.jpg")" height="20"width="20" style="cursor: move" alt=""/>
#Html.LabelFor(model => model.Type)
#Html.EditorFor(model => model.Type)
#Html.LabelFor(model => model.Contenu)
#Html.EditorFor(model => model.Contenu)
Delete
}
</li>
Controller
public class XMLRecordController : Controller
{
[HttpGet]
public ActionResult HandleForm()
{
var file = new XMLRecord()
{
Records = new List<XMLRecord>(){
new XMLRecord(){Type="Title", Contenu="Head of Service"},
new XMLRecord(){Type="Item", Contenu="Dr. A.Libois"}
}
};
return View(file);
}
[HttpPost]
public ActionResult HandleForm(XMLRecord file)
{
if (file == null)
{
return HttpNotFound();
}
else
{
return Content("It's OK");
}
}
}
In your partial view I changed the collection's name to Records instead of XMLRecords because the name of the collection in your model is Records.
<li style="padding-bottom:15px" >
#using (Html.BeginCollectionItem("Records")) {
<img src="#Url.Content("~/Content/images/draggable.jpg")" height="20"width="20" style="cursor: move" alt=""/>
#Html.LabelFor(model => model.Type)
#Html.EditorFor(model => model.Type)
#Html.LabelFor(model => model.Contenu)
#Html.EditorFor(model => model.Contenu)
Delete
}
Since your Model(XMLRecord) contains a member named Record, you have to use
#using (Html.BeginCollectionItem("Records"))
instead of
#using (Html.BeginCollectionItem("XmlRecords"))
If the problem still exists, take a look at similar problem mentioned in nested-begincollectionitem.

Passing multiple models from one view to a controller

If I google for "multiple models in one view" I can only find results about how I can pass models to a view. But I'm interested in the "from view to controller" direction.
So let's assume:
I have 3 different forms and 1 table (WebGrid) in one view.
And I have one model per form and one model for the table.
Let my model classes be ModelF1, ModelF2, ModelF3 and ModelT.
All the examples I have seen until now uses a container ViewModel like
class MyViewModel {
ModelF1 inst1,
ModelF2 inst2,
ModelF3 inst3,
ModelT instT
}
And then they pass it between view <-> controller in 2 ways.
But I want to catch my models this way without using a viewmodel:
class MyController {
ActionResult Index() {
return new View(modelF1Instance, modelF2Instance, modelF3Instance, modelTInstance);
}
ActionResult Form1Action(ModelF1 inst1, ModelT instT) {
// process models after a form1 submit
}
ActionResult Form2Action(ModelF2 inst2, ModelT instT) {
// process models after a form2 submit
}
ActionResult Form3Action(ModelF3 inst3, ModelT instT) {
// process models after a form3 submit
}
}
Is this possible without parsing the whole form elements in a CustomBinder?
Firstly you can only send a strongly typed view model back to your view using
return View(model);
View is a method on the base class, not a class to be instantiated with return new View(...
Then to your real question: Yes you can do this, but using a top level ViewModel which contains your different form items is far, far easier in the majority of use cases. The main problem the top level container ViewModel handles really well is value persistence and server-side validation and error messages between round trips.
If you are only worried about the perception of inefficiency from creating a top level ViewModel container, then don't. This is far more efficient than all the workarounds you may have to put in place in order to get well behaved forms working without the top level ViewModel.
There is some example code below. The code below should demonstrate that using models contained within the top level ViewModel is just simpler and neater: some of the forms deliberately don't round trip some of the state. Note the usage of HiddenFor and ModelState.Clear which are both related to what you are trying to do, but even these won't persist the value for inst4.Name for Form4Submit. The various options explored are:
Use a query parameter to denote which form is being posted
Use a different form name, but still with the view model.
Use a redirect-only Action for the form (send new instances, and only part of the viewmodel)
Use a mixture of the above
public class TestController : Controller
{
//
// GET: /Test/
[System.Web.Mvc.HttpGet]
public ActionResult Index(string msg = null)
{
var model = new MyViewModel
{
Inst1 = new ModelF1 { Name = "Name of F1" },
Inst2 = new ModelF2 (),
InstT = new ModelT {Name = "Name of T"},
PostNumber = 0,
Message = msg
};
return View(model);
}
[System.Web.Mvc.HttpPost]
public ActionResult Index(MyViewModel model, int option = 1)
{
// process models after a form1/2 submit
model.Message = "You posted " +
((option == 1) ? model.Inst1.Name : model.Inst2.Name)
+ " to Index for "
+ ((option == 1) ? "inst1" : "inst2");
model.PostNumber ++;
// This, and the hiddenFor are required to allow us to update the PostNumber each time
ModelState.Clear();
return View(model);
}
[System.Web.Mvc.HttpPost]
public ActionResult Form2Submit(MyViewModel model)
{
// process models after a form2 submit
model.Message = "You posted " + model.Inst2.Name + " to Form2Submit";
model.PostNumber++;
ModelState.Clear();
return View("Index", model);
}
[System.Web.Mvc.HttpPost]
public ActionResult Form3Submit(ModelF3 inst3, ModelT instT)
{
// process models after a form3 submit
var n = instT.Name;
var msg = "You posted " + inst3.Name + ", " + n + " to Form3Submit";
// We no longer have access to pass information back to the view, so lets redirect
return RedirectToAction("Index", new { msg = msg });
}
[System.Web.Mvc.HttpPost]
public ActionResult Form4Submit(ModelF4 inst4, MyViewModel model)
{
// process models after a form4 submit
var n = model.InstT.Name;
model.Message = "You posted " + inst4.Name + ", " + n + " to Form4Submit";
model.PostNumber++;
ModelState.Clear();
return View("Index", model);
}
public class MyViewModel
{
public int PostNumber { get; set; }
public string Message { get; set; }
public ModelF1 Inst1 { get; set; }
public ModelF2 Inst2 { get; set; }
public ModelT InstT { get; set; }
}
public class ModelBase { public string Name { get; set; } }
public class ModelF1 : ModelBase {}
public class ModelF2 : ModelBase { }
public class ModelF3 : ModelBase { }
public class ModelF4 : ModelBase { }
public class ModelT : ModelBase { }
}
Then for the multi-form view:
#using MyWebSite.Controllers;
#model TestController.MyViewModel
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<p>
#Html.Raw(Model.PostNumber) : #Html.Raw(Model.Message)
</p>
<p>
#Html.LabelFor(m => Model.InstT) : <br />
#Html.DisplayFor(m => Model.InstT)
</p>
<div>
<p>Default form submit</p>
#using (Html.BeginForm())
{
<div>
#Html.HiddenFor(m => m.PostNumber)
#Html.LabelFor(m => Model.Inst1.Name)
#Html.TextBoxFor(m => Model.Inst1.Name)
</div>
<input type="submit" value="Submit Index" />
}
</div>
<div>
<p>Use a parameter to denote the form being posted</p>
#using (Html.BeginForm("Index", "Test", new { option = 2 }))
{
<div>
#* Omitting these will not persist them between trips
#Html.HiddenFor(m => Model.Inst1.Name)
#Html.HiddenFor(m => Model.InstT.Name)*#
#Html.HiddenFor(m => m.PostNumber)
#Html.LabelFor(m => Model.Inst2.Name)
#Html.TextBoxFor(m => Model.Inst2.Name)
</div>
<input type="submit" value="Submit with option parameter" />
}
</div>
<div>
<p>Use a different form name, but still use the ViewModel</p>
#using (Html.BeginForm("Form2Submit", "Test"))
{
<div>
#Html.HiddenFor(m => Model.Inst1.Name)
#Html.HiddenFor(m => Model.InstT.Name)
#Html.HiddenFor(m => m.PostNumber)
#Html.LabelFor(m => Model.Inst2.Name)
#Html.TextBoxFor(m => Model.Inst2.Name)
</div>
<input type="submit" value="Submit F2" />
}
</div>
<div>
<p>Submit with a redirect, and no ViewModel usage.</p>
#using (Html.BeginForm("Form3Submit", "Test"))
{
var inst3 = new TestController.ModelF3();
<div>
#Html.HiddenFor(m => Model.InstT.Name)
#Html.LabelFor(m => inst3.Name)
#Html.TextBoxFor(m => inst3.Name)
</div>
<input type="submit" value="Submit F3" />
}
</div>
<div>
<p>Submit with a new class, and the ViewModel as well.</p>
#using (Html.BeginForm("Form4Submit", "Test"))
{
var inst4 = new TestController.ModelF4();
<div>
#Html.HiddenFor(m => Model.Message)
#Html.HiddenFor(m => Model.PostNumber)
#Html.HiddenFor(m => Model.Inst1.Name)
#Html.HiddenFor(m => Model.Inst2.Name)
#Html.HiddenFor(m => Model.InstT.Name)
#Html.LabelFor(m => inst4.Name)
#Html.TextBoxFor(m => inst4.Name)
</div>
<input type="submit" value="Submit F4" />
}
</div>
</body>
</html>

ASP.NET MVC redirect actions error with 'create' partial view on 'index' page

I'm having a problem with partial views. I have an index view of Announcements and I'm trying to add a partial view to create a new Announcement within the same page.
I can display the partial view, and submit the form to create a new record. The record gets submitted into the database, but when re-rendering the page, I get the error: Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper', {"Child actions are not allowed to perform redirect actions."} on my Html.Action statement in my index page.
I've been struggling to make this work, and have firstly changed the Html.Partial to a Html.Action statement as the controller methods weren't firing, then secondly, after I read that this error is because while rendering the page, .NET doesn't know what my redirect action is doing so automatically stops it, tried changing the Html.Action to Html.RedirectAction inside a code block, but still get the same error detailed above.
My model is quite simple:
public class Announcement
{
public Announcement()
{
AnnouncementDate = System.DateTime.Now;
}
[Key]
public int AnnouncementID { get; set; }
public string Title { get; set; }
public string Type { get; set; }
}
My Controller methods:
public ViewResult Index(string searchString, int? page)
{
var Announcements = from a in db.Announcements
select a;
if (!String.IsNullOrEmpty(searchString))
{
Announcements = Announcements.Where(s => (s.Title.ToUpper().Contains(searchString.ToUpper()) || s.AnnouncementText.ToUpper().Contains(searchString.ToUpper())));
}
Announcements = Announcements.OrderBy(s => s.Title);
int pageSize = 10;
int pageNumber = (page ?? 1);
return View(Announcements.ToPagedList(pageNumber, pageSize));
}
//
// GET: /Announcement/Create
public ActionResult Create()
{
Announcement announcement = new Announcement();
return PartialView(announcement);
}
//
// POST: /Announcement/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Announcement announcement)
{
if (ModelState.IsValid)
{
db.Announcements.Add(announcement);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(announcement);
}
Index.cshtml
#model PagedList.IPagedList<Project.Models.Announcement>
#using PagedList.Mvc;
#using PagedList;
#using (Html.BeginForm())
{
#Html.TextBox("SearchString", ViewBag.CurrentFilter as string, new { #class = "search-query", placeholder = "Search by name" })
<input type="submit" value="Search" class="btn" />
}
#item.Title
#item.Type
#Html.Action("Create"); // This is the line causing errors after I submit the Create form. Have tried changing to Html.RedirectAction
Create.cshtml:
#model Project.Models.Announcement
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(model => model.Title, new { #style = "width:250px" })
#Html.TextBoxFor(model => model.Type, new { #style = "width:250px" })
<input type="submit" value="Create" class="btn btn-small" />
}
After doing some testing locally...
You can keep
#Html.Action("Create")
However, you have to change one small thing. Define what action the POST points to in your form :)
#model Project.Models.Announcement
#using (Html.BeginForm("Create"))
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(model => model.Title, new { #style = "width:250px" })
#Html.TextBoxFor(model => model.Type, new { #style = "width:250px" })
<input type="submit" value="Create" class="btn btn-small" />
}

Resources