Pass model value from View to controller is not working - asp.net

My model has two properties, one of them is an object of another class
public class Association : Entity
{
public Association()
{
this.User = new User();
}
public User User
{
get;
set;
}
public Role Role
{
get;
set;
}
};
and my view is strongly typed to this model
#model MuddyBoots.Greenlight.Association
.
.
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<div>
#Html.TextBoxFor(model => model.User.FirstName,new { id = "first-name" })
<span class="red-asterisk">#Html.ValidationMessageFor(model => model.User.FirstName)</span>
</div>
<div>
#Html.HiddenFor(model => model.Role, new { id="hiddenRole"})
<ul id="user-roles">
<li><input type="radio" name="user-role" id="role-admin" value="01" checked="checked" /> Read only</li>
<li><input type="radio" name="user-role" id="role-member" value="02" /> Restricted</li>
<li><input type="radio" name="user-role" id="role-read" value="03"/> Standard</li>
<li><input type="radio" name="user-role" id="role-subscriber" value="04" /> Administrator</li>
</ul>
</div>
}
my controller function is written like that:
[HttpPost]
public ActionResult AddUser(Association association)
{
string firstName = association.User.FirstName;
var role = association.Role;
IRepository<Association> associationRepository = new IRepository<Association>(db);
if (ModelState.IsValid)
{
siteRepository.Update(site);
return RedirectToAction("Index");
}
return View(association);
}
My problem is: when I post my view, my association object is null, it has no values.
to be more precise, when I try to debug these 2 lines:
string firstName = association.User.FirstName;
var role = association.Role;
their values are null, but if I comment the first line, the role variable has a value. So am sensing that the problem is related to the User property, but I do not know how to solve it.

You seem to have used some hidden field for the Role type:
#Html.HiddenFor(model => model.Role, new { id = "hiddenRole" })
But the Role property is a complex type, you cannot serialize it as a hidden field. You will have to use hidden fields for each of the properties you want to be sent:
#Html.HiddenFor(model => model.Role.Property1)
#Html.HiddenFor(model => model.Role.Property2)
#Html.HiddenFor(model => model.Role.Property...)
Also you seem to be using some radio buttons inside your form which are named user-role but you cannot have a property on your Role class with this name (you cannot have dash in a property name), so I guess you will have to use the proper name here if you want the value of those radio buttons to be bound to some property of the Role class on your Association model.
For example let's suppose that your Role class looks like this:
public class Role
{
public string Value { get; set; }
}
Now your view could look like this:
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<div>
#Html.TextBoxFor(model => model.User.FirstName, new { id = "first-name" })
<span class="red-asterisk">
#Html.ValidationMessageFor(model => model.User.FirstName)
</span>
</div>
<div>
<ul id="user-roles">
<li>
#Html.RadioButtonFor(model => model.Role.Value, "01", new { id = "role-admin" })
Read only
</li>
<li>
#Html.RadioButtonFor(model => model.Role.Value, "02", new { id = "role-member" })
Restricted
</li>
<li>
#Html.RadioButtonFor(model => model.Role.Value, "03", new { id = "role-read" })
Standard
</li>
<li>
#Html.RadioButtonFor(model => model.Role.Value, "04", new { id = "role-subscriber" })
Administrator
</li>
</ul>
</div>
<button type="submit">OK</button>
}

I don't think the ModelBinder will bind child objects. You could create a custom ModelBinder to to bind your Association class or just create a ViewModel class that flattens your current Model into a single class. So your AssociationViewModel model class might look like this:
public class AssociationViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string RoleName { get; set; }
}
public ActionResult AddUser(AssociationViewModel associationViewModel)
{
if (ModelState.IsValid)
{
var association = new Association
{
User.FirstName = associationViewModel.FirstName,
Role = new Role { Name = associationViewModel.RoleName }
};
IRepository<Association> associationRepository = new IRepository<Association>(db);
....
}
}

Related

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

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.

editor template return null value

I have created an editor template base on this article ASP.NET MVC: Annotated for Input by Dino Esposito
Everything works fine until i press the submit button. I find out that my POST function return model is NULL, its like the model is not bind to the view. I have been trying all trick that I know and I found from the internet but I still can't fix it.
This is my controller
// GET: /Asset/New
public ActionResult New()
{
ViewBag.typeID = new SelectList(db.Ref_Asset_Types, "ID", "name");
return View(new AssetViewModel());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult New(AssetViewModel vm)
// vm.asset should contain new value but currently return null
{
if (ModelState.IsValid)
{
db.Assets.Add(vm.asset);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.typeID = new SelectList(db.Ref_Asset_Types, "ID", "name", vm.asset.typeID);
return View("New", vm);
}
this is my view
#using (Html.BeginForm("New","Asset","POST")) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.EditorFor(m=>m.asset, "InputTemplate" )
// note : the code works if i don't use my own template ==> #Html.EditorFor(m=>m.asset)
<div class="form-actions btn pull-right">
#Html.ActionLink("Back to List", "Index", null, new { #class = "btn btn-sm"})
<button type="reset" class="btn btn-sm" value="Index">
Reset
</button>
<button type="submit" class="btn btn-sm btn-success">
<i class="glyphicon glyphicon-plus"></i>
Tambah
</button>
</div>
}
and this is my InputTemplate
#inherits System.Web.Mvc.WebViewPage
#if (Model == null)
{
<span>#ViewData.ModelMetadata.NullDisplayText</span>
}
else
{
foreach (var prop in ViewData
.ModelMetadata
.Properties
.Where(pm => pm.ShowForDisplay && !ViewData.TemplateInfo.Visited(pm)))
{
if (prop.DisplayName != null) { // only display prop not of ComplexType
// note : using bootstrap for css styling
<div class="form-group col-xs-6">
<label class="col-xs-4 control-label text-right">
<span style="color:red"> #(prop.IsRequired ? "*" : "") </span>
<span>#prop.GetDisplayName()</span>
</label>
<div class="col-xs-8">
#if(prop.IsReadOnly)
{
<span class="readonly-field">#Html.Display(prop.PropertyName)</span>
}
else if (prop.TemplateHint == "DropDown")
{
<span>#Html.DropDownList(prop.PropertyName,(IEnumerable<SelectListItem>) ViewData[prop.PropertyName], new { #class = "form-control" })</span>
<span>#Html.ValidationMessage(prop.PropertyName)</span>
}
else
{
<div class="editor-field">
<span>#Html.Editor(prop.PropertyName, new { #class = "text-box single-line form-control" })</span>
<span>#Html.ValidationMessage(prop.PropertyName, new { #class = "label-danger" } )</span>
</div>
}
</div>
</div>
} // if
} // foreach
}
This is my viewmodel
using System;
using SIGMA.Models;
namespace SIGMA.ViewModels
{
public class AssetViewModel
{
public AssetViewModel()
{
asset = new Asset();
}
public Asset asset { get; set; }
}
}
This is my model
public class Asset
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
[HiddenInput(DisplayValue = false)]
public int ID { get; set; }
[DisplayName("No. Siri")]
[StringLength(45)]
public string serial_num { get; set; }
[DisplayName("Model")]
[Required(ErrorMessage = "Model perlu diisi!")]
[StringLength(45)]
public string model { get; set; }
[DisplayName("Harga Seunit")]
[RegularExpression(#"^\d{0,6}(\.\d{2})?$", ErrorMessage = "Sila gunakan format harga yang betul.")]
public float? unit_cost { get; set; }
[UIHint("DropDown")]
[DisplayName("Jenis Aset")]
[Required(ErrorMessage = "Jenis aset perlu dipilih!")]
[DisplayFormat(NullDisplayText = "Belum didaftar")]
public int? typeID { get; set; }
public virtual Ref_Asset_Type type { get; set; }
}
Sorry guys for the trouble.. i think i solve it.
My biggest mistake is using reserved word 'model' and 'type' as my property name. This some how cause problem to asp.net in interpreting my model using the user define editor template.
Once I change my property name - model to model_name and type to asset_type, i can see the my entry in my return model already.
Thanks to all
.... spends the whole day and night for this silly mistake but the lesson learn is worth it

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.

[Bind(Include = does not work with collection

I have a controller that looks like below:
public async Task<ActionResult> CreateProduct([Bind(Include = "Id,MyCollection")] MyClass myClass)
and this is the view:
<div class="form-group">
#Html.LabelFor(model => model.Id, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Id)
#Html.ValidationMessageFor(model => model.Id)
</div>
</div>
<table>
<tr>
<th>#Html.LabelFor(model => model.MyCollection.First().A)</th>
<th>#Html.LabelFor(model => model.MyCollection.First().B)</th>
<th>#Html.LabelFor(model => model.MyCollection.First().C)</th>
</tr>
#foreach (var item in this.Model.Warnings)
{
<tr>
<td>#Html.ValueFor(model => item.A)</td>
<td>#Html.EditorFor(model => item.B)</td>
<td>#Html.EditorFor(model => item.C)</td>
</tr>
}
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
when I click Save, it posts to the action but only Id is assigned to the object, not myCollection.
What do I need to do to include the collection when posting them to controller?
Update
Model is generated by Entity Framework
public abstract partial class MyBaseClass
{
public Module()
{
this.MyCollection= new HashSet<Warning>();
}
public int Id { get; set; }
public virtual ICollection<Warning> MyCollection { get; set; }
}
public partial class MyClass : MyBaseClass
{
// more properties that aren't used on this controller
}
In order to get this to work you need to understand how ASP.NET MVC handles model binding for a List. Scott Hansleman has a good post explaining this.
Since your question was rather vague in terms of what the actual properties you're dealing with are, I put together a little example which successfully binds to a list:
Controller
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
//Initial test data
var zoo = new Zoo()
{
Id = 1,
Name = "Vilas Zoo",
Animals = new List<Animal>()
{
new Animal() {
Id = 1,
Name = "Red Panda"
},
new Animal() {
Id = 2,
Name = "Sloth"
},
new Animal() {
Id = 3,
Name = "Badger"
},
}
};
return View(zoo);
}
[HttpPost]
public JsonResult Index(Zoo zoo)
{
return Json(zoo);
}
}
Model
public class Zoo
{
public int Id { get; set; }
public string Name { get; set; }
public List<Animal> Animals { get; set; }
}
public class Animal
{
public int Id { get; set; }
public string Name { get; set; }
}
View
<h1>#Model.Id - #Model.Name</h1>
#using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
for (var i=0; i < Model.Animals.Count; i++)
{
#Html.EditorFor(m => Model.Animals[i])
}
<button type="submit">Save it, yo</button>
}
Notice how I'm using a for loop instead of a foreach and the actual index of the loop is being used in the call to EditorFor.

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>

Resources