foreign key not populating MVC3 - c#

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.

Related

Exception when comparing Guid

I am trying to get a list of all categories that contain a site with a specific ID. Below is the scaffold generated method i slightly modified.
[HttpGet("Categories/{id}")]
public async Task<IActionResult> GetCategoriesSite([FromRoute] Guid id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// This line throws the error
var categories = await _context.Categories.Where(o => o.Site.Id.Equals(id)).ToListAsync();
if (categories == null)
{
return NotFound();
}
return Ok(categories);
}
Unfortunately when i run this it throws the following error:
System.ArgumentException: Expression of type 'System.Nullable`1[System.Guid]' cannot be used for parameter of type 'System.Guid' of method 'Boolean Equals(System.Guid)'
Entities are very simple:
public class Category
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Site Site { get; set; }
}
public class Site
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<Category> Categories { get; set; }
}
What could i be doing wrong?
Seems like a bug in EF Core, check this issue on github. Workaround with replacing of Equals() with == operator worked fine for me:
var categories = await _context.Categories.Where(o => o.Site.Id == id).ToListAsync();

MVC 5 Create Validation error but valid ModelState

I am trying to create within MVC 5 and am getting a validation error even though the ModelState is coming back valid.
Error message
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
and when I look at the message, it shows....
The name 'e' does not exist in the current context
When I look at the POST data, the model that was created has all required fields filled in. I did notice that the model ID was assigned 0. I'm not sure if that is the error or if it is supposed to pass a zero for the ID.
What might the problem be?
WosController.cs
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "id,woNumber,woDescription,dueDate,qty,item_id,releaseDate,parentWO_id,wip_id")] Wo wo)
{
if (ModelState.IsValid)
{
db.Wos.Add(wo);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(wo);
}
Wo.cs
public partial class Wo
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Wo()
{
this.WoParts = new HashSet<WoPart>();
this.WoStatuses = new HashSet<WoStatus>();
}
public int id { get; set; }
public string woNumber { get; set; }
public string woDescription { get; set; }
public Nullable<System.DateTime> dueDate { get; set; }
public string qty { get; set; }
public Nullable<int> item_id { get; set; }
public Nullable<System.DateTime> releaseDate { get; set; }
public string status { get; set; }
public Nullable<int> parentWO_id { get; set; }
public int wip_id { get; set; }
public Nullable<int> part_id { get; set; }
public virtual Item Item { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<WoPart> WoParts { get; set; }
public virtual Wo woParentWO { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<WoStatus> WoStatuses { get; set; }
public virtual Part Part { get; set; }
public virtual Wip Wip { get; set; }
}
Wrap your call to SaveChangesAsync in a try...catch like so:
try
{
await db.SaveChangesAsync();
}
catch (DbEntityValidationException e)
{
var errorMessages = e.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
var fullErrorMessage = string.Join("; ", errorMessages);
var exceptionMessage = string.Concat(e.Message, " The validation errors are: ", fullErrorMessage);
throw new DbEntityValidationException(exceptionMessage, e.EntityValidationErrors);
}
That will show you the actual properties causing the validation issues. Then, update your question with the results, if you still need assistance.
Likely, your database is out of sync with your entities. The status property is not required on your entity, and by default properties of type string are nullable. That would explain why you're passing validation on post, but failing on actually saving the entity.
Generally, it's best not to rely on the database setting a default value in the first place. Instead, have the property itself have a default value, and then it will always be fine, regardless of what's going on at the database level:
private string _status;
public string status
{
get { return _status ?? "Default Value"; }
set { _status = value;
}
Short of that, if status is truly not required, then you should ensure that the status column on your table is nullable.

MVC - Validation (Model.State) using ViewModel

I´m needing some help. I have the following scenario and I think that I doing something wrong.
- Model "State"
namespace App.Model
{
public class State
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int idState { get; set; }
[Required(ErrorMessage = "Initials is required")]
public string StateInitials { get; set; }
[Required(ErrorMessage = "Name is required")]
public string StateName { get; set; }
[Display(Name = "Update Date")]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime? UpdateDate { get; set; }
[Display(Name = "Update Responsible")]
public string UpdateResponsible { get; set; }
} //class
} //namespace
- Model "Location"
namespace App.Model
{
public class Location
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int idLocation { get; set; }
public int idState { get; set; }
[Required(ErrorMessage = "Name is required")]
public string LocationName { get; set; }
public string Status { get; set; }
public string ManagerName { get; set; }
public string Address { get; set; }
[Display(Name = "Update Date")]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]
public DateTime? UpdateDate { get; set; }
[Display(Name = "Update Responsible")]
public string UpdateResponsible { get; set; }
} //class
} //namespace
The relation between State and Location is one to many, but I didn´t describe this on model (using navigation fields).
I have a view where I want to edit the locations. To do that, I´m using the following view model.
- View Model "LocationsViewModel"
namespace App.ViewModel
{
public class LocationsViewModel
{
public State objState { get; set; }
public List<Location> lstLocations { get; set; }
} //class
} //namespace
To edit the Locations I use the following controller.
namespace App.Controllers
{
public class LocationController : Controller
{
private DbContext db = new DbContext();
// GET: /Location/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
State objState = db.States.Find(id);
if (objState == null)
{
return HttpNotFound();
}
LocationsViewModel model = new LocationsViewModel();
model.objState = objState;
model.lstLocations = getLocations(objState.idState); //I didn´t show this method here just to simplify
return View(model);
} //Edit
// POST: /Location/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Editar(LocationsViewModel model)
{
State objState = db.States.Find(model.objState.idState);
try
{
if (ModelState.IsValid)
{
//Saving Locations
foreach (Location obj in model.lstLocations)
{
Location objLocation = db.Locations.Find(obj.idLocation);
objLocation.LocationName = obj.LocationName;
objLocation.Status = obj.Status;
objLocation.ManagerName = obj.ManagerName;
objLocation.Address = obj.Address;
objLocation.UpdateDate = DateTime.Now;
objLocation.UpdateResponsible = User.Identity.Name;
db.Entry(objLocation).State = EntityState.Modified;
db.SaveChanges();
} //foreach
return RedirectToAction("Index");
}
}
catch (Exception e)
{
ModelState.AddModelError("", e.Message);
}
model.objState = objState;
model.lstLocations = getLocations(objState.idState); //I didn´t show this method here just to simplify
return View(model);
} //Edit
} //class
} //namespace
The problem/question is:
I wrote this code to edit (save) the list of locations of a specific State. When I submit the "Edit" view, the MVC try to validade the list of Locations (lstLocations) and the State (objState) as well, but I want to validate only the list of locations.
Note 1. I need to pass to my Edit view both objects: objState and lstLocations. I need the objState object because I show some State´s properties to the user on page (view).
Note 2. I´m getting ModelState.IsValid = false because model.objLocation is not valid, but I don´t want to check objLocation (is not relevant for this view). I just want to check the list of Locations (lstLocation)
What is the best approach to achieve my gol? Am I doing something wrong? Need I to change my way of thinking?
You'll need two things. The first is to remove the object from the ModelState which you do not want to validate. The second is you need to put your code block that is to execute in a valid state within the if(ModelState.IsValid) block.
public ActionResult Edit(int? id)
{
//don't validate this field
ModelState.Remove("yourObject.property");
if (ModelState.IsValid)
{
...
}
}
In addition to the solution you've already chosen, I found the custom RequiredIfAttribute useful. Using that, you can control if something is required base upon another condition, e.g.:
...
public bool RequireLocationName {
get {
return !Addresses.Any();
}
}
[RequiredIf("RequireLocationName", true)]
public bool LocationName { get; set; }

How to use ViewModels in Edit Action without making another request?

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

Scaffolding ModelView creates underlying database tables

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.

Categories