I'm trying to use ViewModels for the first time using AutoMapper. I have two models:
public class Item
{
public int ItemId { get; set; }
public bool Active { get; set; }
public string ItemCode { get; set; }
public string Name { get; set; }
public List<ItemOption> ItemOptions { get; set; }
//...
}
public class ItemOption
{
public int ItemOptionId { get; set; }
public string Name { get; set; }
public string Barcode { get; set; }
//...
}
Which I have turned into two ViewModels:
public class ItemDetailViewModel
{
public int ItemDetailViewModelId { get; set; }
public int ItemId { get; set; }
public bool Active { get; set; }
public string ItemCode { get; set; }
public string Name { get; set; }
public List<ItemDetailItemOptionViewModel> ItemOptions { get; set; }
}
public class ItemDetailItemOptionViewModel
{
public int ItemDetailItemOptionViewModelId { get; set; }
public int ItemOptionId { get; set; }
public string Name { get; set; }
public string Barcode { get; set; }
}
I then set the following in my application start-up:
Mapper.CreateMap<Item, ItemDetailViewModel>();
Mapper.CreateMap<ItemOption, ItemDetailItemOptionViewModel>();
Finally I scaffolded my ItemDetailViewModel:
I then built my project and added a new Item through /Item/Create
I had a look in the database expecting to see that I would have an entry in the Item table, but instead I have ItemDetailViewModel and ItemDetailItemOptionViewModel tables, which I wasn't expecting and the data is is ItemDetailViewModel.
I assume I have done something wrong with my scaffolding? How do I scaffold off the ViewModel without making it part of the main business models?
Further Details
If it isn't possible to scaffold the controller with a ViewModel, then how do I reference the ViewModel in the controller and save changes back to the database?
For example what would the following change to once I remove ItemDetailViewModel from the db context?
//
// POST: /Item/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ItemDetailViewModel itemdetailviewmodel)
{
if (ModelState.IsValid)
{
db.ItemDetailViewModels.Add(itemdetailviewmodel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(itemdetailviewmodel);
}
Further Details [2]
So am I correct that my Index/Details should work as so or is there a better way of doing it?
//
// GET: /Item/
public ActionResult Index()
{
var items = db.Items.ToList();
var itemdetailviewmodel = AutoMapper.Mapper.Map<ItemDetailViewModel>(items);
return View(itemdetailviewmodel);
}
//
// GET: /Item/Details/5
public ActionResult Details(int id = 0)
{
ItemDetailViewModel itemdetailviewmodel = AutoMapper.Mapper.Map<ItemDetailViewModel>(db.Items.Find(id));
if (itemdetailviewmodel == null)
{
return HttpNotFound();
}
return View(itemdetailviewmodel);
}
Scaffolding is not that intelligent. The standard controller scaffolding template is creating a DbContext with the controller model and presumes you are working with the DB models, not view models and it does not use Automapper. So you'll need to either not use scaffolding, or check what it has done before using it.
And nothing is wrong with the way you use scaffolding, it is just not supposed to do what you expect.
Update this is how you do this without scaffolding
// Without Automapper
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ItemDetailViewModel model)
{
if (ModelState.IsValid)
{
var item = new Item()
{
Active = model.Active,
ItemCode = model.ItemCode,
Name = model.Name,
ItemOptions = // code to convert from List<ItemDetailItemOptionViewModel> to List<ItemOption>
}
db.Items.Add(item);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
// with Automapper - not recommended by author of Automapper
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ItemDetailViewModel model)
{
if (ModelState.IsValid)
{
var item = Automapper.Mapper.Map<Item>(model);
db.Items.Add(item);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
You'll need to modify your DbContext to have IDbSet<Item> Items instead of IDbSet<ItemDetailViewModels> ItemDetailViewModels.
Automapper was created to map from Domain Models to View Models and not the other way. I have done that for a while, but this is troublesome and causes subtle bugs and other maintenance problems. Even Jimmy Bogard himself says you should not map from view models to domain models.
Related
Im new to asp.net and i started with basic crud operations.
I made the css class with db name values (code,name,price,currency) and DBcontext for this, when i made the controller it doesnt not add the values to the database but it's stored in somewhere.
When i use as a normal query from c# code, it's stored into the database.
My question is why does the CRUD operations from the (csfile and context) are not in the database?
--cs class--
public class Productos
{
[Key]
public int Codigo { get; set; }
public string Nombre { get; set; }
public decimal Precio { get; set; }
public string Moneda { get; set; }
}
-product context
public class ProductoContext : DbContext
{
public ProductoContext() : base("database name")
{
}
public DbSet<Productos> Producto { get; set; }
}
---this is the code to create---
// GET: Productos/Create
public ActionResult Create()
{
return View();
}
// POST: Productos/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Codigo,Nombre,Precio,Moneda")] Productos productos)
{
if (ModelState.IsValid)
{
db.Producto.Add(productos);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(productos);
}
---- connection string------
I need a specific answer or solution about this particular case ,
I have EditVehicleViewModel that's passed to Edit Vehicle Controller Action like so
public async Task<ActionResult> Edit(EditVehicleViewModel vehiclViewModel)
{
if (ModelState.IsValid)
{
// I NEED TO MAP THE VIEW_MODEL TO THE MODEL HERE
db.Entry(vehicleModel).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(vehiclViewModel);
}
I need to update the Model based on the changes that's been to the ViewModel , without making a request to get the Vehicle that's changed and SaveChanges() on it , while still using this line of code if possible
db.Entry(vehiclViewModel).State = EntityState.Modified;
EDIT
Here's my model and ViewModel
ViewModel
public class EditVehicleViewModel
{
[Required]
public string LicenceNumber { get; set; }
public int? Year { get; set; }
public string Color { get; set; }
[DataType(DataType.Upload)]
public HttpPostedFileBase ImageUpload { get; set; }
public string VINNumber { get; set; }
}
Model
public class Vehicle
{
public int ID { get; set; }
[Required]
public string LicenceNumber { get; set; }
[Required]
public Nullable<DateTime> Year { get; set; }
[Required]
public string Color { get; set; }
[DataType(DataType.ImageUrl)]
public string ImageUrl { get; set; }
[Required]
public string VINNumber { get; set; }
}
First you have to install any one of the following package from nuget,
PM> Install-Package TinyMapper
OR
PM> Install-Package AutoMapper
then add in your code, if tinymapper is used
TinyMapper.Bind<Vehicle, EditVehicleViewModel>();
Vehicle vehicleModel = TinyMapper.Map<EditVehicleViewModel>(vehiclViewModel);
db.Entry(vehicleModel).State = EntityState.Modified;
OR
if automapper is used,
Mapper.CreateMap<Vehicle , EditVehicleViewModel>();
Vehicle vehicleModel = Mapper.Map<EditVehicleViewModel>(vehiclViewModel);
db.Entry(vehicleModel).State = EntityState.Modified;
Sorry if this is not right . I am still not too sure where vehicleModel is
public async Task<ActionResult> Edit(EditVehicleViewModel vehiclViewModel)
{
if (ModelState.IsValid)
{
// I am guessing the VINNumber is the identifier
Vehicle vModel = db.Vehicle.FirstOrDefault(v => v.VINNumber == vehiclViewModel.VINNumber);
// Mapping here
vModel.LicenceNumber = vehiclViewModel.LicenceNumber;
vModel.Year = vehiclViewModel.Year;
vModel.Color = vehiclViewModel.Color;
vModel.VINNumber = vehiclViewModel.VINNumber
vModel.ImageUrl = vehiclViewModel.ImageUrl;
db.Entry(vModel).State = vModel.ID == 0 ? EntityState.Added : EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(vehiclViewModel);
}
I assume this is a post because it is an edit. I might be wrong but it might be an idea to add the vehicle id that was used to identify it on the edit page load in a
#Html.Hidden()
That can then be used as the identifier when looking for the vehicle that needs to be saved
I have a model Team which looks like this:
public class Team
{
[Key]
public int TeamId { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public virtual Group Group { get; set; }
}
I have a method to Edit it:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Team team)
{
using (var db = new DbConnection())
{
if (ModelState.IsValid)
{
db.Entry(team).State = EntityState.Modified;
db.Groups.Attach(team.Group);
db.SaveChanges();
}
return PartialView(team);
}
}
However it is not saving the change to the Group column. Meanwhile Create method does work although I see no difference between them:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Team team)
{
using (var db = new DbConnection())
{
if (ModelState.IsValid)
{
db.Groups.Attach(team.Group);
db.Teams.Add(team);
db.SaveChanges();
return RedirectToAction("Index");
}
return PartialView(team);
}
}
The values for the Group come the same in both methods. I bring the values to the form like this (again, same thing for both):
ViewBag.Groups = db.Groups.ToList().Select(g => new SelectListItem()
{
Text = g.Code,
Value = g.GroupId.ToString(),
Selected = false
}).ToList();
and use them like this:
#Html.DropDownListFor(model => model.Group.GroupId, (List<SelectListItem>)ViewBag.Groups)
Can someone please explain what the difference between the two methods is (in terms of one saving Group and other - not)?
P.S. Using both the latest MVC and EF.
Apparently it is a must (?) (at least I couldn't find anything better) to add a field matching the type of the Group in order to be able to easily save. No need to attach anything this way. Now my model looks like:
public class Team
{
[Key]
public int TeamId { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public int? GroupId { get; set; }
public virtual Group Group { get; set; }
}
and my Save and Create methods got this line:
db.Groups.Attach(team.Group);
removed.
Maybe a simple question, but I can't seem to figure it out. Saving a collection to a model when adding a model to the database isn't working. I have a site which uses asp.net MVC and entity framework.
The models:
public class Event
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<EventRange> Ranges { get; set; }
}
public class EventRange
{
public int Id { get; set; }
public string RangeName { get; set; }
public string RangeDescription { get; set; }
public int Capacitiy { get; set; }
}
The controller actions:
[HttpPost]
public ActionResult Create(Event model)
{
ICollection<EventRange> eventRanges = new Collection<EventRange>();
var range = new EventRange {RangeName = "testrange", RangeDescription = "test", Capacitiy = 5}
eventRanges.Add(range);
model.Ranges = eventRanges;
db.Events.Add(model);
db.SaveChanges();
return View();
}
public ActionResult Events()
{
return View(db.Events);
}
When setting a breakpoint in the Events action and evaluated the query, the Range isn't saved to the event:
Code Screenshot
Note that that the database created for the eventrange model by EF does save the range:
EF DB Screenshot
Am I doing something wrong?
What if you mark the Ranges property as virtual?
public virtual ICollection<EventRange> Ranges { get; set; }
Hi people I have the following code:
public ActionResult Create(GameTBL gametbl)
{
if (ModelState.IsValid)
{
//First you get the gamer, from GamerTBLs
var gamer = db.GamerTBLs.Where(k => k.UserName == User.Identity.Name).SingleOrDefault();
//Then you add the game to the games collection from gamers
gamer.GameTBLs.Add(gametbl);
db.SaveChanges();
return RedirectToAction("Index");
}
}
It is giving me the following error:
Error 1 'MvcApplication1.Controllers.GameController.Create(MvcApplication1.Models.GameTBL)': not all code paths return a value
What this code is trying to do is trying to populate the foreign key of gamer into the Game Table
Model for my controller Gamer:
public string UserName { get; set; }
public int GamerID { get; set; }
public string Fname { get; set; }
public string Lname { get; set; }
public string DOB { get; set; }
public string BIO { get; set; }
Model for my Game Controller:
public int GameID { get; set; }
public string GameName { get; set; }
public string ReleaseYear { get; set; }
public string Cost { get; set; }
public string Discription { get; set; }
public string DownloadableContent { get; set; }
public string Image { get; set; }
public string ConsoleName { get; set; }
public int GamerIDFK { get; set; }
public byte[] UserName { get; set; }
You just need to return a view when your ModelState isn't valid.
public ActionResult Create(GameTBL gametbl)
{
if (ModelState.IsValid)
{
//First you get the gamer, from GamerTBLs
var gamer = db.GamerTBLs.Where(k => k.UserName == User.Identity.Name).SingleOrDefault();
//Then you add the game to the games collection from gamers
gamer.GameTBLs.Add(gametbl);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(gametbl);
}
This will make the page show any errors in model creation (assuming you have validation).
try this...the return statement should be outside the if statement...the issue is you are not returning a view/action result when the modelstate is not valid...
public ActionResult Create(GameTBL gametbl)
{
if (ModelState.IsValid)
{
//First you get the gamer, from GamerTBLs
var gamer = db.GamerTBLs.Where(k => k.UserName == User.Identity.Name).SingleOrDefault();
//Then you add the game to the games collection from gamers
gamer.GameTBLs.Add(gametbl);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(gametbl);
}
Just so you know, the error isn't really ASP.Net MVC related - it would be an error in any method that returns a value.
The error message not all code paths return a value means just that - there is a path through the code that doesn't return a value, when the method signature says that it should.
In your case, your action method has the signature ActionResult Create(GameTBL gametbl) so all paths through the method have to return an ActionResult. In your code, the path that occurs when ModelState.IsValid is true does return an ActionResult - but nothing is returned in the path where ModelState.IsValid is false.
The other answers give you examples on how to correct your code by returning an ActionResult through the 'ModelState.IsValid is false' path.