AutoMapper and required fields - asp.net-mvc

I am trying to create a new record using only some of the fields in my Domain Model. I have created a ViewModel for this and am using AutoMapper.
My code is failing at the minute due to the required fields that I have on my Domain Model. I have added opt=>opt.Ignore() on the necessary field however, I am still having problems.
When I remove [Required] from the StaffPresent field the record is added to the DB.
In my Global.asax.cs
Mapper.CreateMap<CustomerSupportRecord, CustomerSupportRecordForCreation>();
Mapper.CreateMap<CustomerSupportRecordForCreation, CustomerSupportRecord>().ForMember(p=>p.StaffPresent, opt=>opt.Ignore());
Domain Model
public class CustomerSupportRecord
{
public int CustomerSupportRecordID { get; set; }
[Required]
public int CustomerID { get; set; }
[Required]
public string EmployeeID { get; set; }
[Required(ErrorMessage = "Please enter a Date")]
[DataType(DataType.Date)]
[Display(Name = "Date")]
public DateTime Date { get; set; }
[Required(ErrorMessage = "Please select an Arrival Time")]
[DataType(DataType.Time)]
[Display(Name = "Arrival")]
public DateTime ArrivalTime { get; set; }
[DataType(DataType.Time)]
[Display(Name = "Departure")]
public DateTime? DepartureTime { get; set; }
[Required(ErrorMessage = "Please select a Type")]
[Display(Name = "Type")]
public int CustomerSupportTypeID { get; set; }
[Required(ErrorMessage = "Please enter the staff who were present at the Feedback")]
[Display(Name = "Staff Present at Feedback")]
public string StaffPresent { get; set; }
[Display(Name = "Setting")]
public string ReflectionSetting { get; set; }
[Display(Name = "Advisor")]
public string ReflectionAdvisor { get; set; }
[Display(Name = "Notes")]
public string Notes { get; set; }
[Display(Name = "Comments")]
public string Comments { get; set; }
// Navigation Properties
public virtual Customer Customer { get; set; }
public virtual CustomerSupportType CustomerSupportType { get; set; }
public virtual Employee Employee { get; set; }
}
ViewModel
public class CustomerSupportRecordForCreation
{
public int CustomerSupportRecordID { get; set; }
public int CustomerID { get; set; }
public string EmployeeID { get; set; }
[DataType(DataType.Date)]
[Display(Name = "Date")]
public DateTime Date { get; set; }
[DataType(DataType.Time)]
[Display(Name = "Arrival")]
public DateTime ArrivalTime { get; set; }
[Display(Name = "Type")]
public int CustomerSupportTypeID { get; set; }
[Display(Name = "Notes")]
public string Notes { get; set; }
}
And finally my Controller
//
// GET: /CustomerSupport/CustomerSupportRecord/Create
public ActionResult Create()
{
ViewBag.CustomerSupportTypeID = new SelectList(db.CustomerSupportType, "CustomerSupportTypeID", "CustomerSupportTypeName");
var model = new CustomerSupportRecordForCreation { CustomerID = 1, EmployeeID = "20213" };
return View("Create", model);
}
//
// POST: /CustomerSupport/CustomerSupportRecord/Create
[HttpPost]
public ActionResult Create(CustomerSupportRecordForCreation customersupportrecord)
{
if (ModelState.IsValid)
{
var newRecord = Mapper.Map<CustomerSupportRecordForCreation, CustomerSupportRecord>(customersupportrecord);
db.CustomerSupportRecord.Add(newRecord);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CustomerSupportTypeID = new SelectList(db.CustomerSupportType, "CustomerSupportTypeID", "CustomerSupportTypeName", customersupportrecord.CustomerSupportTypeID);
return View(customersupportrecord);
}

AutoMapper's configuration doesn't have anything to do with validation attributes, it just specifies the rules for how to map objects between each-other.
In your case, the mapping:
Mapper.CreateMap<CustomerSupportRecordForCreation, CustomerSupportRecord>().ForMember(p=>p.StaffPresent, opt=>opt.Ignore());
tells AutoMapper not to copy the StaffPresent property.
If your database model has a [Required] attribute, you'll still need to set that data elsewhere.
One thing to note based on your comment, it's a good idea to add validation to your ViewModels, as it prevents you from calling your database methods with improper data, gives you client side validation, and lets you enforce different constraints than your model might require.

Related

MVC Validation - Issue checking DB records for matching values before posting

Hi everyone I cant seem to figure out how to check a record before creating a new post in MVC
When a user creates a "SuperMember" I want it to check IF that "Character" has already been assigned that "membership" already aka a duplicate record. Each Character can have many memberships but not the same one.
Heres my code -
MODELS
public class Character
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CharacterID { get; set; }
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[Display(Name = "Hero Alias")]
public string HeroAlias { get; set; }
[Required]
[Display(Name = "Email Address")]
public string Email { get; set; }
public ICollection<SuperMembers> SuperMembers { get; set; }
}
public class Membership
{
[Key]
public int MembershipID { get; set; }
[Required]
[Display(Name = "Membership Name")]
public string MembershipName { get; set; }
[Required]
[Display(Name = "Membership Tyoe")]
public string Type { get; set; }
public ICollection<SuperMembers>SuperMembers { get; set; }
}
public class SuperMembers
{
[Key]
[Display(Name = "Membership Number")]
public int SuperMembersID { get; set; }
[Required]
[Display(Name = "Hero Alias")]
public int CharacterID { get; set; }
public Character Character { get; set; }
[Required]
[Display(Name = "Membership")]
public int MembershipID { get; set; }
public Membership Membership { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "money")]
[Required]
[Display(Name = "Account Balance")]
public decimal AccountBalance { get; set; }
}
Then the Controller POST Method
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("SuperMembersID,CharacterID,MembershipID,AccountBalance")] SuperMembers superMembers)
{
if (ModelState.IsValid)
{
var IfAlreadyExists = dbo.SuperMembers.Where(x => x.CharacterID == CharacterID && x.MembershipID == MembershipName).FirstOrDefault();
if (IfAlreadyExists == null)
{
//POST
}
else
{
//Return ERROR "Sorry this Membership already exists"
}
_context.Add(superMembers);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
ViewData["CharacterID"] = new SelectList(_context.Character, "CharacterID", "HeroAlias", superMembers.CharacterID);
ViewData["MembershipID"] = new SelectList(_context.Membership, "MembershipID", "MembershipName", superMembers.MembershipID);
return View(superMembers);
}
The dbo in "dbo.SuperMembers" is underlined too, im using a local db for this little practice project so not sure if thats also a factor?
Im fairly new to this, this is my first StackOverflow post so apologies if ive missed anything obvious - Would appreciate any pointers. Thanks

A specified Include path is not valid on eager loading

I am using eager loading in entity framework. My Model class is like
public class EmployeeMaster : AbsCommonParameters
{
[Key]
public int Id { get; set; }
public string EmpId { get; set; }
[Required(ErrorMessage = "Employee name is required")]
public string Name { get; set; }
[Required(ErrorMessage = "Employee geography is required")]
public string Geography { get; set; }
public string EmailId { get; set; }
[DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
[Column(TypeName = "Date")]
[Display(Name = "Date Of Joining")]
public Nullable<DateTime> DOJ { get; set; }
[Required(ErrorMessage = "Employee designation is required")]
public string Designation { get; set; }
[NotMapped]
public List<CenterMaster> lstCenters { get; set; }
[NotMapped]
public List<CostCodeMaster> lstCostCode { get; set; }
}
In Controller Index Action my Code is like this
public ActionResult Index()
{
var lstUser = db.ObjEmployeeMaster.Include(p => p.lstCenters).
Include(p => p.lstCostCode).Where(x => x.DeletedStatus == false).ToList();
return View(lstUser);
}
Also In DBContext Constructor I have used
this.Configuration.LazyLoadingEnabled = false;
After this I got error
A specified Include path is not valid
Please help where I am wrong.
Thanks in advance.

MVC Entity Framework Models and Relationships

im quite new to ASP.net and entity framework. I created a class but im just going to simplify it and take the example class from Microsoft. So here we go. These are all the example classes
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Instructor
{
public int ID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get { return LastName + ", " + FirstMidName; }
}
public virtual ICollection<Course> Courses { get; set; }
public virtual OfficeAssignment OfficeAssignment { get; set; }
}
public class OfficeAssignment
{
[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
public virtual Instructor Instructor { get; set; }
}
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Title { get; set; }
[Range(0, 5)]
public int Credits { get; set; }
public int DepartmentID { get; set; }
public virtual Department Department { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
public virtual ICollection<Instructor> Instructors { get; set; }
}
Like i said this is just for a example im trying to get know it.
My First Question: Do you need something like DepartmentID When you already do public virtual Department Department?
Second question: When i have a Collection of data how do i add values or Ids to that collection in the controller i can find this anywhere.
Third Question: Can some one link me a complete MVC example application with proper relationships

Pass model to controller and send result to page not using form submit

I have a page I am writing to send emails to customers. One of the ways to select customers to email is to select products they have. Each product has different detail information and you will also be able to choose specifics about the product to narrow down who you are emailing.
Because this is going to be complex I need to do the processing to come up with the customer email list on the controller side but it would be a nightmare to try to pull all the data form the controls to send to it manually.
I would like to use an AJAX call that on the controller side would get the model tied to the view, query the database, and send back the list of emails so on the view I can pop up outlook for them with the list of email addresses already populated. Because I need to go back to the view with data I don't think I can do this with a form post which is how I normally get model into into the controller.
Does someone know how I could accomplish this?
Here are some of the clases I have to try to help people understand my layout
public class ProductType
{
[HiddenInput(DisplayValue = false)]
public int ID { get; set; }
[Required(ErrorMessage = "Please enter a product type description")]
public string Description { get; set; }
public virtual ICollection<ProductTypeDetail> ProductDetails { get; set; }
}
public class ProductTypeDetail
{
[HiddenInput(DisplayValue = false)]
public int ID { get; set; }
[HiddenInput(DisplayValue = false)]
public int? ProductTypeID { get; set; }
public ProductType ProductType { get; set; }
[Required(ErrorMessage = "Please enter a description")]
public string Description { get; set; }
[Required(ErrorMessage = "Please enter a valid type")]
public string Type { get; set; }
public virtual ICollection<ProductTypeDetailValidValue> ValidValues { get; set; }
}
The above 2 classes are for product types which someone could enter anything for and as many as they want. The product details are detail information you might need to know about your products. For example you could have a product type of Vehicle Registration System and put a product detail item in that for a specific import process that pertains to the product they you need to know if they use or not.
public Customer()
{
SiteVisits = new List<SiteVisit>();
Payments = new List<Payment>();
Contacts = new List<CustomerEmail>();
}
[HiddenInput(DisplayValue = false)]
public int ID { get; set; }
[Display(Name = "Name")]
[Required(ErrorMessage = "Please enter a customer name")]
public string CustomerName { get; set; }
[Display(Name = "Line 1")]
public string Address1 { get; set; }
[Display(Name = "Line 2")]
public string Address2 { get; set; }
[Display(Name = "Line 3")]
public string Address3 { get; set; }
[Display(Name = "Line 4")]
public string Address4 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
[HiddenInput(DisplayValue = false)]
[Required(ErrorMessage = "Please enter a customer type")]
public int CustomerTypeID { get; set; }
[Display(Name = "Type")]
public virtual CustomerType CustomerType { get; set; }
[HiddenInput(DisplayValue = false)]
[Required(ErrorMessage = "Please enter a customer status")]
public int CustomerStatusID { get; set; }
[Display(Name = "Status")]
public virtual CustomerStatus CustomerStatus { get; set; }
[DataType(DataType.MultilineText)]
public string Comments { get; set; }
[Required(ErrorMessage = "Please enter an expiration year")]
public long ExpirationYear { get; set; }
[Required(ErrorMessage = "Please enter an expiration month")]
public long ExpirationMonth { get; set; }
[Required(ErrorMessage = "Please enter a control name")]
public string ControlName { get; set; }
public Boolean Networked { get; set; }
public long Population { get; set; }
[Display(Name = "First Fiscal Month")]
public long FirstMonth { get; set; }
[Display(Name = "FTP User Name")]
public string FTPUserName { get; set; }
[Display(Name = "FTP Password")]
public string FTPPassword { get; set; }
[Display(Name = "Customer ID")]
public string CustomerUpdateID { get; set; }
[DataType(DataType.Date)]
[Display(Name = "Customer Since")]
public DateTime? StartDate { get; set; }
public virtual ICollection<CustomerPhoneNumber> PhoneNumbers { get; set; }
public virtual ICollection<CustomerProduct> Products { get; set; }
public virtual ICollection<CustomerEmail> Contacts { get; set; }
public virtual ICollection<SiteVisit> SiteVisits { get; set; }
public virtual ICollection<Payment> Payments { get; set; }
}
public class CustomerProduct
{
[HiddenInput(DisplayValue = false)]
public int ID { get; set; }
[HiddenInput(DisplayValue = false)]
public int? ProductTypeID { get; set; }
public virtual ProductType ProductType { get; set; }
[HiddenInput(DisplayValue = false)]
public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
[HiddenInput(DisplayValue = false)]
public int? VersionID { get; set; }
public virtual ProductVersion Version { get; set; }
[HiddenInput(DisplayValue = false)]
public int? StatusID { get; set; }
public virtual ProductStatus Status { get; set; }
public virtual ICollection<CustomerProductDetail> ProductDetails { get; set; }
}
public class CustomerProductDetail
{
[HiddenInput(DisplayValue = false)]
public int ID { get; set; }
[HiddenInput(DisplayValue = false)]
public int CustomerProductID { get; set; }
public virtual CustomerProduct CustomerProduct { get; set; }
[HiddenInput(DisplayValue = false)]
public int ProductTypeDetailID { get; set; }
public virtual ProductTypeDetail ProductTypeDetail { get; set; }
//[Required(ErrorMessage = "Please enter a value")]
public string Value { get; set; }
}
So above I have the customer class. Each customer can be set up with any number of the product types you have set up and you can select values for the product details of that product type for this particular customer. The customers also contain contacts. that is the class that has the email addresses.
So I need to show a screen that displays all the product types you have set up and lets you select values for detail items on products you select then I need to query and find customers that match this
All I needed to do was serialize the form and pass it as data in my ajax call. If on the Controller side that is getting called I have an argument that is of the same type as the model my view is strongly typed to the model binder is smart enough to fill in my object automatically

One Model only update some fields in each view

I am wondering what is the correct way to approach this. I currently have one model - (shown below), which contains all the fields required for my record.
My issue is that when the record is created I only need to pass data for
CustomerID, EmployeeID, Date and ArrivalTime.
The remainder of the fields in the model will be populated when the record is updated at a later stage.
As some of my fields are required this will obviously cause validation errors if I don't post data for those fields.
I am wondering what is the best practice to achieve this?
Should I split the model into two?, or can I do partial validation?
public class CustomerSupportRecord
{
public int CustomerSupportRecordID { get; set; }
[Required]
public int CustomerID { get; set; }
[Required]
public string EmployeeID { get; set; }
[Required(ErrorMessage = "Please enter a Date")]
[DataType(DataType.Date)]
[Display(Name = "Date")]
public DateTime Date { get; set; }
[Required(ErrorMessage = "Please select an Arrival Time")]
[DataType(DataType.Time)]
[Display(Name = "Arrival")]
public DateTime ArrivalTime { get; set; }
[Required(ErrorMessage = "Please select a Departure Time")]
[DataType(DataType.Time)]
[Display(Name = "Departure")]
public DateTime DepartureTime { get; set; }
[Required(ErrorMessage = "Please select a Type")]
[Display(Name = "Type")]
public int CustomerSupportTypeID { get; set; }
[Display(Name = "Setting")]
public string ReflectionSetting { get; set; }
[Display(Name = "Advisor")]
public string ReflectionAdvisor { get; set; }
[Display(Name = "Notes")]
public string Notes { get; set; }
[Display(Name = "Comments")]
public string Comments { get; set; }
// Navigation Properties
public virtual Customer Customer { get; set; }
public virtual CustomerSupportType CustomerSupportType { get; set; }
public virtual Employee Employee { get; set; }
}
The correct approach would be to use different viewmodel classes for the different views and only include the properties you need on that view.
So your viewmodel for the first view look just like this:
public class CustomerSupportRecordForCreation
{
public int CustomerSupportRecordID { get; set; }
[Required]
public int CustomerID { get; set; }
[Required]
public string EmployeeID { get; set; }
[Required(ErrorMessage = "Please enter a Date")]
[DataType(DataType.Date)]
[Display(Name = "Date")]
public DateTime Date { get; set; }
[Required(ErrorMessage = "Please select an Arrival Time")]
[DataType(DataType.Time)]
[Display(Name = "Arrival")]
public DateTime ArrivalTime { get; set; }
}
You will have to map between that viewmodel classes and your domain/dal classes. This is where tools like AutoMapper comes in handy.
Edit Automapper:
Using Automapper is really simple.
You have to configure your mappings (i.e. in Application_Start). When the properties of the classes you want to map are named identically, its simple as this:
Mapper.CreateMap<CustomerSupportRecord,
CustomerSupportRecordForCreation>();
Then you can use the mapped in your app. When you have a CustomerSupportRecord and want to return the CustomerSupportRecordForCreation for your view write:
CustomerSupportRecord record = getRecordFromDb...
return View(Mapper.Map<CustomerSupportRecordForCreation>(record));
There is a good tutorial article on Codeproject: http://www.codeproject.com/Articles/61629/AutoMapper or just google

Resources