Submitting nested class within a model to a controller - c#

I am trying to submit a form with a model that has a nested class within it. However when I get the data to the controller, the nested class has null fields.
Model:
public class Person
{
public string Name { get; set; }
public ExtraStuff Stuff { get; set; } = new ExtraStuff();
}
public class ExtraStuff
{
public string Field1 { get; set; }
public string Field2 { get; set; }
}
View:
#model ProjectName.Models.Person
#Html.TextBoxFor(model => model.Name)
#Html.TextBoxFor(model => model.Stuff.Field1)
#Html.TextBoxFor(model => model.Stuff.Field2)
Controller:
public ActionResult ActionName (Person data)
{
data.Name //this is fine
data.Stuff.Field1 //comes in empty
data.Stuff.Field2 //comes in empty
}

I'm wondering if you're doing some custom form submission, because there doesn't seem to be anything wrong with the code provided. If you make a form like the following it should work:
#using (Html.BeginForm("ActionName", "MyController"))
{
#Html.TextBoxFor(model => model.TopValue)
#Html.TextBoxFor(model => model.Nested.SomeValue)
<input type="submit" value="Submit"/>
}
Take a look at this fiddle for example.

Related

View Model and Post action to Nested Classes in ASP.NET Framework

I have been working on a mock website for a car dealership using MVC in .NET Framework. I am using razor pages to make a simple page to display existing models, and add a new model of car to the database, but no matter what syntax I use I can not seem to get my post data to properly map to the Model class when it is returned to my controller. Here is my VM:
public class ModelVM
{
public List<Make> Makes { get; set; }
public List<Model> Models { get; set; }
public Model Model { get; set; }
}
The list of makes is to populate a dropdown for adding. The list of models is used to populate a table of all Models. Here are the classes for each.
public class Make
{
public int MakeId { get; set; }
public string MakeName { get; set; }
public string AddedByEmail { get; set; }
[DataType(DataType.Date)]
public DateTime DateAdded { get; set; }
}
public class Model
{
public int ModelId { get; set; }
public Make Make { get; set; }
public string ModelName { get; set; }
public string AddedByEmail { get; set; }
public DateTime DateAdded { get; set; }
}
Controller:
[Route("admin/Models")]
public ActionResult Models()
{
var vm = new ModelVM();
var _morepo = RepoFactory.GetModelRepo();
var _marepo = RepoFactory.GetMakeRepo();
vm.Makes = _marepo.GetMakes();
vm.Models = _morepo.GetModels();
vm.Model = new Model();
vm.Model.Make = new Make();
return View(vm);
}
public ActionResult AddModels(Model model)
{
var mrepo = RepoFactory.GetModelRepo();
mrepo.AddModel(model);
return RedirectToAction("Models", "Admin");
}
Here is my page view. Everything works on it, except for the post function.
#model GuildCars.UI.Models.ModelVM
#{
ViewBag.Title = "Models";
}
<h2>Models</h2>
#using (Html.BeginForm("AddModels", "Admin", FormMethod.Post, new { #class = "row form-control" }))
{
<label class="form-label col-1" for="SpecialName">New Model:</label>
<input class="col-3" id="ModelName" name="ModelName" type="text" required />
<label class="form-label col-1" for="MakeId">Make:</label>
<select id="#Model.Model.Make.MakeId" name="#Model.Model.Make.MakeId" class="col-4">
#foreach (var m in Model.Makes)
{
<option value="#m.MakeId">#m.MakeName</option>
}
</select>
<button class="btn btn-primary col-1" type="submit">Save</button>
}
<div class="col-4">
<table class="table">
<tr>
<th>Make</th>
<th>Date Added</th>
<th>User</th>
</tr>
#foreach (var m in Model.Models)
{
<tr>
<td>#m.Make.MakeName</td>
<td>#m.ModelName</td>
<td>#m.DateAdded.ToString("MM/dd/yyyy")</td>
<td>#m.AddedByEmail</td>
</tr>
}
</table>
</div>
I have gone though 100 iterations of changing the name and Id, but none of them work. The MakeName is returned since it is a simple type, but the Make class within the model returns null. My goal is to get the Model being returned to the post controller to be populated with the Make selected in the dropdown. I have been programming for less than one year, so I am unsure if this is just a simple syntax error, or if there is a bigger concept that I am missing somewhere.
you have to fix an action input parameter, since your view uses ModelVm as a model, post action should use the same
public ActionResult AddModels(ModelVM model)
if you want to submit data you have to fix the view to use asp-for for the inplut controls
#{
var items=Model.Makes.Select( m=> new SelectListItem (m.MakeId.ToString(), m.MakeName) ).Tolist();
}
....
<input class="col-3" asp-for ="#Model.Model.ModelName" type="text" required />
<label class="form-label col-1" for="MakeId">Make:</label>
<select asp-for="#Model.Model.Make.MakeId" asp-items="#items" class="col-4">
</select>
to use this helper your Views Folder should contain _ViewImports.cshtml file with this code
#using TestCore.WebApp
#using TestCore.WebApp.Models
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
but if you use old mvc you can replace view with this
#Html.TextBoxFor(model => model.Model.ModelName, new { #class = "form-control", #required = "required"} )
#Html.LabelFor(m => m.Model.Make.MakeId)
#Html.DropDownListFor(model => model.Model.Make.MakeId, #items, "select", new { #class = "form-control" })
and IMHO you have to add MakeId to your model class, then your view model will be easier to understand
public class Model
{
public int ModelId { get; set; }
public string ModelName { get; set; }
public string AddedByEmail { get; set; }
public DateTime DateAdded { get; set; }
public int MakeId { get; set; }
public virtual Make Make { get; set; }
}

MVC5 : View not submitting correct ViewModel

Minimal Code Structure
I have two ViewModels cmsViewModel and appointments
public partial class cmsViewModel
{
public string rows { get; set; }
public DateTime appt_date_time { get; set; }
public int appt_id { get; set; }
public List<community_meeting_schedule> lst { get; set; }
public cmsViewModel()
{
rows = null;
appt_date_time = DateTime.Today;
lst = null;
appt_id = -1;
}
}
public partial class appointments
{
public int appt_client_id { get; set; }
public int customer_id { get; set; }
[Key]
public int appt_id { get; set; }
public DateTime appt_date_time { get; set; }
public DateTime time_stamp { get; set; }
}
The action method in controller looks like:
[HttpPost]
public ActionResult CMSConfim(cmsViewModel model, string Command)
{
return View("CMSchedule", model);
}
The View CMSConfim.cshtml looks like below:
#model Scheduler_MVC.Models.cmsViewModel
#using (Html.BeginForm("CMSConfim", "appointments", FormMethod.Post))
{
#Html.HiddenFor(model => model.appt_id, new { id = "appt_id" })
#Html.HiddenFor(model => model.appt_client_id, new { id = "appt_client_id" })
#Html.HiddenFor(model => model.appt_date_time)
#Html.HiddenFor(model => model.appt_status)
#Html.HiddenFor(model => model.appt_type)
#Html.HiddenFor(model => model.lst)
<input type="submit" value="Back" id="backBtn" class="btn btn-default" />
<input type="button" value="Submit" id="submitBtn" class="btn btn-default" style="display:inline-block;" />
}
I would like to add that I am able to render correct values in the display fields on the form. The error comes while submitting the form.
Error
Now when I submit the form through Back. I get the following error.
The model in the dictionary is of type 'cmsViewModel' but required is that of type 'appointments'
Please suggest what I might be doing wrong.
Your post view model type is "appointments", but your Html.BeginForm is also routing to an "AppointmentsController"
#using (Html.BeginForm("CMSConfim", "appointments", FormMethod.Post))
This route is expecting the following
public class AppointmentsController : Controller
{
public Action CMSConfirm(appointments model)
{
}
}
Update your Html.BeginForm if the controller name is wrong
I don't see a match for parameter "Command" either.
Why do you have in your razor sintax cmsViewModel? You expect to submit appointmets but there is cmsVM. Also in your controller you are expecting cmsVM. You need to provide appointmentsVM in razor and also to expect it in your Controller.
[HttpPost]
public ActionResult CMSConfim(appointments model, string Command)
{
return View("CMSchedule", model);
}
Or if you want to get both in controller.
[HttpPost]
public ActionResult CMSConfim(cmsViewModel model, appointments appoint, string Command)
{
return View("CMSchedule", model);
}

Cannot combine two models into a ViewModel

I have been trying to get two models to appear in a single view using ViewModels but am failing.
I have simple view with 3 fields right now (Comment, Name, Department) and and a model to match it. I have a controller that returns an empty view and when you submit the form, the empty model is filled in and passed back to the controller. I now want to turn the department field into a drop down and have deduced (maybe incorrectly?) that I should create another model with the static values in it and then pass the ViewModel to the view via the controller but when I attempt to do this, it fails:
View:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="fieldset">
<fieldset>
<legend>CommentDb</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Comment)
</div>
<div>
#Html.TextAreaFor(model => model.Comment, new {style = "width: 900px; height:200px;", autocomplete = "off" })
#Html.ValidationMessageFor(model => model.Comment)
</div>
<div class="editor-label">
#Html.Label("Your Name")
#*#Html.LabelFor(model => model.CommentByName)*#
#Html.TextBoxFor(model => model.CommentByName, new { autocomplete = "off", placeholder = "Optional" } )
#Html.ValidationMessageFor(model => model.CommentByName)
</div>
<div class="editor-label">
#Html.Label("Your Department", new { #class = "display-label" })
#*#Html.LabelFor(model => model.Department)*#
#Html.TextBoxFor(model => model.Department, new { autocomplete = "off", placeholder = "Optional" })
#Html.ValidationMessageFor(model => model.Department)
</div>
</fieldset>
<br />
<br />
</div>
<div>
<input type="submit" value="Create" id="submit"/>
</div>
}
Department drop down model:
namespace SuggestionBox.Models
{
public class DropDownModel
{
public String Departments { get; set; }
public String SetDropDownList()
{
Departments = "Engineering";
return Departments;
}
}
}
Database model:
namespace SuggestionBox.Models
{
public class CommentModel
{
[Key]
public int CommentiD { get; set; }
public string CommentByName { get; set; }
public string Department { get; set; }
[Required]
public string Comment { get; set; }
public DateTime InsertDate { get; set; }
}
}
namespace SuggestionBox.Models
{
public class CommentDbContext : DbContext
{
public CommentDbContext() : base()
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<CommentDbContext, SuggestionBox.Migrations.Configuration>());
}
public DbSet<CommentModel> Comments { get; set; }
}
}
My attempted ViewModel:
namespace SuggestionBox.ViewModels
{
public class SuggestionBoxViewModel
{
public CommentModel Comments { get; set; }
public DropDownModel Departments { get; set; }
public SuggestionBoxViewModel()
{
Departments = new DropDownModel();
Departments.SetDropDownList();
Comments = new CommentModel();
}
}
}
The Controller:
public ViewResult Index()
{
SuggestionBoxViewModel vm = new SuggestionBoxViewModel();
return View(vm);
}
In return View(vm);, the IDE says: Arguement type 'SuggestionBox.ViewModels.SuggestionBoxViewModel' is not assignable to the model type 'SuggestionBox.Models.CommentModel'
Any idea what I am doing wrong here?
Cheers.
You are making a simple task into something overly complex. Your view model should contain only the properties used in the view, and should not contain methods. Populating your view model properties is the responsibility of the controller.
View model
public class CommentModelVM
{
[Required]
public string Comment { get; set; }
public string CommentByName { get; set; }
[Display(Name="Your Department")] // add attributes associated with the view
public string Department { get; set; }
public SelectList DepartmentList { get; set } // to populate the dropdown options
}
Controller
public ActionResult Create()
{
CommentModelVM model = new CommentModelVM();
ConfigureViewModel(model);
return View(model);
}
public ActionResult Create(CommentModelVM model)
{
if (!ModelState.IsValid())
{
// Repopulate options and return view
ConfigureViewModel(model);
return View(model);
}
// Save and redirect
}
private void ConfigureViewModel(CommentModelVM model)
{
List<string> departments = // create your list of departments here (from database or static list)
model.DepartmentList = new SelectList(departments);
}
View
....
#Html.LabelFor(m => m.Department)
#Html.DropDownListFor(m => m.Department, Model.DepartmentList)
....
I just want to start by saying that generally it is a bad idea to name properties or classes for our ViewModels out the view controls that they are bound to. For example: DropDownModel. Doing this creates confusion since Models and ViewModels don't represent a UI component they represent entities and data that the view uses to render its UI controls.
With that being said to answer you question I don't see the need for a ViewModel class to represent the static list of departments for your dropdown. I think you should just add a new list of departments to your SuggestionBoxViewModel class like this:
namespace SuggestionBox.ViewModels
{
public class SuggestionBoxViewModel
{
public CommentModel Comments { get; set; }
public IEnumerable<string> Departments { get; set; }
public string SelectedDepartment { get; set; }
public SuggestionBoxViewModel()
{
Departments = new [] {"Engineering","Sales"};
Comments = new CommentModel();
}
public int CommentiD
{
get { return Comments.CommentiD; }
}
public string CommentByName
{
get { return Comments.CommentByName; }
}
}
}
Then in your view all you have to do is bind the dropdown to the list of departments. Like this:
At the top of your view:
#model SuggestionBox.ViewModels.SuggestionBoxViewModel
Then where you want the dropdown to display:
#Html.DropDownListFor(m => m.SelectedDepartment, new SelectList(Model.Departments))
And that's it! I hope this helps.
Your View seems to expect a Comment Model.
If you wanted to bind to a ViewModel, then you would have to implement all the properties that Comment exposes.
So your ViewModel might look like this:
namespace SuggestionBox.ViewModels
{
public class SuggestionBoxViewModel
{
public CommentModel Comments { get; set; }
public DropDownModel Departments { get; set; }
public SuggestionBoxViewModel()
{
Departments = new DropDownModel();
Departments.SetDropDownList();
Comments = new CommentModel();
}
public int CommentiD
{
get { return Comments.CommentiD; }
}
public string CommentByName
{
get { return Comments.CommentByName; }
}
...etc.
}
}

not able display details in grid using MVC

where I have got one textbox and button and when the user entered a value and the press submit button , that values i need to show in Kendo UI Grid in same view ..
for this purpose i have done like this .....
this is my model..
namespace KendoSampleMVCApp.Models
{
public class TextBoxGrid
{
public string EnteredValue { get; set; }
public List<EmployeeDetails> employees;
}
public class EmployeeDetails
{
public string EmployeeId { get; set; }
public string ManagerId { get; set; }
}
}
and this is my controller
namespace KendoSampleMVCApp.Controllers
{
public class EnterValuesGridController : Controller
{
public ActionResult Index( TextBoxGrid model)
{
return View(GetEmployee());
}
[HttpPost]
public ActionResult PostValues(TextBoxGrid model)
{
TempData["enteringValue"] = model.EnteredValue;
return View(model);
}
public IEnumerable<EmployeeDetails> GetEmployee()
{
string enteredValueId =(string) TempData["enteringValue"];
string managerId = "M" +enteredValueId;
List<EmployeeDetails> empdtls = new List<EmployeeDetails>();
EmployeeDetails em1 = new EmployeeDetails();
em1.EmployeeId = enteredValueId;
em1.ManagerId = managerId;
empdtls.Add(em1);
return empdtls;
}
public ActionResult Orders_Read([DataSourceRequest]DataSourceRequest request)
{
return Json(GetOrders().ToDataSourceResult(request));
}
private IEnumerable<EmployeeDetails> GetOrders()
{
return GetEmployee();
}
}
}
and this is my view
#model KendoSampleMVCApp.Models.TextBoxGrid
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm("PostValues","EnterValuesGrid",FormMethod.Post))
{
<div>
<fieldset>
<legend>Enter Textbox Value</legend>
<div class="editor-field">
#Html.TextBoxFor(m=>m.EnteredValue)
</div>
<p>
<input type="submit" name="Submitbutton1" value="Submit1" />
</p>
</fieldset>
</div>
}
#model IEnumerable<KendoSampleMVCApp.Models.EmployeeDetails> <----- error at this line
<h2>Index</h2>
#using (Html.BeginForm())
{
#(Html.Kendo().Grid<KendoSampleMVCApp.Models.EmployeeDetails>()
.Name("grid")
.Columns(columns => {
columns.Bound(p => p.EmployeeId).Filterable(false).Width(100);
columns.Bound(p => p.ManagerId).Filterable(false).Width(100);
})
.Pageable()
.Sortable()
.Scrollable()
.Filterable()
.HtmlAttributes(new { style = "height:430px;" })
.DataSource(dataSource => dataSource
.Ajax()
.PageSize(20)
.Read(read => read.Action("Orders_Read", "EnterValuesGrid"))
)
)
}
I am getting error at this line #model IEnumerable<KendoSampleMVCApp.Models.EmployeeDetails>
saying that
Parser Error Message: Only one 'model' statement is allowed in a file.
would any one pls suggest any ideas why i am getting this error ...can't i use two models in same view ....
many thanks ...
EDIT : can i do like this In my model...
public class ParentViewModel
{
public EmployeeDetails EmployeeDetails { get; set; }
public TextBoxGrid TextBoxGrid { get; set; }
}
The answer is no, you can't use two models in one view. And there is no reason why you should need to. The whole point of a view model is that is designed to store all of the data for the view to which it corresponds.
Create an EnterValuesViewModel class and instantiate it with all of the necessary data from both of your business entities. Then type your view to this class.
public class EnterValuesViewModel
{
public string EnteredValue { get; set; }
public List<EmployeeDetailsViewModel> Employees { get; set; }
}
public class EmployeeDetailsViewModel
{
public string EmployeeId { get; set; }
public string ManagerId { get; set; }
}
View:
#model EnterValuesViewModel
#Html.TextBoxFor(m=>m.EnteredValue)
#foreach(EmployeeDetailsViewModel emp in Model.Employees)
{
#* Stuff about each employee goes here *#
}
Alternatively, look at partial views.

MVC model binding keeps values NULL

I'm trying to get custom model binding to work, but for some reason the values aren't set. The code seems ligit when comparing it to working code, but still it doesnt bind. I guess it some trivial thing i'm missing.
Custom model:
//Cluster is from Entity Framework
//BaseViewModelAdmin defines:
public List<KeyValuePair<string, string>> MenuItems;
public IPrincipal CurrentUser = null;
public Foundation Foundation; //also from Entity Framework
public class AdminClusterCreateModel : BaseViewModelAdmin
{
public Cluster Item;
public AdminClusterCreateModel()
{
Item = new Cluster();
}
}
The view form looks like:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Cluster</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Item.Active)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item.Active)
#Html.ValidationMessageFor(model => model.Item.Active)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Item.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Item.Name)
#Html.ValidationMessageFor(model => model.Item.Name)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
And the controller:
[HttpPost]
public ActionResult Create(AdminClusterCreateModel model, FormCollection form)
{
if(ModelState.IsValid) //true
{
var test = form["Item.Name"]; //Value is correct from form (EG: Test)
UpdateModel(model); //no error
}
//At this point model.Item.Name = null <--- WHY?
return View(model);
}
Cluster on request
public partial class Cluster
{
public Cluster()
{
this.Team = new HashSet<Team>();
}
public long Id { get; set; }
public System.DateTime Created { get; set; }
public System.DateTime Modified { get; set; }
public bool Active { get; set; }
public long FoundationId { get; set; }
public string Name { get; set; }
public virtual Foundation Foundation { get; set; }
public virtual ICollection<Team> Team { get; set; }
}
DefaultModelBinder works explicitly on 'Properties', not on 'Fields'
Changing public Cluster Item to public Cluster Item {get; set;} in AdminClusterCreateModel should do the trick.
public class AdminClusterCreateModel : BaseViewModelAdmin
{
public Cluster Item {get; set;}
public AdminClusterCreateModel()
{
Item = new Cluster();
}
}
Regards
This is a trivial and a corner case, yet if it might help someone:
If your model has a property, named model this to will cause the DefaultModelBinder to return a null.
public class VehicleModel
{
public string Model { get; set; }
}

Categories

Resources