Can´t update a Model entity with several ICollection properties MVC3 - asp.net-mvc-3

Here is my problem,
I have the next Model in Componet.cs (My real component has ten more Icollections but as I think the problem should be the same with 1 ICollection or with 10 I´m not writting them down).
public partial class Component
{
public Component()
{
this.BrandOwner = new HashSet<BrandOwner>();
this.Description = new HashSet<Description>();
}
public int GestCompID { get; set; }
public int ID { get; set; }
public Nullable<int> BaseGestCompID { get; set; }
public Nullable<int> BaseCompID { get; set; }
public string DefName { get; set; }
public string ProductRef { get; set; }
public virtual country country { get; set; }
public virtual language language { get; set; }
public virtual ICollection<BrandOwner> BrandOwner { get; set; }
public virtual ICollection<Description> Description { get; set; }
In my application I can edit that component in order to add, delete or edit any of its attributtes. I can add several BrandOwners or Descriptions depending the Country or the language.
When I select one of those components to Edit I load the Component in a Session variable, and do all the actions over that Session variable.
When I´m finish editing the item I have two buttoms, Accept and Cancel.
If I cancel all the changes made in the Session variable are discharged.
The problem comes when I push the Accept button to update the selected item. I can´t find the way to
Here is my Accept method:
public ActionResult AcceptChanges()
{
try
{
Component component = (Component)Session["component"];
GIEntities.Current.Component.Attach(component);
GIEntities.Current.SaveChanges();
Session["component"] = null;
return RedirectToAction("Index", new { Message = "correct" });
}
catch (DbUpdateException ex)
{
String error = ex.ToString();
Component component = (Component)Session["component"];
return RedirectToAction("EditComponent", new { id = componente.ID });
}
}
I have tried attaching, adding and setting the status to Modified, but it won´t work.
I have also tried detaching de component and attaching it again.
Could somebody tell me what am I doing wrong?
Thank you very much and thanks in advance.
Sorry my bad english.

Related

Creating an object with other object properties from DbContext in MVC EF Core

I am sorry if the answer to this question is so obvious, but all (and I really mean all) of my Google search results are purple, regardless of the search phrase I try to wrap around my question.
I am trying to build an MVC .NET Core 3.0 application with Code-First.
I am able to create my models, have these setup correct (I think) in my Database (Azure SQL), and using Visual Studio's standard templates to create controllers and views. I am therefore able to create each one of my model individually.
What I am trying to do is
Create a view from where I can create a Rental-object, with link to RentalOwner and ParkingSpot.
(Create a view from where I can see all rentals created. This is not in scope for this question)
My models
public class ParkingSpot
{
public int ParkingSpotId { get; set; }
public int ParkingSpotNumber { get; set; }
}
public class RentalOwner
{
public int RentalOwnerId { get; set; }
public int TenantId { get; set; }
public Tenant Tenant { get; set; }
}
public class Tenant
{
public int TenantId { get; set; }
[Required]
[Display(Name="Navn")]
public string Name { get; set; }
[EmailAddress]
[Required]
public string Email { get; set; }
}
public class Rental
{
public int RentalId { get; set; }
public int ParkingSpotId { get; set; }
public ParkingSpot ParkingSpot { get; set; }
public int RentalOwnerId { get; set; }
public RentalOwner RentalOwner { get; set; }
}
I have tried creating a ViewModel, to use for creation of the Rental-model and the binding to other models.
public class RentalCreationView
{
public int Id { get; set; }
public Rental Rental { get; set; }
public int ParkingSpotId { get; set; }
public int TenantId { get; set; }
}
I've tried with this GET and HttpPost POST Actions in my Controller.
// GET: Rentals/Create
public async Task<IActionResult> CreateAsync()
{
var parkingSpots = await _context.ParkingSpots.ToListAsync();
ViewData["ParkingSpots"] = new SelectList(parkingSpots, "ParkingSpotId", "ParkingSpotNumber");
var tenants = await _context.Tenants.ToListAsync();
ViewData["Tenants"] = new SelectList(tenants, "TenantId", "Name");
return View();
}
public async Task<IActionResult> Create(RentalCreationView rcw)
{
if (ModelState.IsValid)
{
_context.RentalOwners.Add(rcw.Rental.RentalOwner);
_context.Rentals.Add(rcw.Rental);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(rcw);
}
I don't think my issue is with my View, and I guess I am missing something in either my models or in my controller.
Right now, when I try to create a Rental, the view is just loading for a couple of minutes after submit.
I have seen numerous of tutorials, read hundreds of questions and articles, and I know that I am missing some basic steps, but the code above is what I have right now (I have about 500 lines of commented out code that doesn't work).
I've spent 2 weeks trying to learn how to do this but I need some help from someone who knows what I'm trying to do.
Any relevant links, videos or documentation would be awesome.. I am really stuck deep.
Thank you
EDIT: I have found a solution, and to all future readers who got this page from their 1000th Google search, what I did was change a bit in the models and add this HttpPost Create in my Controller.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(RentalCreationView rcw)
{
List<ParkingSpot> parkingSpots = _context.ParkingSpots.ToList();
List<Tenant> tenants = _context.Tenants.ToList();
RentalOwner ro = new RentalOwner
{
Tenant = tenants.Find(t => t.TenantId == rcw.TenantId),
OwnerSince = rcw.StartDate
};
Rental ren = new Rental
{
StartDate = rcw.StartDate,
ParkingSpot = parkingSpots.Find(ps => ps.ParkingSpotId == rcw.ParkingSpotId),
RentalOwner = ro
};
if (ModelState.IsValid)
{
_context.RentalOwners.Add(ro);
_context.Rentals.Add(ren);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
else
{
var errors = ModelState.Select(x => x.Value.Errors)
.Where(y => y.Count > 0)
.ToList();
}
return View();
}

AJAX Posting Slickgrid data to MVC

I have a slickgrid and am attempting to save its data back to the server.
When I breakpoint on the server, I can see the data in the Request.Form object, but I can't make it work with my object.
My data looks like...
[
{"id":"0","LineNumber":"","Detail":"MOT cost","Code":" ","Qty":"1","Est":" ","CustomerDamage":false,"Cost":"44.00","Value":"44.00","VAT":true,"SelfBillingLine":"False","DefectStatus":" "},
{"id":"62","LineNumber":"","Detail":"CRACKS IN Chassis","Code":"TLMA02","Qty":"1","Est":"","CustomerDamage":false,"Cost":"35.00","Value":"35.00","VAT":true,"SelfBillingLine":"False","DefectStatus":"Large repair"},
{"id":"63","LineNumber":"","Detail":"TEAR IN N/S CURTAIN","Code":"TLMA02","Qty":"1","Est":"","CustomerDamage":true,"Cost":"10.00","Value":"10.00","VAT":true,"SelfBillingLine":"False","DefectStatus":"Customer"}
]
I am posting with a button onclick...
$("#SBSave").click(function() {
debugger;
var details = JSON.stringify(defectrows);
save('SBDetail/SaveSBItem', details);
});
I have tried a number of things to receive the data, none of them work.
My controller...
[HttpPost]
public void SaveSBItem(SelfBillDetailList details, string Approve = "")
{
// Actions here.
}
My model...
Trying a number of things, neither work...
public class SelfBillDetailList
{
public IEnumerable<SelfBillingIncomingDetail> IncomingDetails { get; set; }
}
public class SelfBillingIncomingDetail
{
public int id { get; set; }
public string Code { get; set; }
public string LineNumber { get; set; }
public string Detail { get; set; }
public string Action { get; set; }
public string Qty { get; set; }
public string Est { get; set; }
public bool VAT { get; set; }
public bool CustomerDamage { get; set; }
public string Cost { get; set; }
public string Value { get; set; }
public DateTime Received { get; set; }
public string DefectStatus { get; set; }
public bool SelfBillingLine { get; set; }
}
So, I have tried an individual SelfBillingIncomingDetail and also a the SelfBillDetailList.
Neither work.
I have even sent an individual row, again, neither work.
I want to send it as a group, so it will be an array of SelfBillingIncomingDetail but nothing works.
Thank you for your help.
I have done it again... eventually found an answer after looking for ages.
Darin Dimitrov's answer in
Post an Array of Objects via JSON to ASP.Net MVC3
let me to the answer.
It seems that when sending the data, I need to give the array of data the same name as the property name in SelfBillDetailList, so...
var details = JSON.stringify({IncomingDetails : defectrows});
fixes it.

How can I edit an entity in MVC4 with EF5 which has a unique constraint?

[HttpPost]
public ActionResult Edit(Car car)
{
if (ModelState.IsValid)
{
db.Entry(car).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(car);
}
This is a controller method scaffolded by MCV 4
My "car" entity has a unique field: LicensePlate.
I have custom validation on my Entity:
Validation:
public partial class Car
{
partial void ValidateObject(ref List<ValidationResult> validationResults)
{
using (var db = new GarageIncEntities())
{
if (db.Cars.Any(c => c.LicensePlate.Equals(this.LicensePlate)))
{
validationResults.Add(
new ValidationResult("This licenseplate already exists.", new string[]{"LicensePlate"}));
}
}
}
}
should it be usefull, my car entity:
public partial class Car:IValidatableObject
{
public int Id { get; set; }
public string Color { get; set; }
public int Weight { get; set; }
public decimal Price { get; set; }
public string LicensePlate { get; set; }
public System.DateTime DateOfSale { get; set; }
public int Type_Id { get; set; }
public int Fuel_Id { get; set; }
public virtual CarType Type { get; set; }
public virtual Fuel Fuel { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var result = new List<ValidationResult>();
ValidateObject(ref result);
return result;
}
partial void ValidateObject(ref List<ValidationResult> validationResults);
}
QUESTION:
Everytime I edit a car, it raises an error:
Validation failed for one or more entities. See
'EntityValidationErrors' property for more details.
The error is the one raised by my validation, saying it can't edit because there is already a car with that license plate.
If anyone could point me in the right direction to fix this, that would be great!
I searched but couldn't find anything, so even related posts are welcome!
note: this field has a unique constraint, so it is imperative that this validation is still triggered for a create-action
Allright I found a fix but I'm not sure it's the best ever.
I modified the validation so that it only triggers when the Id is non existend (so.. 0).
This way, I can make a distiction between new entities and updated ones.
if (db.Cars.Any(c => c.LicensePlate.Equals(this.LicensePlate) && c.Id != this.Id))
This does fix my problem, but somehow I think there should be a cleaner fix.

Is there any way to force a model to be revalidated in an MVC3 controller?

I have a Model which contains an Address and Person twice, once for the "main" contact, and once for the "invoice" contact, and a boolean value called InvoiceContactSameAsMain - a clumsy name, but descriptive. The getter of the property checks to see if the Address and Contact objects for "main" and "invoice" are the same, and returns true if they are. The setter checks to see if the value is true, and if so, copies the main Person over the invoice Person , and the main Address over the invoice Address.
In my View, the boolean value is represented by a check box (as you'd expect). Attached to this is a small JS function which, if the check box is checked, hides the invoice fields and "switches off" the client-side validation by setting the data-val HTML attribute to false and forcing a re-parse of the unobtrusive validation attributes in the form. Un-checking the box naturally shows the fields and turns the validation back on.
All of this works fine, until I get to my Controller.
Despite the Model being "valid" and containing the correct fields (thanks to my InvoiceContactSameAsMain setter), ModelState.IsValid remains resolutely false, and I can't seem to find any way to revalidate the model. If I clear the ModelState, any and all errors disappear. I'd very much rather avoid digging through the fields in the ModelState by name, as the Person and Address objects are used throughout the project and may need to change or be extended at some point.
Is there something obvious I've missed here that will allow me to revalidate the ModelState? I've tried TryUpdateModel and TryValidateModel, but they both appear to use the cached ModelState values. I've even tried recursively calling my Action again, passing in the "fixed" model. I'm almost thankful that one didn't work.
Please let me know if any more detail or examples will help.
Edit: Obviously, if this is completely the wrong way to approach the problem, just let me know.
Edit 2: Added code samples as per Ron Sijm's suggestion.
The model is as follows:
public class Details
{
public int? UserID { get; set; }
public Company Company { get; set; }
public Address CompanyAddress { get; set; }
public Person MainPerson { get; set; }
public Address InvoiceAddress { get; set; }
public Person InvoiceContact { get; set; }
[Display(Name = "Promotional code")]
[StringLength(20, ErrorMessage = "Promotional code should not exceed 20 characters")]
public string PromotionalCode { get; set; }
[Display(Name = "Invoice contact same as main")]
public bool InvoiceContactSameasMain
{
get { return InvoiceContact.Equals(MainPerson); }
set
{
if (value)
{
InvoiceContact = MainPerson.Copy();
InvoiceAddress = CompanyAddress.Copy();
}
}
}
[_Common.MustAccept]
[Display(Name = "I agree with the Privacy Policy")]
public bool PrivacyFlag { get; set; }
[Display(Name = "Please subscribe to Sodexo News Letter")]
public bool MarketingOption { get; set; }
[Display(Name = "Contract number")]
public int? ContractNumber { get; set; }
public Details()
{
Company = new Company();
CompanyAddress = new Address();
MainPerson = new Person();
InvoiceAddress = new Address();
InvoiceContact = new Person();
}
}
This is wrapped in a ViewModel as there are a number of SelectLists involved in the page:
public class DetailsViewModel
{
public Details Details { get; set; }
public SelectList MainContactTitles { get; set; }
public SelectList InvoiceContactTitles { get; set; }
public SelectList SICCodes { get; set; }
public SelectList TypesOfBusiness { get; set; }
public SelectList NumbersOfEmployees { get; set; }
public DetailsViewModel()
{
}
}
The Controller's two relevant actions are as follows:
public class DetailsController : _ClientController
{
[Authorize]
public ActionResult Index()
{
DetailsViewModel viewModel = new DetailsViewModel();
if (Client == null)
{
viewModel.Details = DetailsFunctions.GetClient((int)UserId, null);
}
else
{
viewModel.Details = DetailsFunctions.GetClient((int)UserId, Client.ContractNumber);
}
viewModel.MainContactTitles = DetailsFunctions.GetTitles((int)UserId, viewModel.Details.MainPerson.title);
viewModel.InvoiceContactTitles = DetailsFunctions.GetTitles((int)UserId, viewModel.Details.InvoiceContact.title);
viewModel.SICCodes = DetailsFunctions.GetSICCodes(viewModel.Details.Company.sic_code);
viewModel.NumbersOfEmployees = DetailsFunctions.GetNumbersOfEmployees(viewModel.Details.Company.number_of_employees);
viewModel.TypesOfBusiness = DetailsFunctions.GetTypesOfBusiness(viewModel.Details.Company.public_private);
return View(viewModel);
}
[Authorize]
[HttpPost]
public ActionResult Index(DetailsViewModel ViewModel)
{
if (ModelState.IsValid)
{
//go to main page for now
DetailsFunctions.SetClient((int)UserId, ViewModel.Details);
return RedirectToAction("Index", "Home");
}
else
{
ViewModel.MainContactTitles = DetailsFunctions.GetTitles((int)UserId, ViewModel.Details.MainPerson.title);
ViewModel.InvoiceContactTitles = DetailsFunctions.GetTitles((int)UserId, ViewModel.Details.InvoiceContact.title);
ViewModel.SICCodes = DetailsFunctions.GetSICCodes(ViewModel.Details.Company.sic_code);
ViewModel.NumbersOfEmployees = DetailsFunctions.GetNumbersOfEmployees(ViewModel.Details.Company.number_of_employees);
ViewModel.TypesOfBusiness = DetailsFunctions.GetTypesOfBusiness(ViewModel.Details.Company.public_private);
return View(ViewModel);
}
}
}
I can provide the view and JS if needs be, but as the Model binding is all working just fine, I'm not sure how much help that is.
It's a moderately crap hack, but I've ended up just clearing the ModelState errors for the relevant fields in the controller before checking ModelState.IsValid:
if(ViewModel.Details.InvoiceContactSameasMain)
{
//iterate all ModelState values, grabbing the keys we want to clear errors from
foreach (string Key in ModelState.Keys)
{
if (Key.StartsWith("Details.InvoiceContact") || Key.Startwith("Details.InvoiceAddress"))
{
ModelState[Key].Errors.Clear();
}
}
}
The only upside is, if the Person or Address objects change, this code won't need to be altered.

asp.net mvc 3 entity framework, passing model info in Get request of create action

I'm having trouble passing view information from my Get/Create action to my view. Here are my three model classes;
public class Competition
{
public int Id { get; set; }
public int CompetitionId { get; set; }
public string Name { get; set; }
public string Prize { get; set; }
}
public class CompetitionEntry
{
public int Id { get; set; }
public int CompetitionEntryId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int CompetitionId { get; set; }
}
public class CompetitionEntryViewModel
{
public int Id { get; set; }
public Competition Competitions { get; set; }
public int CompetitionId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
Here is my Get/Create action in CompetitionEntry Controller;
public ActionResult Create(int id)
{
CompetitionEntryViewModel competitionentryviewmodel = db.CompetitionEntriesView.Find(id);
return View(competitionentryviewmodel);
}
I know this doesn't work. The id parameter goes into the URL fine. How to I get access to my Competition class in th Get action? I need to be able to show the competion name on my Create Competition entry view.
Thanks in advance!
public ActionResult Create(int id)
{
var data = db.CompetitionEntriesView.Find(id);
CompetitionEntryViewModel competitionentryviewmodel = new CompetitionEntryViewModel();
competitionentryviewmodel.CompetitionName = data.Name;
return View(competitionentryviewmodel);
}
What you are trying to do is build an object graph and display it through a view model. In order to do this, you need to map your domain model(s) to your view model.
You can do the mapping yourself by writing a lot of code (re-inventing the wheel), or, you could consider using third party tools to do this for you. I recommend you use an AutoMapper as it is very simple to use imo.
The other problem is that your view model contains a domain model. This is likely to cause you a lot of headache in near future. If I were you, I would replace Competition with CompetitionViewModel.
I would also consider creating a view model for a list of competitions, i.e. CompetitionsViewModel. Look into partial views to see how you can display a list of competitions.
Good luck

Resources