I'm using Scaffolding in ASP.Net, I've a Model called "Page" which has attributes as follows
public class Page
{
private DateTime _Created_at = DateTime.Now;
private bool _IsActive = true;
public int ID { get; set; }
public string Code { get; set; }
[Required]
[DisplayName("Parent Code")]
public string ParentCode { get; set; }
[Required]
public string Title { get; set; }
************
}
Here, During Create Method, I'm updating Code attributes as follows
public ActionResult Create(Page page)
{
if (ModelState.IsValid)
{
page.Code = page.Url.Replace(" ", string.Empty);
page.IsActive = true;
db.Pages.Add(page);
db.SaveChanges();
return RedirectToAction("Details", new { id = page.ID });
}
return View(page);
}
Now, Problem is, I don't want this Code value change during Update method, I'm not included it in Edit form. But still it's updating 'NULL' value if I update.
I tried [Bind(Exclude = "Code")] for Page class, But no use.
You need a hidden field for code in your edit view. Use #Html.HiddenFor(model => model.Code).
Related
I have looked through a ton of tutorials and suggestions on how to work with DropDownList in MVC. I was able to get most of it working, but the selected item is not saving into the database. I am using MVC 3 and Razor for the view.
My DropDownList is getting created with the correct values and good looking HTML. When I set a breakpoint, I can see the correct selected item ID in the model getting sent to controller. When the view goes back to the index, the DropDownList value is not set. The other values save just fine.
Here are the related views. The DropDownList is displaying a list of ColorModel names as text with the ID as the value.
public class ItemModel
{
[Key]
public int ItemID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ColorModel Color { get; set; }
}
public class ItemEditViewModel
{
public int ItemID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int ColorID { get; set; }
public IEnumerable<SelectListItem> Colors { get; set; }
}
public class ColorModel
{
[Key]
public int ColorID { get; set; }
public string Name { get; set; }
public virtual IList<ItemModel> Items { get; set; }
}
Here are the controller actions.
public ActionResult Edit(int id)
{
ItemModel itemmodel = db.Items.Find(id);
ItemEditViewModel itemEditModel;
itemEditModel = new ItemEditViewModel();
itemEditModel.ItemID = itemmodel.ItemID;
if (itemmodel.Color != null) {
itemEditModel.ColorID = itemmodel.Color.ColorID;
}
itemEditModel.Description = itemmodel.Description;
itemEditModel.Name = itemmodel.Name;
itemEditModel.Colors = db.Colors
.ToList()
.Select(x => new SelectListItem
{
Text = x.Name,
Value = x.ColorID.ToString()
});
return View(itemEditModel);
}
[HttpPost]
public ActionResult Edit(ItemEditViewModel itemEditModel)
{
if (ModelState.IsValid)
{
ItemModel itemmodel;
itemmodel = new ItemModel();
itemmodel.ItemID = itemEditModel.ItemID;
itemmodel.Color = db.Colors.Find(itemEditModel.ColorID);
itemmodel.Description = itemEditModel.Description;
itemmodel.Name = itemEditModel.Name;
db.Entry(itemmodel).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(itemEditModel);
}
The view has this for the DropDownList, and the others are just EditorFor().
#Html.DropDownListFor(model => model.ColorID, Model.Colors, "Select a Color")
When I set the breakpoint on the db.Color.Find(...) line, I show this in the Locals window for itemmodel.Color:
{System.Data.Entity.DynamicProxies.ColorModel_0EB80C07207CA5D88E1A745B3B1293D3142FE2E644A1A5202B90E5D2DAF7C2BB}
When I expand that line, I can see the ColorID that I chose from the dropdown box, but it does not save into the database.
You do not need to set the whole Color object. Just set the ColorId property.
Change
itemmodel.Color = db.Colors.Find(itemEditModel.ColorID);
To
itemmodel.ColorId = itemEditModel.ColorID;
Edit
Note that your database does not store the whole object. The Color object in ItemModel is just a convenient way to access the ColorModel entity that is assosiated by a foreign key.
According to convention, the name of the foreign key property should be ColorId. Add this int property in your ItemModel class.
I'm building an MVC 3 website. I have a model looking like this:
public class Survey
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime DateStart { get; set; }
public DateTime DateEnd { get; set; }
// Not in view
public DateTime DateCreated { get; set; }
// Not in view
public DateTime DateModified { get; set; }
}
Based on this I also have a View Model to edit the survey information:
public class SurveyEditViewModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime DateStart { get; set; }
public DateTime DateEnd { get; set; }
}
When the user finishes editing I would like to persist the changes. Here's my controller post action:
[HttpPost]
public ActionResult Edit(SurveyEditViewModel model)
{
// Map the view model to a domain model using AutoMapper
Survey survey = Mapper.Map<SurveyEditViewModel, Survey>(model);
// Update the changes
_repository.Update(survey);
// Return to the overview page
return RedirectToAction("Index");
}
In my repository (it's a generic one for now) I have the following code:
public void Update(E entity)
{
using (ABCDataContext context = new ABCDataContext())
{
context.Entry(entity).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
}
When this executes I get the following error: "Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries."
I guess this was to be expected. Mapping from the view model to the model doesn't give me a complete Survey object.
I could modify my controller to look like this. And then it works:
[HttpPost]
public ActionResult Edit(SurveyEditViewModel model)
{
// Map the model to a real survey
survey = _repository.Find(model.Id);
survey.Name = model.Name;
survey.Description = model.Description;
survey.DateStart = model.DateStart;
survey.DateEnd = model.DateEnd;
// Update the changes
_repository.Update(survey);
// Return to the overview page
return RedirectToAction("Index");
}
But I was wondering if a better way is available?
[HttpPost]
public ActionResult Edit(SurveyEditViewModel model)
{
// Fetch the domain model to update
var survey = _repository.Find(model.Id);
// Map only the properties that are present in the view model
// and keep the other domain properties intact
Mapper.Map<SurveyEditViewModel, Survey>(model, survey);
// Update the changes
_repository.Update(survey);
// Return to the overview page
return RedirectToAction("Index");
}
I had a view model in that viewmodel i created two different instances using same model like this:
public ClaimViewModel()
{
engineClaim = new ClaimModel();
boatClaim = new ClaimModel();
}
public ClaimModel engineClaim { get; set; }
public ClaimModel boatClaim { get; set; }
I had properties in ClaimModel like this:
[Required]
public string Complaint { get; set; }
[RequiredIf("isEngineClaim", true , ErrorMessage = "Required")]
public string Cause { get; set; }
[Required]
public string Correction { get; set; }
public bool isEngineClaim { get; set; }
And in controller i am loading the index page like this
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
ClaimViewModel claim = new ClaimViewModel();
claim.engineClaim = new Models.ClaimModel();
claim.engineClaim.isEngineClaim = true;
claim.boatClaim = new Models.ClaimModel();
claim.boatClaim.isEngineClaim = false;
return View("Index", claim);
}
Now my problem is the requiredif validation is not working though the 'isEngineClaim' property is different for two instances. I am following this link
And moreover i had placed the hidden field of 'isEngineClaim' in my view also. But the requiredifvalidation is not working can anyone suggest me the solution.
I have a class, which has 8 props / 8 columns in DB. But on a Edit page, i dont want to show the AddedDate or UserID field, since i dont want user to change it.
public class Voucher
{
public int ID { get; set; }
public string Title { get; set; }
public string SiteName { get; set; }
public string DealURL { get; set; }
public DateTime AddedDate { get; set; }
public DateTime? ExpirationDate { get; set; }
public string VoucherFileURL { get; set; }
public Guid UserID { get; set; }
}
Here is what I have for Edit controller:
// POST: /Voucher/Edit/5
[HttpPost]
public ActionResult Edit(Voucher voucher)
{
if (ModelState.IsValid)
{
string[] excludeProperties = { "AddedDate", "UserID" };
UpdateModel(ModelState, "", null, excludeProperties);
db.Entry(voucher).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(voucher);
}
On Edit page, once i click on submit, i got the following error: System.Data.SqlServerCe.SqlCeException: An overflow occurred while converting to datetime.
Seems like the AddedDate didn't get excluded from the view model and triggered the error.
Would you please let me know how to fix it? Thanks!
public ActionResult Edit([Bind(Exclude = "AddedDate")]Voucher voucher)
no luck either
You are still passing in Voucher which could contain that field in it. I'm not sure what you are trying to accomplish with the UpdateModel here if you are already passing in a Voucher object?
Pass in Voucher, set it to modified and save it. If you want to use whats in the database then you'll have to
Load the object from the database
UpdateModel and exclude the properties
Save your entity.
You could simply use a View Model and post that.
public class Voucher
{
public int ID { get; set; }
public string Title { get; set; }
public string SiteName { get; set; }
public string DealURL { get; set; }
public DateTime? ExpirationDate { get; set; }
public string VoucherFileURL { get; set; }
public Guid UserID { get; set; }
}
and then load up your object from the db":
var voucher = db.Vouchers.Where(o=>o.ID==voucherViewModel.Id);
//manually copy the fields here then save it
//copy
db.SaveChanges();
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.