Cannot combine two models into a ViewModel - c#

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

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

Returning data to a view which has a model which references other models

So I have my model
public class AgencyAll
{
public Agency Agency { get; set; }
public AgencySector AgencySector { get; set; }
public AgencyExpertise AgencyExpertise { get; set; }
}
which acts a reference to other models so I can pass these into my view
Example - Agency model
public partial class Agency
{
public int id { get; set; }
public System.DateTime created { get; set; }
public int createdby { get; set; }
public string createdbytype { get; set; }
public System.DateTime lastupdated { get; set; }
public int lastupdatedby { get; set; }
public string lastupdatedbytype { get; set; }
public bool deleted { get; set; }
public string name { get; set; }
public string address { get; set; }
}
The AgencySector and AgencyExpertise are only contain the agency id and the other id (sector or expertise) as it's a many to many relationship
Part of my view
#model AgencyAll
<div class="col-lg-4 col-md-4 col-sm-4 col-xs-12">
<div class="form-group">
Sector:
#Html.DropDownListFor(model => model.AgencySector.sectorid, (SelectList) ViewBag.SectorList, new {#class = "form-control"})
</div>
</div>
<div class="col-lg-4 col-md-4 col-sm-4 col-xs-12">
<div class="form-group">
Specialisation:
#Html.DropDownListFor(model => model.AgencyExpertise.expertiseid, (SelectList) ViewBag.SpecialismList, new {#class = "form-control"})
</div>
</div>
As you can see, I can call the different models fine
My problem occurs here
public ActionResult ViewData(int id)
{
ViewBag.CountyList = new SelectList(GetCountyList(), "Value", "Text");
ViewBag.SectorList = new SelectList(GetSectorList(), "Value", "Text");
ViewBag.SpecialismList = new SelectList(GetSpecialisationList(), "Value", "Text");
return View(_db.Agencies.FirstOrDefault(x => x.id == id));
}
specifically, this line; return View(_db.Agencies.FirstOrDefault(x => x.id == id));
I am trying to return the agency data for the url ViewData/(id) however as the model for the view is AgencyAll, it cannot assign the dataset to the model as the model does not refer to a table, it refers to multiple models which refer to tables. The return statement is expecting the view to have the Agency model, not AgencyAll.
I cannot figure out what I need to replace return View(_db.Agencies.FirstOrDefault(x => x.id == id)); with in order to pass the data from the class to the model which has the model of the table, to show the data,
Any help would be much appreciated.
You need to be providing the expected model to your view, which is AgencyAll. At the moment you're providing an Agency object.
Change your code to something like this:
public ActionResult ViewData(int id)
{
ViewBag.CountyList = new SelectList(GetCountyList(), "Value", "Text");
ViewBag.SectorList = new SelectList(GetSectorList(), "Value", "Text");
ViewBag.SpecialismList = new SelectList(GetSpecialisationList(), "Value", "Text");
var viewModel = new AgencyAll {
Agency = _db.Agencies.FirstOrDefault(x => x.id == id),
AgencySector = _db.AgencySectors.FirstOrDefault(),
AgencyExpertise = _db.AgencyExpertises.FirstOrDefatul()
}
return View(viewModel);
}

Writing a list to database

For a school assignment I need to make a poll with ASP.NET
The problem I get when trying to write the answers in the database is that only one question and one answer gets written into it.
This is the View
#model CinemaJamV2.WebUIV2.Models.EnqueteModel
#{
ViewBag.Title = "Enquete";
}
<h2>Enquete</h2>
#Html.ValidationSummary(true)
#using (Html.BeginForm("Enquete", "Enquete", new { vraag = "vraag", antwoord = "antwoord", naam = "naam", cijfer = "cijfer" }))
{
<div class="col-md-12">
#for(var i=0;i< Model.enquetevragen.Count();i++)
{
<div class="thumbnail">
#Html.LabelFor(model => model.enquetevragen[i].vraag, new { htmlAttributes = new { #class = "form-control" } })
#Html.EditorFor(model => model.enquete.antwoord, new { htmlAttributes = new { #class = "form-control" } })
#Html.EditorFor(model => model.enquete.cijfer, new { htmlAttributes = new { #class = "form-control" } })
</div>
}
</div>
<div class="col-md-12">
<p>Naam <input type="text" name="naam" /> </p>
<input type="submit" name="submit" value="Verzend" />
</div>
}
This is the Controller:
namespace CinemaJamV2.WebUIV2.Controllers
{
public class EnqueteController : Controller
{
private IRepository<Enquete> repository;
private IRepository<EnqueteVraag> a_repository;
private CineJamContext db = new CineJamContext();
public EnqueteController(IRepository<Enquete> a_model, IRepository<EnqueteVraag> vraag_model)
{
repository = a_model;
a_repository = vraag_model;
}
[HttpGet]
public ActionResult Enquete()
{
EnqueteModel enquetevragen = new EnqueteModel
{
enquetevragen = a_repository.List
};
return View(enquetevragen);
}
[HttpPost]
public ActionResult Enquete(Enquete enquete)
{
if (ModelState.IsValid)
{
db.Enquetes.Add(enquete);
db.SaveChanges();
return RedirectToAction("Enquete");
}
return View(enquete);
}
}
}
The ModelView:
namespace CinemaJamV2.WebUIV2.Models
{
public class EnqueteModel
{
public List<Enquete> enquetes {get; set;}
public Enquete enquete { get; set; }
public List<EnqueteVraag> enquetevragen { get; set; }
}
}
And this is the Model Enquete which should contain all the given answers:
namespace CinemaJamV2.Domain.Entities
{
[Table("Enquete")]
public partial class Enquete : IEntity
{
public int Id { get; set; }
[StringLength(1000)]
public string vraag { get; set; }
[StringLength(1000)]
//[Required]
public string antwoord { get; set; }
public int? cijfer {get; set;}
[StringLength(50)]
//[Required]
public string naam { get; set; }
}
}
This Model contains all the Questions
namespace CinemaJamV2.Domain.Entities
{
[Table("EnqueteVraag")]
public partial class EnqueteVraag : IEntity
{
public int Id { get; set; }
[StringLength(1000)]
public string vraag { get; set; }
}
}
The action for POST has only one instance of the Model as its parameter. You need to read this: Model binding to a list
YOu need to use view model that will have list of Enquete and then in post method again you need to do for loop and save it to database.
See following links for samples.
http://www.binaryintellect.net/articles/b1e0b153-47f4-4b29-8583-958aa22d9284.aspx
http://www.c-sharpcorner.com/UploadFile/pmfawas/Asp-Net-mvc-how-to-post-a-collection/
http://www.codeproject.com/Tips/855577/List-of-Model-Object-Post-to-Controller-in-ASP-NET

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

Get string from DropDownList

I have XML file which contains my data and I want from dropdownlist save choose string to this xml.
In my view I have this:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>MatchXML</legend>
...
<div class="editor-label">
#Html.LabelFor(model => model.Team)
</div>
<div class="editor-field">
#Html.DropDownList("Team", (SelectList)ViewBag.Team, String.Empty)
#Html.ValidationMessageFor(model => model.Team)
</div>
...
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
In controller:
public ActionResult Pridat()
{
ViewBag.Team = new SelectList(repo.GetTeams(), "Name", "Name");
return View();
}
[HttpPost]
public ActionResult Pridat(MatchXML match, string Team)
{
if (ModelState.IsValid)
{
try
{
ViewBag.Team = new SelectList(repo.GetTeams(), "Name", "Name");
match.Team = repo.GetTeamByName(Team);
repo.AddMatch(match);
return RedirectToAction("Index");
}
catch (Exception ex)
{
//error msg for failed insert in XML file
ModelState.AddModelError("", "Error creating record. " + ex.Message);
}
}
return View(match);
}
Models looks:
public class MatchXML
{
public int MatchXMLID { get; set; }
public string Opponent { get; set; }
public DateTime MatchDate { get; set; }
public string Result { get; set; }
public Team Team { get; set; }
public int Round { get; set; }
}
public class Team
{
public int TeamID { get; set; }
public string Name { get; set; }
public virtual User Coach { get; set; }
public virtual ICollection<Player> Players { get; set; }
}
I was trying do some modification to do this but it is not working. I can do it with TeamID and saving ID but I want in xml save string (name of team). Thanks for help
Edit:
I updated show code of controller and view method.
You are binding the dropdown to the Team complex property (first argument of the DropDownList helper). This doesn't make sense. You can bind only to scalar values. I would also recommend you using the strongly typed version of the helper:
#Html.DropDownListFor(x => x.Team.TeamID, (SelectList)ViewBag.Team, String.Empty)
This way you will populate the TeamID property in the POST action with the selected value from the dropdown.
Also replace:
#Html.ValidationMessageFor(model => model.Team)
with:
#Html.ValidationMessageFor(model => model.Team.TeamID)

Categories

Resources