MVC5 : View not submitting correct ViewModel - c#

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);
}

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; }
}

Submitting nested class within a model to a controller

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.

FormMethod.Post returning 0

I have a problem with a FormMethod Post, I'm trying to post one single value (id) and store it in a Session variable, but the value return 0.
This is my code.
#foreach (var item in Model)
{
using (#Html.BeginForm("Index", "ProductSelec", FormMethod.Post))
{
#Html.HiddenFor(modelItem => item.id, new { value = "#Html.DisplayFor(modelItem => item.id)" })
<div class="AppOpt">
<button type="submit" name="submit" style="background-image: url('../Content/ICONS/SystemApp/#Html.DisplayFor(modelItem => item.img)');border-radius: 20px;background-size: cover;background-repeat: no-repeat;" class="AppImg">
<div class="OptNameRec">
<div class="OptIcon">
<img src='~/Content/ICONS/#Html.DisplayFor(modelItem => item.icon)'>
</div>
<div>
<p>#Html.DisplayFor(modelItem => item.nombre)</p>
</div>
<div class="clear"></div>
</div>
<div class="OptImage"></div>
</button>
</div>
}
}
The form is inside the foreach, becuase I'm creating the elements dinamically from a DB.
I want to store the item.id clicked.
This is my Controller
public ActionResult Index()
{
return View(db.aplicaciones.ToList());
}
public ActionResult ProductFamily()
{
return View();
}
[HttpPost]
public int Index(aplicaciones aplicaciones)
{
Session["appID"] = aplicaciones.id;
return aplicaciones.id;
}
and this is my Model.
public partial class aplicaciones
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public aplicaciones()
{
this.appNfam = new HashSet<appNfam>();
}
public int id { get; set; }
public string nombre { get; set; }
public string icon { get; set; }
public string img { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<appNfam> appNfam { get; set; }
}
I was trying to create another Model, but when I added, the foreach didn't read the values from the database.
I hope you can help me.
Change your controller method to:
[HttpPost]
public int Index(int id)
{
Session["appID"] = id;
return id;
}
Change your Html.BeginForm to be:
#using (Html.BeginForm("Index", "ProductSelec", new { id = item.id },FormMethod.Post, new { })
You should also be able to remove the hidden field since the ID will be posted by itself from your form action.

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.
}
}

using strongly typed partial views inside another Strongly Typed view

I have NameModel and RegisterModel and SuperClass classes as below: -
Case 1: - Using SuperClass
public class SuperClass
{
public RegisterModel Register{ get; set; }
public NameModel NameInfo { get; set; }
}
public class NameModel
{
[Required]
public string FirstName { get; set; }
public string MiddleName { get; set; }
[Required]
public string LastName { get; set; }
}
public class RegisterModel
{
public NameModel NameInfo{ get; set; }
[Required]
public string UserName { get; set; }
[Required]
public string Password { get; set;}
}
MyNamePartial strongly typed View is as follows :-
#model MyNamespace.Models.NameModel
#{
Layout = null;
}
{
#Html.TextBoxFor(m=>m.FirstName,new { #id="firstName"} )
#Html.TextBoxFor(m=>m.MiddleName,new { #id="middleName"} )
#Html.TextBoxFor(m=>m.LastName,new { #id="lastName"} )
}
My Registration View is strongly typed of Register Model as follows :-
#model MyNamespace.Models.SuperClass
#{
Layout = "~/Views/_Layout.cshtml";
}
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "myForm" }))
{
<div id="form">
#Html.Partial("NameModel",Model.NameInfo)
#Html.TextBoxFor(m=>m.Register.UserName,new { #id="userName"})
#Html.TextBoxFor(m=>m.Register.Password,new { #id="password"})
<input type="submit" value="Register" id="btnRegister" />
</div>
}
Above approach gives object reference error.
Case 2: - Using HTML.Action and no SuperClass
Tried using #Html.Action("MyNamePartialView")instead of #Html.Partial("NameModel",Model.NameInfo),Then I use Controller Action method as below
My Registration View is strongly typed of Register Model as follows :-
#model MyNamespace.Models.RegisterModel
#{
Layout = "~/Views/_Layout.cshtml";
}
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "myForm" }))
{
<div id="form">
#Html.Action("MyNamePartialView")
#Html.TextBoxFor(m=>m.UserName,new { #id="userName"})
#Html.TextBoxFor(m=>m.Password,new { #id="password"})
<input type="submit" value="Register" id="btnRegister" />
</div>
}
Register Controller :-
public ActionResult MyNamePartialView()
{
return PartialView("MyNamePartial", new NameModel());
}
[HttpPost]
[AllowAnonymous]
public ActionResult Register(RegisterrModel model)
{
#ViewBag.sideMenuHeader = "Create an Account";
if (ModelState.IsValid)
{
//Perform Something
return View();
}
return View();
}
The above case doesnt bind the values entered on form. it sets null for NameModel.
I don't want to use EditorFor as i have to supply html and custom attributes to helpers.The binding for the partial view fails.it gives me object reference error in Registration view. How can i use such strongly typed Partial views with such a such Model class hierarchy as explained above ?
The simplest way to do this is to use a child action
#model MyNamespace.Models.Register.SuperModel
#{
Layout = "~/Views/_Layout.cshtml";
}
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "myForm" }))
{
<div id="form">
#Html.Action("MyNamePartialView")
</div>
#Html.TextBoxFor(m=>m.Register.UserName,new { #id="userName"})
#Html.TextBoxFor(m=>m.Register.Password,new { #id="password"})
<input type="submit" value="Register" id="btnRegister" />
}
make your action post accept 2 classes
public ActionResult Register(RegisterModel RegisterInfo, NameModel NameInfo)

Categories

Resources