I am trying to do a bit of a custom error handler. We have 4 tabs (using JQuery tabs), they are all build from one large model. Say for simplicity the model looks like:
myModel.HomeInfo
myModel.PhoneNumbers
myModel.Addresses
myModel.PersonalDetails
Each part is an object that have various bits of information. They all have attributes on them and validate messages.
At the top of the page (above the tabs) I want to display some top level errors, by that I mean the errors for attributes on the "myModel" object. This works when I do the:
foreach (ModelState state in viewData.ModelState.Values)
When I do:
#Html.ValidationSummary(false)
on my view I get all errors from each of the four objects and all their children, (more than 10). But when I go through the errors my self, (code above), I only get 2 errors, (the errors for "myModel" only, not its child properties).
I tried to use ILSPY to see what the validation summary is doing and replicate it. Believe I had the code pretty much line for line, but it still only got the two errors.
I do not know what magic is going on when I use the #Html.ValidationSummary().
What I want to know is how I can get all the errors for the whole object my self to be able to display some of the errors on each tab.
for clarification here is my basic model:
public class MemberProfileModel
{
[CompanyTabValid]
public CompanyInformationModel CompanyInformation { get; set; }
[ContactTabValid]
public ContactInformationModel ContactInformation { get; set; }
[InvoiceTabValid]
public InvoiceInformationModel InvoiceInformation { get; set; }
[TabProductIdentificationMarkValid]
public ProductIdentificationMarkModel ProductIdentificationMark { get; set; }
}
public class CompanyTabValid : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var model = value as CompanyInformationModel;
if(model == null) throw new ArgumentNullException("value");
var failed = new ValidationResult("Company information incomplete.");
return model.IsValid ? ValidationResult.Success : failed;
}
}
public class ContactInformationModel : BaseModel
{
public ContactInformationModel()
{
PrimarySiteAddress = new AddressInformation();
PrimarySiteContact = new ContactInformation();
RegisteredOfficeAddress = new AddressInformation();
RegisteredOfficeContact = new ContactInformation();
}
public override void Validate()
{
IsValid = PrimarySiteAddress.IsValid &&
PrimarySiteContact.IsValid &&
RegisteredOfficeAddress.IsValid &&
RegisteredOfficeContact.IsValid;
}
public AddressInformation PrimarySiteAddress { get; set; }
public ContactInformation PrimarySiteContact { get; set; }
public AddressInformation RegisteredOfficeAddress { get; set; }
public ContactInformation RegisteredOfficeContact { get; set; }
}
public class AddressInformation : BaseModel
{
public int Id { get; set; }
public Guid MemberId { get; set; }
/// <summary>
/// This property is only here to make EF happy, do not use
/// </summary>
public int LocationTypeValue { get; set; }
public LocationType LocationType { get { return (LocationType) LocationTypeValue; } set { LocationTypeValue = (int) value; } }
[Required(AllowEmptyStrings = false, ErrorMessage = "Address Line 1 required.")]
[Display(Name = "Address Line 1 *")]
public string AddressLine1 { get; set; }
[Display(Name = "Address Line 2")]
public string AddressLine2 { get; set; }
[Display(Name = "Address Line 3")]
public string AddressLine3 { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "Town required.")]
[Display(Name = "Town *")]
public string Town { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "County required.")]
[Display(Name = "County *")]
public string County { get; set; }
[Display(Name = "Country *")]
public string Country { get; set; }
[RequiredOneOfTwo("InterationalPostCode", ErrorMessage="PostCode or international PostCode are required.")]
[Display(Name = "Post Code *")]
public string PostCode { get; set; }
[RequiredOneOfTwo("PostCode", ErrorMessage = "International PostCode or PostCode are required.")]
[Display(Name = "International Post Code *")]
public string InterationalPostCode { get; set; }
public override void Validate()
{
if (string.IsNullOrEmpty(AddressLine1))
{
this.IsValid = false;
return;
}
else if (string.IsNullOrEmpty(Town))
{
this.IsValid = false;
return;
}
else if (string.IsNullOrEmpty(County))
{
this.IsValid = false;
return;
}
else if (string.IsNullOrEmpty(Country))
{
this.IsValid = false;
return;
}
else if (string.IsNullOrEmpty(PostCode) && string.IsNullOrEmpty(InterationalPostCode))
{
this.IsValid = false;
return;
}
this.IsValid = true;
return;
}
}
I have shown an example of a validation attribute (some of ours are custom, some are normal), the top level MemberProfileModel = myModel in this example, and ContactInformationModel is one of its children which in turn has its own objects such as AddressInformation.
Thanks
I found out why this wasn't working for me. As usual it was me being silly. Because the model has multiple layers / levels to it, I.e. model.someobject.someotherobject.someproperty, when I called tryValidateModel it would validate the top level but not the inner layers.
The solution to this was to ensure they are all called:
TryValidateModel(mp);
TryValidateModel(mp.ContactInformation.PrimarySiteAddress);
TryValidateModel(mp.ContactInformation.RegisteredOfficeAddress);
So my solution is to either create a method to call try validate on each object level or create a refelctive method to do it for me.
In the post event of your page, in the controller just add this:
[HttpPost]
public ActionResult Create(TestViewModel testViewModel)
{
// If not Valid
if (!ModelState.IsValid)
{
return this.View(testViewModel)
}
...
}
And don't forget to add your required and/or other validation to your viewmodel access methods:
[Required(ErrorMessageResourceType = typeof(Resources.MyProject), ErrorMessageResourceName = "validation_Test")]
public virtual string HomeInfo { get; set; }
And in your view:
<div class="editor-row">
<div class="editor-label">
#Html.LabelFor(model => model.HomeInfo)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.HomeInfo)
#Html.ValidationMessageFor(model => model.HomeInfo)
</div>
</div>
I got this from here:
http://www.unknownerror.org/opensource/aspnet/Mvc/q/stackoverflow/1352948/how-to-get-all-errors-from-asp-net-mvc-modelstate
public static List<string> GetErrorListFromModelState
(ModelStateDictionary modelState)
{
var query = from state in modelState.Values
from error in state.Errors
select error.ErrorMessage;
var errorList = query.ToList();
return errorList;
}
Related
I've problem with creating new record to empty tables on SQLServer
When trying to pass new record I get 'Object reference not set to an instance of an object.' error
When I try edit existing record, tables display content properly, but save changes won't work. With Guests table it will only reload old entry without changes, and for Contacts it's returning same error as on creating new record.
App should let create and edit record by displaying as partial view editor forms for each table.
I'm beginner in MVC.
Below is my code.
Tables models:
[Table("GuestsTest")]
public class Guest
{
[Key]
[HiddenInput(DisplayValue = false)]
public int GuestID { get; set; }
public string GuestLastName { get; set; }
public string GuestFirstName { get; set; }
public string GuestMiddleName { get; set; }
public string GuestEmail { get; set; }
public string GuestSex { get; set; }
}
[Table("ContactsTest")]
public class Contact
{
[Key]
[HiddenInput(DisplayValue = false)]
public int ContactID { get; set; }
[HiddenInput(DisplayValue = false)]
public int GuestID { get; set; }
public int PostalCode { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string HouseNumber { get; set; }
public string PhoneNumber { get; set; }
My view model
public class TableViewModel
{
public Guest GetGuest { get; set; }
public Contact GetContact { get; set; }
}
My controllers
public class AdminController : Controller
{
private IGuestRepository guestRepository;
private IContactRepository contactRepository;
private IQRCodeRepository qrcodeRepository;
public AdminController(IGuestRepository repoG, IContactRepository repoC, IQRCodeRepository repoQ)
{
guestRepository = repoG;
contactRepository = repoC;
qrcodeRepository = repoQ;
}
public ActionResult Index()
{
return View(guestRepository.Guests);
}
public ActionResult EditGuest(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
TableViewModel viewModel = new TableViewModel();
viewModel.GetGuest = guestRepository.Guests.FirstOrDefault(g => g.GuestID == id);
viewModel.GetContact = contactRepository.Contacts.FirstOrDefault(c => c.ContactID == id);
if (viewModel.GetGuest == null)
{
return HttpNotFound();
}
return View(viewModel);
}
public ActionResult GuestForm(int? id)
{
var viewModel = new TableViewModel();
viewModel.GetGuest = guestRepository.Guests.FirstOrDefault(g => g.GuestID == id);
return PartialView("_GuestForm", viewModel.GetGuest);
}
[HttpPost]
public ActionResult GuestForm(TableViewModel getGuest)
{
if (ModelState.IsValid)
{
guestRepository.SaveGuest(getGuest.GetGuest);
qrcodeRepository.CreateQRCode(getGuest.GetGuest);
TempData["message"] = string.Format("Zapisano {0} {1}", getGuest.GetGuest.GuestFirstName, getGuest.GetGuest.GuestLastName);
return RedirectToAction("EditGuest/" + getGuest.GetGuest.GuestID);
}
else
{
return PartialView(getGuest.GetGuest);
}
}
public ActionResult ContactForm(int? id)
{
var viewModel = new TableViewModel();
viewModel.GetContact = contactRepository.Contacts.FirstOrDefault(c => c.ContactID == id);
return PartialView("_ContactForm", viewModel.GetContact);
}
[HttpPost]
public ActionResult ContactForm(TableViewModel getGuest)
{
if (ModelState.IsValid)
{
contactRepository.SaveContact(getGuest.GetContact);
TempData["message"] = string.Format("Zapisano {0} {1}", getGuest.GetGuest.GuestFirstName, getGuest.GetGuest.GuestLastName);
return RedirectToAction("EditGuest/" + getGuest.GetGuest.GuestID);
}
else
{
return PartialView(getGuest.GetContact);
}
}
public ActionResult Create()
{
return View("EditGuest", new TableViewModel());
}
My view
#model MSConference.WebUI.Models.TableViewModel
#{
if (Model.GetGuest.GuestEmail == null)
{
ViewBag.Title = "Tworzenie nowego użytkownika";
}
else
{
ViewBag.Title = "Edycja";
}
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
#if (Model.GetGuest.GuestEmail == null)
{
<h2>Tworzenie nowego użytkownika</h2>
}
else
{
<h2>Edycja - #Model.GetGuest.GuestFirstName #Model.GetGuest.GuestLastName</h2>
}
#using (Html.BeginForm("EditGuest", "Admin"))
{
#Html.AntiForgeryToken()
<div class="container">
<ul class="nav nav-pills">
<li class="active"><a data-toggle="pill" href="#EditGuest">Edycja - Gość</a></li>
<li><a data-toggle="pill" href="#EditContact">Edycja - Kontakt</a></li>
<li><a data-toggle="pill" href="#EditBill">Edycja - Rezerwacja</a></li>
<li><a data-toggle="pill" href="#EditPlan">Edycja - Konferencja</a></li>
</ul>
<div class="tab-content">
<div id="EditGuest" class="tab-pane fade in active">#Html.Partial("_GuestForm", new MSConference.WebUI.Models.TableViewModel())</div>
<div id="EditContact" class="tab-pane fade">#Html.Partial("_ContactForm", new MSConference.WebUI.Models.TableViewModel())</div>
<div id="EditBill" class="tab-pane fade">sgdg</div>
<div id="EditPlan" class="tab-pane fade">gsdgsgsgsg</div>
</div>
</div>
}
<div>
#Html.ActionLink("Powrót do Listy", "Index", null, new { #class = "btn btn-success" })
</div>
I tried every method of passing model I could find and understand
EDIT
Here are my Repositories. Create error comes from if (contact.ContactID == 0)
public class EFGuestRepository : IGuestRepository
{
private EfDbContext context = new EfDbContext();
public IEnumerable<Guest> Guests
{
get { return context.Guests; }
}
public void SaveGuest(Guest guest)
{
if (guest.GuestID == 0)
{
context.Guests.Add(guest);
}
else
{
Guest dbEntry = context.Guests.Find(guest.GuestID);
if (dbEntry != null)
{
dbEntry.GuestLastName = guest.GuestLastName;
dbEntry.GuestFirstName = guest.GuestFirstName;
dbEntry.GuestMiddleName = guest.GuestMiddleName;
dbEntry.GuestEmail = guest.GuestEmail;
dbEntry.GuestSex = guest.GuestSex;
}
}
context.SaveChanges();
}
public Guest DeleteGuest(int guestId)
{
Guest dbEntry = context.Guests.Find(guestId);
if (dbEntry != null)
{
context.Guests.Remove(dbEntry);
context.SaveChanges();
}
return dbEntry;
}
}
public class EFContactRepository : IContactRepository
{
private EfDbContext context = new EfDbContext();
public IEnumerable<Contact> Contacts
{
get { return context.Contacts; }
}
public void SaveContact(Contact contact)
{
if (contact.ContactID == 0)
{
contact.GuestID = contact.ContactID;
context.Contacts.Add(contact);
}
else
{
Contact dbEntry = context.Contacts.Find(contact.ContactID);
if (dbEntry != null)
{
contact.GuestID = contact.ContactID;
dbEntry.PostalCode = contact.PostalCode;
dbEntry.City = contact.City;
dbEntry.Street = contact.Street;
dbEntry.HouseNumber = contact.HouseNumber;
dbEntry.PhoneNumber = contact.PhoneNumber;
}
}
context.SaveChanges();
}
public Contact DeleteContact(int guestId)
{
Contact dbEntry = context.Contacts.Find(guestId);
if (dbEntry != null)
{
context.Contacts.Remove(dbEntry);
context.SaveChanges();
}
return dbEntry;
}
public interface IGuestRepository
{
IEnumerable<Guest> Guests { get; }
void SaveGuest(Guest guest);
Guest DeleteGuest(int guestId);
}
public interface IContactRepository
{
IEnumerable<Contact> Contacts { get; }
void SaveContact(Contact guest);
Contact DeleteContact(int guestId);
}
I've built whole project working with Adam Freeman pro asp.net mvc 5 book (SportsStore project).
Passing entities to the view isn't a good practice, and depending on what you do with them when they are returned from the view, this can expose you to data tampering. Your "TableViewModel" should just consist of the flattened fields from the guest and contact, or a GuestViewModel and ContactViewModel revealing only the keys and details you need to display/edit. Entities are designed to be associated to a DbContext. Putting a reference to them in a view model is orphaning them. When you pass them back to the controller, they become just POCO instances that are deserialized from the JSON data coming from the view. They have no change tracking etc. that you might expect from using entities while they're freshly loaded from a DbContext. You can attach them to a DbContext, but you would have to manually set the entity State to "Modified" otherwise the context does not know the entity has been changed.
Your issue as it stands right now will probably be in what your SaveGuest method is doing.
The typical MVC lifecycle for the data would be roughly:
View:
Load entity(ies) from context
Populate view models
Pass to view.
Update:
Validate view model against current session
Load entity(ies) from context based on keys
Check that view model isn't stale (last mod date / timestamp / row version matches)
Validate and copy across only details that can be updated from view model into entity
SaveChanges.
Chances are if you're not seeing changes, you're probably attaching the entity to the new context without setting the entity State to "Modified". Note that this is not recommended as you are unconditionally trusting the data coming from the client. For instance you may intend to only see that a user has modified data that you created controls for, but by attaching the entity, you leave the door open for the POST call to be intercepted or played back with any/all data on the entity being altered. You would need to load the existing entity anyways to validate that nothing that shouldn't have been changed had been changed. Another possibility is you could be reloading the entity without realizing, not copying the values across from your view model's entity before calling SaveChanges, or adding the entity to the context thinking it would update the existing row, but it is saving a completely new row with new PK.
I solved my problem by replacing
#Html.Partial("PartialView", Model)
With
#{ Html.RenderPartial("PartialView", Model);}
I also rebuilded my models
Now my entity models looks like this:
[Table("GuestsTest")]
public class Guest
{
[Key]
public int GuestID { get; set; }
public string GuestLastName { get; set; }
public string GuestFirstName { get; set; }
public string GuestMiddleName { get; set; }
public string GuestEmail { get; set; }
public string GuestSex { get; set; }
[Required]
public virtual Contact Address { get; set; }
}
[Table("ContactsTest")]
public class Contact
{
public int ContactID { get; set; }
[Key, ForeignKey("Guest")]
public int GuestID { get; set; }
public int PostalCode { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string HouseNumber { get; set; }
public string PhoneNumber { get; set; }
public virtual Guest Guest { get; set; }
}
And my view models got full rebuild to this:
public class TableViewModel
{
public GuestViewModel GetGuest { get; set; }
public ContactViewModel GetContact { get; set; }
}
public class GuestViewModel
{
[Key]
[HiddenInput(DisplayValue = false)]
public int? GuestID { get; set; }
[MaxLength(50)]
[Required(ErrorMessage = "Proszę podać nazwisko.")]
[Display(Name = "Nazwisko")]
public string GuestLastName { get; set; }
[MaxLength(50)]
[Required(ErrorMessage = "Proszę podać imię.")]
[Display(Name = "Imię")]
public string GuestFirstName { get; set; }
[MaxLength(50)]
[Display(Name = "Drugie imię")]
public string GuestMiddleName { get; set; }
[MaxLength(50)]
[Required(ErrorMessage = "Proszę podać adres email.")]
[RegularExpression(".+\\#.+\\..+", ErrorMessage = "Proszę podać prawidłowy adres e-mail.")]
[Display(Name = "Email")]
public string GuestEmail { get; set; }
[MaxLength(1)]
[Required(ErrorMessage = "Proszę podać płeć.")]
public string GuestSex { get; set; }
}
public class ContactViewModel
{
[HiddenInput(DisplayValue = false)]
public int ContactID { get; set; }
[Key, ForeignKey("Guest")]
[HiddenInput(DisplayValue = false)]
public int GuestID { get; set; }
[Required(ErrorMessage = "Proszę podać kod pocztowy.")]
[Display(Name = "Kod pocztowy")]
public int PostalCode { get; set; }
[Required(ErrorMessage = "Proszę podać Miejscowość.")]
[Display(Name = "Miejscowość")]
public string City { get; set; }
[Required(ErrorMessage = "Proszę podać ulicę.")]
[Display(Name = "Ulica")]
public string Street { get; set; }
[Required(ErrorMessage = "Proszę podać numer domu/mieszkania.")]
[Display(Name = "Numer domu/mieszkania")]
public string HouseNumber { get; set; }
[Required(ErrorMessage = "Proszę podać numer telefonu.")]
[Display(Name = "Numer telefonu")]
public string PhoneNumber { get; set; }
}
Last, I overloaded my save function to work with new models
Again...
I am doing a MVC with EF5 App. I have a Users Entity, that EF bind with Users table in Database... Looks like this.
public partial class Users
{
public long User_id { get; set; }
[Required]
[StringLength(30, ErrorMessage = "LastName cannot be longer than 30 characters.")]
public string LastName { get; set; }
[Required]
[StringLength(30, ErrorMessage = "Name cannot be longer than 30 characters.")]
public string Name { get; set; }
public int ProcessState_id { get; set; }
public string Sex { get; set; }
[Required,Range(1, int.MaxValue, ErrorMessage = "El País es Obligatorio")]
public int Country_id { get; set; }
[Required]
[EmailAddress(ErrorMessage = "Invalid Email Address")]
public string Email { get; set; }
public System.DateTime CreationDate { get; set; }
public Nullable<System.DateTime> UpDateTime { get; set; }
[RegularExpression(#"^.{5,}$", ErrorMessage = "Minimum 3 characters required")]
[Required]
[StringLength(9, MinimumLength = 3, ErrorMessage = "Password cannot be longer than 9 characters.")]
public string Password { get; set; }
public string Url { get; set; }
public byte[] Picture { get; set; }
public string CodArea { get; set; }
public string PhoneNumber { get; set; }
public virtual Countries Countries { get; set; }
public virtual ProcessStates ProcessStates { get; set; }
public virtual States States { get; set; }
[NotMapped] // Does not effect with your database
[RegularExpression(#"^.{5,}$", ErrorMessage = "Minimum 3 characters required")]
[StringLength(9, MinimumLength = 3, ErrorMessage = "Confirm Password cannot be longer than 9 characters.")]
[Compare("Password")]
public virtual string ConfirmPassword { get; set; }
}
I have a Model Class that i use it in my Create View....
public class UserViewModel
{
public Users user { get; set; }
public IList<SelectListItem> AvailableCountries { get; set; }
}
My Create Method in the Controller gets a UserViewModel instance...
My Create Method looks like this.
public async Task<ActionResult> Create(UserViewModel model, System.Web.HttpPostedFileBase image = null)
{
try
{
if (ModelState.IsValid)
{
model.user.ProcessState_id = Security.WAITING;
model.user.Rol_id = Security.ROL_PUBLIC;
model.user.CreationDate = DateTime.Now;
model.user.IP = Request.UserHostAddress;
model.user.Url = UserValidation.EncriptacionURL(model.user.Email);
if (image != null)
{
// product.ImageMimeType = image.ContentType;
model.user.Picture= new byte[image.ContentLength];
image.InputStream.Read(model.user.Picture, 0, image.ContentLength);
}
_db.Users.Add(model.user);
_db.SaveChanges();
return RedirectToAction("Create");
}
model.AvailableCountries = GetCountries();
return View(model);
}
catch (RetryLimitExceededException /* dex */)
{
}
return View(model);
}
So far so good.
For my Edit View, i need less properties from User class, so I have a new class with the properties I need. This class is called UserEditView.
public class UserEditView
{
public long User_id { get; set; }
[Required]
[StringLength(30, ErrorMessage = "LastName cannot be longer than 30 characters.")]
public string LastName { get; set; }
[Required]
[StringLength(30, ErrorMessage = "Name cannot be longer than 30 characters.")]
public string Name { get; set; }
[Required, Range(1, int.MaxValue, ErrorMessage = "El País es Obligatorio")]
public int Country_id { get; set; }
[Required]
[EmailAddress(ErrorMessage = "Invalid Email Address")]
public string Email { get; set; }
public Nullable<System.DateTime> UpDateTime { get; set; }
public byte[] Picture { get; set; }
public string CodArea { get; set; }
public string PhoneNumber { get; set; }
public virtual Countries Countries { get; set; }
}
I also create a new Model for Edit View, called UserEditViewModel and looks like this.
public class UserEditViewModel
{
public UserEditView user { get; set; }
public IList<SelectListItem> AvailableCountries { get; set; }
}
On my Edit method, I use Mapper to bind User entity with UserEditView
public ViewResult Edit(int User_id=3)
{
Users users = _db.Users
.FirstOrDefault(p => p.User_id == User_id);
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Users, UserEditView>();
});
IMapper mapper = config.CreateMapper();
UserEditView userEditView = mapper.Map<Users, UserEditView>(users);
var model = new UserEditViewModel
{
user = userEditView,
AvailableCountries = GetCountries(),
};
return View(model);
}
My problem arise when I want to Update the User table.
The Edit method gets UserEditViewModel instance.
public async Task<ActionResult> Edit(UserEditViewModel model, System.Web.HttpPostedFileBase image = null)
{
try
{
if (ModelState.IsValid)
{}
}
}
UserEditViewModel has an instance of UserEditView but I need an instance of Users in order to EF updates Users Table.
I need to Map again?
How can I get a Users Instance?
I add the following Class
public static class AutoMapperBootStrapper
{
public static void BootStrap()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Users, UserEditView>();
cfg.CreateMap<UserEditView, Users>();
});
IMapper mapper = config.CreateMapper();
}
And I add in my Global.asax
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AutoMapperBootStrapper.BootStrap();
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
then in the controller... i do
public ViewResult Edit(int User_id=3)
{
Users users = _db.Users.FirstOrDefault(p => p.User_id == User_id);
UserEditView userEditView = Mapper.Map<Users, UserEditView>(users);
}
But Mapper.Map have an error... it says Mapper is not instantiated.
the problem is because I defined more than one Mapper. If i define just one, it Works fine...
I need to Map again? How can I get a Users Instance?
You could get the User model from your database using the id and then map the properties that you need to be updated from the view model:
[HttpPost]
public ActionResult Edit(UserEditViewModel model, HttpPostedFileBase image = null)
{
if (!ModelState.IsValid)
{
// Validation failed => redisplay the Edit form so that the
// user can correct the errors
return View(model);
}
var user = _db.Users.FirstOrDefault(p => p.User_id == model.user.User_id);
if (user == null)
{
// no user with the specified id has been found in the database =>
// there's nothing to update
return NotFound();
}
// This will map only the properties of the user object that
// are part of the view model
Mapper.Map<Users, UserEditView>(model.user, user);
// at this stage you could manually update some properties that
// have not been mapped such as the uploaded image
// finally persist the changes to the database
_db.SaveChanges();
// redirect to some other action to show the updated users
return RedirectToAction("users");
}
Also the code you have shown in your question:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Users, UserEditView>();
});
IMapper mapper = config.CreateMapper();
This is absolutely NOT something that you should be doing inside a controller action. AutoMapper mappings should be configured only once per application lifetime, ideally when your application starts, i.e. for a web application that would be Application_Start in Global.asax. In a controller action you should only use the already configured mappings. I strongly recommend you going through the AutoMapper's documentation for getting better understanding of how to use this framework.
Quote from the documentation:
Where do I configure AutoMapper?
If you're using the static Mapper method, configuration should only
happen once per AppDomain. That means the best place to put the
configuration code is in application startup, such as the Global.asax
file for ASP.NET applications. Typically, the configuration
bootstrapper class is in its own class, and this bootstrapper class is
called from the startup method. The bootstrapper class should call
Mapper.Initialize to configure the type maps.
I am trying to insert a product into my database with an associated category. One product can belong to several categories and obviously one category can have several products. When I insert, I am sure I am missing something in my controller method but I'm not sure what it is. I have a bridge table called ProductCategory that just has a ProductID and a CategoryID in it. That table is not getting populated when I do the insert.
Here is my controller method that is doing the insert:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult EditProduct([Bind(Include = "ID,itemNumber,product,description,active,PDFName,imageName,SelectedCategories")] ProductModel model)
{
if (ModelState.IsValid)
{
using (var context = new ProductContext())
{
context.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
if (model.ID == 0)
{
// Since it didn't have a ProductID, we assume this
// is a new Product
if (model.description == null || model.description.Trim() == "")
{
model.description = "Our Famous " + model.product;
}
if (model.imageName == null || model.imageName.Trim() == "")
{
model.imageName = model.itemNumber + ".jpg";
}
if (model.PDFName == null || model.PDFName.Trim() == "")
{
model.PDFName = model.itemNumber + ".pdf";
}
Session["dropdownID"] = model.ID;
// I think I probably need some additional code here...
context.Products.Add(model);
}
else
{
// Since EF doesn't know about this product (it was instantiated by
// the ModelBinder and not EF itself, we need to tell EF that the
// object exists and that it is a modified copy of an existing row
context.Entry(model).State = EntityState.Modified;
}
context.SaveChanges();
return RedirectToAction("ControlPanel");
}
}
return View(model);
}
And my Product model:
public class ProductModel
{
public int ID { get; set; }
[Required(ErrorMessage = "Required")]
[Index("ItemNumber", 1, IsUnique = true)]
[Display(Name = "Item #")]
public int itemNumber { get; set; }
[Required(ErrorMessage = "Required")]
[Display(Name = "Product")]
[MaxLength(50)]
public String product { get; set; }
[Display(Name = "Description")]
[MaxLength(500)]
public String description { get; set; }
[DefaultValue(true)]
[Display(Name = "Active?")]
public bool active { get; set; }
[Display(Name = "Image Name")]
public String imageName { get; set; }
[Display(Name = "PDF Name")]
public String PDFName { get; set; }
[Display(Name = "Category(s)")]
public virtual ICollection<CategoryModel> ProductCategories { get; set; }
public int[] SelectedCategories { get; set; }
public IEnumerable<SelectListItem> CategorySelectList { get; set; }
//public ICollection<CategoryModel> CategoryList { get; set; }
public virtual BrochureModel Brochure { get; set; }
public IEnumerable<SelectListItem> BrochureList { get; set; }
[Display(Name = "Category(s)")]
public String CategoryList { get; set; }
public static IEnumerable<SelectListItem> getCategories(int id = 0)
{
using (var db = new ProductContext())
{
List<SelectListItem> list = new List<SelectListItem>();
var categories = db.Categories.ToList();
foreach (var cat in categories)
{
SelectListItem sli = new SelectListItem { Value = cat.ID.ToString(), Text = cat.categoryName };
//if (id > 0 && cat.ID == id)
//{
// sli.Selected = true;
//}
list.Add(sli);
}
return list;
}
}
public ProductModel()
{
active = true;
}
}
And my Category model:
public class CategoryModel
{
public int ID { get; set; }
[Required(ErrorMessage = "Required")]
[Display(Name = "Category Name")]
[MaxLength(50)]
public String categoryName { get; set; }
[MaxLength(50)]
public String categoryDBName { get; set; }
[DefaultValue(true)]
[Display(Name = "Active?")]
public bool isActive { get; set; }
//public virtual ICollection<ProductCategory> ProductCategories { get; set; }
public virtual ICollection<ProductModel> Products { get; set; }
}
Here is my Product context:
public class ProductContext : DbContext
{
public ProductContext()
: base("DefaultConnection")
{
Database.SetInitializer<ProductContext>(new CreateDatabaseIfNotExists<ProductContext>());
}
public DbSet<CategoryModel> Categories { get; set; }
public DbSet<ProductModel> Products { get; set; }
public DbSet<BrochureModel> Brochures { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
//modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
//modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Entity<CategoryModel>().ToTable("Categories");
modelBuilder.Entity<ProductModel>().ToTable("Products");
modelBuilder.Entity<BrochureModel>().ToTable("Brochures");
modelBuilder.Entity<ProductModel>()
.HasMany(p => p.ProductCategories)
.WithMany(p => p.Products)
.Map(m =>
{
m.ToTable("ProductCategory");
m.MapLeftKey("ProductID");
m.MapRightKey("CategoryID");
});
//modelBuilder.Entity<CategoryModel>()
//.HasMany(c => c.ProductCategories)
//.WithRequired()
//.HasForeignKey(c => c.CategoryID);
}
public System.Data.Entity.DbSet<newBestPlay.Models.RegisterViewModel> RegisterViewModels { get; set; }
}
Let me know if other code or more info is needed.
You're never doing anything with your SelectedCategories array. You need to use this to pull CategoryModel instance from the database and then associate those with the product.
context.Categories.Where(c => model.SelectedCategories.Contains(c.ID)).ToList()
.ForEach(c => model.ProductCategories.Add(c));
...
context.SaveChanges();
UPDATE
Can I ask how to list out the categories for each product in my view?
That's kind of a loaded question, as it's highly dependent on what type of experience you're trying to achieve. Generally speaking, with any collection, you'll need to iterate over the items in that collection and then render some bit of HTML for each item. You can do this in a number of different ways, which is why there's not really one "right" answer I can give you. However, just to give you an idea and not leave you with no code at all, here's a very basic way to just list out the name of every category:
#string.Join(", ", Model.ProductCategories.Select(c => c.categoryName))
I'm having trouble getting validation to work on my MVC page when using Entity Framework. If someone could point out what I'm doing wrong I would appreciate it. It is definitely seeing the meta data because the labels are working. However, when I hit submit, it just continues on to the next page. Could it have something to do with the fact that I have an instance of a class inside the view model?
Meta Data Class
[MetadataType(typeof(CompanyMetaData))]
public partial class Company
{
}
[MetadataType(typeof(CompanyUserMetaData))]
public partial class CompanyUser
{
}
public class CompanyMetaData
{
[Required(ErrorMessage = "Company Name is required")]
public string Name { get; set; }
[Required(ErrorMessage = "Service Center is required")]
public string ServiceCenterCode { get; set; }
[Required(ErrorMessage = "Account Number is required")]
public string AccountNumber { get; set; }
[Required(ErrorMessage = "Edition is required")]
public string Edition { get; set; }
}
public class CompanyUserMetaData
{
[Required]
[RegularExpression(#"^\w+#[a-zA-Z_]+?\.[a-zA-Z]{2,3}$", ErrorMessage = "Invalid Email Address")]
public string EmailAddress { get; set; }
[Required(ErrorMessage = "Password is required")]
public string Password { get; set; }
[Required(ErrorMessage = "First Name is required")]
public string FirstName { get; set; }
[DisplayName("Last Name")]
[Required(ErrorMessage = "Last Name is required")]
public string LastName { get; set; }
}
View Model
public class CreateCompanyViewModel : ILayoutAwareViewModel
{
public List<AdvisorServiceCenterVW> ServiceCenters { get; set; }
public LayoutViewModel LayoutViewModel { get; set; }
public Company newCompany { get; set; }
public CompanyUser newUser { get; set; }
public List<FuneralHome> newFuneralHomes { get; set; }
}
Markup Sample
<div class="form-group">
<label>#Html.LabelFor(d=>d.newUser.LastName)</label>
<div class="controls">
#Html.TextBoxFor(d => d.newUser.LastName, new { #class = "form-control" })
#Html.ValidationMessageFor(d => d.newUser.LastName)
</div>
</div>
Controller
public ActionResult CreateCompanyLocations(CreateCompanyViewModel incomingModel)
{
var model = (CreateCompanyViewModel)TempData["model"];
LayoutViewModel lvm = _layoutHelper.GetLayoutViewModelData("Configure New Company");
model.LayoutViewModel = lvm;
model.newCompany = incomingModel.newCompany;
model.newUser = incomingModel.newUser;
var fhs = _siteService.GetCustomerLocations(model.newCompany.AccountNumber);
model.newFuneralHomes = new List<FuneralHome>();
foreach (var fh in fhs)
{
model.newFuneralHomes.Add(new FuneralHome()
{
Address = fh.Address,
Name = fh.CustomerName,
City = fh.City,
AccountNumber = fh.AccountNumber,
ServiceCenterCode = fh.ServiceCenterCode,
State = fh.State,
ZipCode = fh.ZipCode,
Phone = fh.Phone,
ContactName = fh.ContactName
});
}
TempData["model"] = model;
return View(model);
}
You need to check ModelState.IsValid in your controller code and branch accordingly. Currently your controller is just processing the model whether it is valid or not. The typical pattern looks something like this:
if(ModelState.IsValid)
{
// Do stuff for when model is valid
}
else
{
// return the view with the invalid model to give the user
// a chance to fix it
return View(model);
}
It wound up having nothing to do with the above answer. I was missing the jquery validation and jquery unobtrusive scripts on my layout page so that is what was causing the validation not to fire. You do NOT need to do anything in the controller for this to work correctly.
I'm trying to make use of the IValidatableObject as described here http://davidhayden.com/blog/dave/archive/2010/12/31/ASPNETMVC3ValidationIValidatableObject.aspx.
But it just wont fire when I'm trying to validate, the ModelState.IsValid is always true.
Here is my model code:
[MetadataType(typeof(RegistrationMetaData))]
public partial class Registration : DefaultModel
{
[Editable(false)]
[Display(Name = "Property one")]
public int PropertyOne { get; set; }
}
public class RegistrationMetaData :IValidatableObject
{
[Required(ErrorMessage = "Customer no. is required.")]
[Display(Name = "Customer no.")]
public string CustomerNo { get; set; }
[Display(Name = "Comments")]
public string Comments { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (new AccountModel().GetProfile(CustomerNo) == null)
yield return new ValidationResult("Customer no. is not valid.", new[] { "CustomerNo" });
}
}
I extend a LINQ to SQL table called Registration, my first guess was that this is not possible to do on a Meta class, but I'm not sure?
I do not get any errors, and it builds just fine, but the Validate method will not fire. What have I missed?
That's because it is the Registration model that should implement IValidatableObject and not RegistrationMetaData:
[MetadataType(typeof(RegistrationMetaData))]
public partial class Registration : IValidatableObject
{
[Editable(false)]
[Display(Name = "Property one")]
public int PropertyOne { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (new AccountModel().GetProfile(CustomerNo) == null)
yield return new ValidationResult("Customer no. is not valid.", new[] { "CustomerNo" });
}
}
public class RegistrationMetaData
{
[Required(ErrorMessage = "Customer no. is required.")]
[Display(Name = "Customer no.")]
public string CustomerNo { get; set; }
[Display(Name = "Comments")]
public string Comments { get; set; }
}