How do I pass a ViewModel to an Edit Action in ASP.NET MVC 5 - asp.net-mvc

I'm learning about using ViewModels to pass information from the view to the controller and vice versa. I have my create action, view, and viewmodel working perfectly but I'm having trouble with the edit one. I get the error:
The model item passed into the dictionary is of type 'CatVM.Models.Cat', but this dictionary requires a model item of type 'CatVM.Models.EditCatViewModel'.
Here is my code:
Controller Method
// GET: /Cats/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Cat cat = unitOfWork.CatRepository.GetByID(id);
if (cat == null)
{
return HttpNotFound();
}
return View(cat);
}
View
#model CatVM.Models.EditCatViewModel
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Cat</h4>
<hr />
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.ID)
<div class="form-group">
#Html.LabelFor(model => model.Name, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Color, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Color)
#Html.ValidationMessageFor(model => model.Color)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.FurLength, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FurLength)
#Html.ValidationMessageFor(model => model.FurLength)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Size, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Size)
#Html.ValidationMessageFor(model => model.Size)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
EditCatViewModel
public class EditCatViewModel
{
public int ID { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Required]
[StringLength(50)]
public string Color { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Fur Type")]
public string FurLength { get; set; }
[StringLength(50)]
public string Size { get; set; }
}
}

That's because the item you receive from CatRepository.GetByID(id); is of type Cat, not EditCatViewModel.
You can bypass this by constructing a new viewmodel from this object:
Cat cat = unitOfWork.CatRepository.GetByID(id);
var viewModel = new EditCatViewModel {
Name = cat.Name,
Color = cat.Color,
FurLength = cat.FurLength,
Size = cat.Size
};
return View(viewModel);
Alternatively you could construct implicit or explicit casting methods or use a mapping tool like AutoMapper.

Your return view should be of type CatVM.Models.EditCatViewModel now your returning a Cat
return View(cat);
transform your model in a view model and pass this object back to the view

Related

Passing date value from view to controller

In my model I have
[DataType(DataType.Date)]
public DateTime SaleDate { get; set; }
public virtual Customer Customers { get; set; }
in my controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include="SaleID,shippingBillNo,CustomerID,PlantID,Quantity,SaleDate")] Sale sale)
{
if (ModelState.IsValid)
{
var strddlValue = Convert.ToInt32(Request.Form["CustomerID"]);
var saledate = Convert.ToDateTime(Request.Form["mySaleDate"]);
In my View
#using (Html.BeginForm(null, null, FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Sale</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.CustomerID, "CustomerID", new {
#class = "control-label col-md-2" })
<div class="col-md-10">
#Html.Hidden("CustomerText")
#Html.DropDownList("CustomerID", String.Empty)
#Html.ValidationMessageFor(model => model.CustomerID)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.SaleDate, new { #class = "control-
label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.SaleDate, new { Name =
"mySaleDate"})
#Html.ValidationMessageFor(model => model.SaleDate)
</div>
</div>
and the JavaScript for the DateTimePicker is:
<script type="text/javascript">
$(function () {
$('#SaleDate').datetimepicker({
format: "DD/MM/YYYY"
}).on('dp.change', function (e) {
$(this).data('DateTimePicker').hide();
});
});
I succeed to pass the CustomerID to the controller. No Problem with that. Instead of passing the date I pick in the editofor with the DataTimePicker helper I get a meaningless value of '01/01/0001'. What am I doing wrong?

ASP NET MVC 5 SeletList DropDownList

I have some problems with SelectList and DropDownList.
Ich have the two Models (TimeRecord has a navigation-property to Project):
public class Project
{
public int ProjectId { get; set; }
[Required]
public string ProjectName { get; set; }
}
and
public class TimeRecord
{
public int TimeRecordId { get; set; }
public int ProjectId { get; set; }
public string Description { get; set; }
public Project TmRecProject { get; set; }
}
In my Controller in the Create-action method the SelectList is pass by ViewBag to the View (till now all is correct working)
public ActionResult Create()
{
ViewBag.ProjectId = new SelectList(db.Projects, "ProjectId", "ProjectName");
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(TimeRecord timeRecord)
{
if (ModelState.IsValid)
{
db.TimeRecords.Add(timeRecord);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(timeRecord);
}
Here is the View:
#model SelectListDropDownTest.Models.TimeRecord
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>TimeRecord</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.TmRecProject.ProjectId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("ProjectId", null, new { #class = "form-control" } )
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Description, "", 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>
}
In the Create-View I can select a Project from the DropDownList. My Problem is when I pass the Model "TimeRecord" back to the Controller the Project "TmRecProject" is always null.
What is the best way to solve this Problem?
First of all I am not able to understand how are you able to select value from dropdown list as you have not bind your 'ViewBag.ProjectId' with your drop down list.
Change in your view and your code will start working !!!!
Change:
Bind the drop down list with the model in View.
#Html.DropDownListFor(model => model.TmRecProject.ProjectId, ViewBag.ProjectId as IEnumerable<SelectListItem>, new { #class = "form-control" })
Others are as it is and you will get data on your post method.

Bind Checkboxlist to model

I'm working on an app for a cycling team. I have to add riders to a ride. To do this I want the user te select the date of a ride, select a value for the point of this ride an then have a list of the members of the team where the user selects which member participated on the ride.
It works fine until I post the form. when I debug my model in the controler the list of Riders in de model counts 4 riders (which is correct as I have 4 riders in my Db for testing) but the riders are null when I check the list.
Can anyone help me, I dont know what I'm doing wrong.
this are the viewmodels I use:
public class RiderViewModel
{
public string Id { get; set; }
public string FulllName { get; set; }
public bool IsChecked { get; set; }
}
public class RideViewModel
{
public string RideId { get; set; }
[Display(Name = "Datum")]
[DataType(DataType.Date)]
[Required(ErrorMessage = "Datum is verplicht")]
public DateTime Date { get; set; }
[Display(Name = "Aantal punten")]
[Required(ErrorMessage = "Een waarde voor het puntenaantal is verplicht")]
public int Punten { get; set; }
[Display(Name = "Leden")]
public List<RiderViewModel> Riders { get; set; }
}
this is my controler:
//POST Rides/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(RideViewModel model)
{
if (ModelState.IsValid)
{
var db = new ApplicationDbContext();
var riders = new List<ApplicationUser>();
foreach(RiderViewModel r in model.Riders)
{
var user = db.Users.FirstOrDefault(u => u.Id == r.Id);
if(user == null)
{
ModelState.AddModelError(string.Empty, "Er is een Fout opgetreden bij het selecteren van de leden. contacteer de systeembeheerder indien het probleem blijft bestaan");
return View(model);
}
riders.Add(user);
}
var ride = new Ride
{
Date = model.Date,
Punten = model.Punten,
Riders = riders
};
db.Rides.Add(ride);
db.SaveChanges();
return RedirectToAction("Index", "AdminPanel");
}
return View(model);
}
and this is the view:
using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Ride</h4>
<hr />
<div class="form-group">
#Html.LabelFor(model => model.Date, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Date, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Date, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Punten, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Punten, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Punten, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.Label("Leden", htmlAttributes: new { #class = "control-label col-md-2"})
<div class="col-md-10">
#for (var i = 0; i < Model.Riders.Count(); i++)
{
<div class="col-md-10">
#Html.HiddenFor(model => model.Riders[i])
#Html.CheckBoxFor(model => model.Riders[i].IsChecked)
#Html.LabelFor(model => model.Riders[i].IsChecked, Model.Riders[i].FulllName)
</div>
}
</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>
}
Thanx a lot.
grtz Jeff
You can use a EditorTemplate for this, just add a new file in the EditorTemplates folder named RiderViewModel :
#model RiderViewModel
<div class="col-md-10">
#Html.HiddenFor(model => model.Id)
#Html.CheckBoxFor(model => model.IsChecked)
#Html.LabelFor(model => model.IsChecked, Model.FulllName)
</div>
Then in the view just call the EditorFor:
#Html.EditorFor(m => m.Riders)
Your usage of
#Html.HiddenFor(model => model.Riders[i])
is creating a hidden input for a complex property which is typeof RiderViewModel. If you inspect the html, it will be something like
<input type="hidden" id="Riders_0_" name="Riders[0]" value="someAssembly.RiderViewModel" />
When you submit, the DefaultModelBinder tries to set model.Riders[0] to the value someAssembly.RiderViewModel which fails and so model.Riders[0] is null
Assuming you want to generate a hidden input for the ID property, change it to
#Html.HiddenFor(model => model.Riders[i].ID)

Database First EF6 add data to table with Custom ViewModel

I using MVC 5.1 and EF 6.1 database first model. I don't like the view that was created by the scaffolding as it uses all the database columns. On the create view I just want the user to input the required fields all other data can be added through editing the details view. I have created the a view model to use other than the .edmx generated one. The view works as expected but when the create action button is click the post operation dies with a "[NullReferenceException: Object reference not set to an instance of an object.]
Here is my view model
public class DesktopCreateViewModel
{
[Required]
[Display(Name = "Serial Number")]
[StringLength(30)]
public string SERIAL_NUMBER { get; set; }
public int Model_DesktopId { get; set; }
public int DeviceType_DesktopId { get; set; }
[Required]
[StringLength(50)]
public string MAC1 { get; set; }
[Display(Name = "Arrival Date")]
public DateTime ARRIVAL_DATE { get; set; }
[Timestamp]
[Display(Name = "Record Updated")]
public DateTime RECORD_UPDATED { get; set; }
public int LocationId { get; set; }
public int Location_CodeId { get; set; }
public int MemoryId { get; set; }
public int MonitorId { get; set; }
public int PlantId { get; set; }
public int DepartmentId { get; set; }
public int Operating_SystemId { get; set; }
[ForeignKey("DeparmentId")]
public virtual Department Department { get; set; }
[ForeignKey("DeviceType_DesktopId")]
public virtual DeviceType_Desktop DeviceType_Desktop { get; set; }
[ForeignKey("Location_CodeId")]
public virtual Location_Code Location_Code { get; set; }
[ForeignKey("LocationId")]
public virtual Location Location { get; set; }
[ForeignKey("MemoryId")]
public virtual Memory Memory { get; set; }
[ForeignKey("Model_DesktopId")]
public virtual Model_Desktop Model_Desktop { get; set; }
[ForeignKey("MonitorId")]
public virtual Monitor Monitor { get; set; }
[ForeignKey("Operating_SystemId")]
public virtual Operating_System Operating_System { get; set; }
[ForeignKey("PlantId")]
public virtual Plant Plant { get; set; }
public IEnumerable<DesktopCreateViewModel> Create_Desktop { get; set; }
public virtual ICollection<DesktopCreateViewModel> Desktops { get; set; }
Here is the portion of the controller that fails
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ICollection<DesktopCreateViewModel> desktop)
{
if (ModelState.IsValid)
{
foreach (var item in desktop)
{
db.Entry(desktop).State = EntityState.Added;
}
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.DepartmentId = new SelectList(db.Departments, "Id", "DEPARTMENT1", desktop.DepartmentId);
ViewBag.DeviceType_DesktopId = new SelectList(db.DeviceType_Desktop, "Id", "DEVICE_TYPE", desktop.DeviceType_DesktopId);
ViewBag.Location_CodeId = new SelectList(db.Location_Code, "Id", "LOCATION_CODE1", desktop.Location_CodeId);
ViewBag.LocationId = new SelectList(db.Locations, "Id", "LOCATION1", desktop.LocationId);
ViewBag.MemoryId = new SelectList(db.Memories, "Id", "MEMORY1", desktop.MemoryId);
ViewBag.Model_DesktopId = new SelectList(db.Model_Desktop, "Id", "MODEL", desktop.Model_DesktopId);
ViewBag.MonitorId = new SelectList(db.Monitors, "Id", "MONITOR1", desktop.MonitorId);
ViewBag.Operating_SystemId = new SelectList(db.Operating_System, "Id", "OS", desktop.Operating_SystemId);
ViewBag.PlantId = new SelectList(db.Plants, "Id", "PLANT1", desktop.PlantId);
return View(desktop);
}
This is the View
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Desktop</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.SERIAL_NUMBER, new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.EditorFor(model => model.SERIAL_NUMBER)
#Html.ValidationMessageFor(model => model.SERIAL_NUMBER)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Model_DesktopId, "Model", new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.DropDownList("Model_DesktopId", String.Empty)
#Html.ValidationMessageFor(model => model.Model_DesktopId)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.DeviceType_DesktopId, "Device Type", new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.DropDownList("DeviceType_DesktopId", String.Empty)
#Html.ValidationMessageFor(model => model.DeviceType_DesktopId)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.MAC1, new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.EditorFor(model => model.MAC1)
#Html.ValidationMessageFor(model => model.MAC1)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.ARRIVAL_DATE, new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.EditorFor(model => model.ARRIVAL_DATE)
#Html.ValidationMessageFor(model => model.ARRIVAL_DATE)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.RECORD_UPDATED, new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.EditorFor(model => model.RECORD_UPDATED)
#Html.ValidationMessageFor(model => model.RECORD_UPDATED)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.LocationId, "Location", new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.DropDownList("LocationId", String.Empty)
#Html.ValidationMessageFor(model => model.LocationId)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Location_CodeId, "Location Code", new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.DropDownList("Location_CodeId", String.Empty)
#Html.ValidationMessageFor(model => model.Location_CodeId)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.MemoryId, "Memory", new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.DropDownList("MemoryId", String.Empty)
#Html.ValidationMessageFor(model => model.MemoryId)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.MonitorId, "Monitor", new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.DropDownList("MonitorId", String.Empty)
#Html.ValidationMessageFor(model => model.MonitorId)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.PlantId, "Plant", new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.DropDownList("PlantId", String.Empty)
#Html.ValidationMessageFor(model => model.PlantId)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.DepartmentId, "Department", new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.DropDownList("DepartmentId", String.Empty)
#Html.ValidationMessageFor(model => model.DepartmentId)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Operating_SystemId, "Operating System", new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.DropDownList("Operating_SystemId", String.Empty)
#Html.ValidationMessageFor(model => model.Operating_SystemId)
</div>
</div>
<div class="form-group">
<div class="col-md-offset-8 col-md-4">
<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")
}
The action is expecting a collection of viewmodels (ICollection<DesktopCreateViewModel>), but the view only contains one. Try changing to:
public ActionResult Create(DesktopCreateViewModel desktop)

Store View form in existing database

Forgive the newbie ASP.NET MVC question. I am used to tutorials where Code First is used with the Entity Framework. Here, this is not the case. I have a form that I want the user to fill out. When it has been filled out, I want to use EF to write the values to an existing database. I can't figure out how to "trap" the values in the view so I can write my EF code. I used a model and I redirected the BeginForm to an "Edit" action method but I don't know how to get my filled in class. Here is the HomeController methods:
[HttpGet]
public ActionResult Trial()
{
UserAccount account = new UserAccount();
return View(account);
}
public ActionResult Edit()
{
}
Here is the model class:
public class UserAccount
{
public int AccountID { get; set; }
public string AccountName { get; set; }
public string RegistrationCode { get; set; }
public DateTime Created { get; set; }
}
}
Here is the View the wizard generated. When I hit the "Create" button, I want to go to the "Edit" action menu or someplace I can use EF to write to the existing database table. How do I do this?
#model AlphaFrontEndService.Models.UserAccount
#{
ViewBag.Title = "Trial";
}
<h2>Trial</h2>
#using (Html.BeginForm("Edit", "Home"))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>UserAccount</h4>
<hr />
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.AccountID, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.AccountID)
#Html.ValidationMessageFor(model => model.AccountID)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.AccountName, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.AccountName)
#Html.ValidationMessageFor(model => model.AccountName)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.RegistrationCode, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.RegistrationCode)
#Html.ValidationMessageFor(model => model.RegistrationCode)
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Created, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Created)
#Html.ValidationMessageFor(model => model.Created)
</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>
You need a POST action for Trial method like below:
[HttpPost]
public ActionResult Trial(UserAccount model)
{
if (ModelState.IsValid)
{
//Store the form data into your database
}
return View(model);
}
Then in your view, add a submit button element inside the form, also instead of Edit, you just need to use Trial for the postback.
#using (Html.BeginForm("Trial", "Home")) {
//
<input type="submit" value="Submit"/>
}
Note: You don't need to create other Edit action method if you don't have some other reasons.
If you don't know how to save the data to your database, below is an example:
Create your DbContext class
public class MyDbContext : DbContext
{
public MyDbContext()
: base("name=YourDbConnection")
{
}
public DbSet<UserAccount> UserAccounts { get; set; }
}
Then the action method will looks like:
public class HomeController : Controller
{
//
[HttpPost]
public ActionResult Trial(UserAccount model)
{
if (ModelState.IsValid)
{
using (var db = new MyDbContext())
{
db.UserAccounts.Add(model);
db.SaveChanges();
return RedirectToAction("Index");
}
}
return View(model);
}
}

Resources