Automapper Missing Map Configuration - c#

I have an Attendee Class and An AttendeeViewModel
The datetime field on the Attendee Model gets set to the default .NET Datetime when i map it from AttendeeViewModel instead of the value that is already existing in the Attendee Model
Here's my AttendeeViewModel
public class AttendeeViewModel
{
public int Id { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last Name")]
public string LastName { get; set; }
public int FEventId { get; set; }
public string DisplayName
{
get { return string.Format("{0}, {1}", FirstName, LastName); }
}
}
Here's my Base AttendeeModel
public class Attendee
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string Email { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public DateTime CreatedAt { get; set; }
public bool IsActive { get; set; }
public int FEventId { get; set; }
public virtual ApplicationUser CreatedBy { get; set; }
public virtual FEvent FEvent { get; set; }
public ICollection<ProjectPledge> ProjectPledges { get; set; }
}
Here's My mapping configuration
public static void Configure()
{
Mapper.CreateMap<AttendeeViewModel, Attendee>().ForMember(dest=>dest.CreatedAt , opt=>opt.Ignore());
}
And heres's the Controller Action
[HttpPost]
[ValidateAntiForgeryToken]
public virtual ActionResult Edit(AttendeeViewModel attendee)
{
if (!_attendeeService.CanAddAttendee(attendee.Email, attendee.FilanthropyEventId))
{
AddEmailModelError();
}
if (ModelState.IsValid)
{
var mappedAttendee = _attendeeService.GetById(attendee.Id);
mappedAttendee = Mapper.Map<AttendeeViewModel, Attendee>(attendee);
_attendeeService.AddOrUpdate(mappedAttendee);
return RedirectToAction(MVC.Attendee.Index(mappedAttendee.FilanthropyEventId));
}
return View(attendee);
}
if I set the configuration to be this insetad of opt.Ignore()
Mapper.CreateMap<AttendeeViewModel, Attendee>().ForMember(dest=>dest.CreatedAt , opt=>opt.UseDestinationValue());
The Mapping fails giving this exception
Missing type map configuration or unsupported mapping.
Mapping types:
AttendeeViewModel -> DateTime
MyProject.Web.ViewModels.AttendeeViewModel -> System.DateTime
Destination path:
Attendee.CreatedAt.CreatedAt
Source value:
MyProject.Web.ViewModels.AttendeeViewModel
Any ideas on how i can resolve this?

If you want to map onto an existing object you need to use the overload that takes the existing destination:
Mapper.Map<Source, Destination>(source, destination);
that should do the trick.

Have you tried removing the ".ForMember" section and just let AutoMapper ignore it? In order to help you any more it would be helpful to see the two models for comparison.
Update: after lookind at your models I would suggest the following should solve the issue you are having...
Mapper.CreateMap <attendeeviewmodel, attendee>.ForMember (x => x.CreatedAt, opt => opt.MapFrom (src => datetime.utcnow));

Related

Cannot convert ViewModel to DataModel

I have a UserViewModel for my users. I want to use that for registration.
I do not want to use my datamodel for registration or login.
My UserViewModel is as below:
public class UserViewModel
{
public int user_id { get; set; } //Primary Key in user table
[Required]
[DisplayName("Email:")]
public string email { get; set; }
[Required]
[DisplayName("First Name:")]
public string f_name { get; set; }
[Required]
[DisplayName("Last Name:")]
public string l_name { get; set; }
[Required]
[DisplayName("Contact Number:")]
public string contact { get; set; }
[Required]
[DisplayName("Gender:")]
public string gender { get; set; }
[Required]
[DisplayName("Blood Type:")]
public string blood_type { get; set; }
[Required]
[DisplayName("Password:")]
[DataType(DataType.Password)]
public string password { get; set; }
[Required]
[DisplayName("Confirm Password:")]
[DataType(DataType.Password)]
[Compare("password")]
public string confirm_password { get; set; }
}
My Registration ActionMethod is as below:
[HttpPost]
public ActionResult Registration(UserViewModel uvmr)
{
db.users.Add(uvmr);
db.SaveChanges();
return View();
}
My dataModel for user(user.cs) is as below:
public partial class user
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public user()
{
this.appointments = new HashSet<appointment>();
}
public int user_id { get; set; }
public string f_name { get; set; }
public string l_name { get; set; }
public string email { get; set; }
public string contact { get; set; }
public string gender { get; set; }
public string blood_type { get; set; }
public string password { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<appointment> appointments { get; set; }
}
N.B: I have no Confirm Password column in my user table.
Now the error says cannot convert from das.Models.ViewModel.UserViewModel to das.Models.DataModel.user
What is work-around for this one?
You can't provide just any object instance to db.users.Add(uvmr); - the DbSet<T>.Add() method is typed to each of your data models.
Since your models are fairly similar, converting one to the other is relatively simple:
var newUser = new user
{
user_id = uvmr.user_id
f_name = uvmr.f_name
l_name = uvmr.l_name
email = uvmr.email
contact = uvmr.contact
gender = uvmr.gender
blood_type = uvmr.blood_type
password = uvmr.password
};
Then you can add the new user instance:
db.users.Add(newUser);
Assuming that you don't want to duplicate this code everytime you need to map one to the other, you can create a mapping utility - I tend to use a static class for that:
public static class Mapper
{
public static user MapUser(UserViewModel uvmr)
{
return uvmr == null ? null : new user
{
user_id = uvmr.user_id
f_name = uvmr.f_name
l_name = uvmr.l_name
email = uvmr.email
contact = uvmr.contact
gender = uvmr.gender
blood_type = uvmr.blood_type
password = uvmr.password
};
}
}
Then you could do something like:
var user = Mapper.MapUser(uvmr);
Of course, utilities like AutoMapper can do the same, and in your case might be simpler - AutoMapper, even without any setup code, will try to map properties with the same name.
As an aside, your class names and properties are currently violating Microsoft's naming conventions - I'd suggest reading the Naming Guidelines if this is code meant to shared outside of your organization.

Required attribute dependent on another attribute's value (ASP.NET Core Web Api)

I have a model class and I want to make on the parameter "required" only if the value of another parameter is something.
[JsonConverter(typeof(JsonStringEnumConverter))]
[Required]
public AddressTagEnum AddressTagId { get; set; }
[RequiredIf("AddressTagId", 3)]
[MaxLength(20)]
public string AddressTagOther { get; set; }
How can I achieve this?
Thanks in advance.
there are several ways to solve your problem:
there is no standard way to do this
there are librariers e.g. Expressive Annotations that should help
there is a built-in attribute Remote that lets you perform a server validation like: [Remote(action: "VerifyEmail", controller: "Users")] see docs
i prefer (i know that this is opinion based) a implementation with IValidatableObject. Citing the docs:
public class ValidatableMovie : IValidatableObject
{
private const int _classicYear = 1960;
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Title { get; set; }
[DataType(DataType.Date)]
[Display(Name = "Release Date")]
public DateTime ReleaseDate { get; set; }
[Required]
[StringLength(1000)]
public string Description { get; set; }
[Range(0, 999.99)]
public decimal Price { get; set; }
public Genre Genre { get; set; }
public bool Preorder { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
{
yield return new ValidationResult(
$"Classic movies must have a release year no later than {_classicYear}.",
new[] { nameof(ReleaseDate) });
}
}
}

ASP.NET MVC 5 Edit Action - How to write to Multiple DB Tables/Models

Is there any way to somehow combine the data from two models and THEN map them both to the same viewModel in the context of an edit action?
I have never had to update several tables at once in an edit action in ASP.NET MVC with Entity Framework 6.1.3. This is the layout:
I have a DB table called "Address" which has fields for StreetNumber, StreetName, City, State, ZipCode. It has a one-to-one relationship with another table called Bars. As in, a bar can only have one address and one address can only have one bar.
Because I am storing this data in two separate tables, I am having a very difficult time trying to successfully implement an Edit action which takes data from one form (BarForm) and should update both the Bar and Address database tables. See my code:
BarController
public ActionResult Edit(int id)
{
var bar = _context.Bars.SingleOrDefault(m => m.Id == id);
var address = _context.Addresses.SingleOrDefault(a => a.BarId == id);
//Make sure that the id actually exists:
if (bar == null)
{
return HttpNotFound();
}
var viewModel = Mapper.Map<Bar, BarFormViewModel>(bar, new BarFormViewModel());
if (address == null)
{
address = new Address();
}
Mapper.Map<Address, BarFormViewModel>(address, viewModel);
viewModel.IsNew = false;
return View("BarForm", viewModel);
}
[ValidateAntiForgeryToken]
public ActionResult Save(BarFormViewModel bar)
{
if (!ModelState.IsValid)
{
var viewModel = Mapper.Map<BarFormViewModel, BarFormViewModel>(bar, new BarFormViewModel());
viewModel.IsNew = false;
return View("BarForm", viewModel);
}
if (bar.Id == 0)
{
var newbar = Mapper.Map<BarFormViewModel, Bar>(bar);
newbar.LastUpdated = DateTime.UtcNow;
_context.Bars.Add(newbar);
var addressToAdd = Mapper.Map<BarFormViewModel, Address>(bar);
_context.Addresses.Add(addressToAdd);
}
else
{
var barInDb = _context.Bars.Single(b => b.Id == bar.Id);
var addressInDb = _context.Addresses.Single(a => a.BarId == bar.Id);
Mapper.Map<BarFormViewModel, Bar>(bar, barInDb);
Mapper.Map<BarFormViewModel, Address>(bar, addressInDb);
}
_context.SaveChanges();
return RedirectToAction("Index", "Bar");
}
Domain Models:
public class Bar
{
public int Id { get; set; }
public string Name { get; set; }
[Required]
public string GooglePlaceId { get; set; }
public string SundayDiscounts { get; set; }
public string MondayDiscounts { get; set; }
public string TuesdayDiscounts { get; set; }
public string WednesdayDiscounts { get; set; }
public string ThursdayDiscounts { get; set; }
public string FridayDiscounts { get; set; }
public string SaturdayDiscounts { get; set; }
[Display(Name = "Last Updated")]
public DateTime LastUpdated { get; set; }
}
public class Address
{
public int Id { get; set; }
public int? Number { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
public string State { get; set; }
[Required]
public int ZipCode { get; set; }
public Bar Bar { get; set; }
public int BarId { get; set; }
}
View Model which includes both Address and Bar properties:
{
public class BarFormViewModel
{
public int? Id { get; set; }
public string Name { get; set; }
[Required]
[Display(Name = "Google Place ID")]
public string GooglePlaceId { get; set; }
[Display(Name = "Sunday Happy Hour Info:")]
public string SundayDiscounts { get; set; }
[Display(Name = "Monday Happy Hour Info:")]
public string MondayDiscounts { get; set; }
[Display(Name = "Tuesday Happy Hour Info:")]
public string TuesdayDiscounts { get; set; }
[Display(Name = "Wednesday Happy Hour Info:")]
public string WednesdayDiscounts { get; set; }
[Display(Name = "Thursday Happy Hour Info:")]
public string ThursdayDiscounts { get; set; }
[Display(Name = "Friday Happy Hour Info:")]
public string FridayDiscounts { get; set; }
[Display(Name = "Saturday Happy Hour Info:")]
public string SaturdayDiscounts { get; set; }
[Display(Name = "Last Updated")]
public DateTime? LastUpdated { get; set; }
//Address Model Info
public Address Address { get; set; }
public int? AddressId { get; set; }
[RegularExpression("([1-9][0-9]*)", ErrorMessage = "Must be a number")]
public int? Number { get; set; }
public string StreetName { get; set; }
public string City { get; set; }
public string State { get; set; }
[Required]
public int? ZipCode { get; set; }
public bool IsNew { get; set; }
}
The problem here is that I am getting an empty AddressId with this setup, which is causing an exception when the Save action gets run. This is because the BarForm view is getting passed a ViewModel which has been mapped from a Bar object and the Bar domain model actually has no Address information in it, since it is not the Address model/table.
Is there any way to somehow combine the data from both the Address and Bar models and THEN map them both to the same viewModel?
I keep getting a Sequence Contains no Elements error for this line in the Save action:
var addressInDb = _context.Addresses.Single(a => a.Id == bar.AddressId);
I also tried:
var addressInDb = _context.Addresses.Single(a => a.BarId == bar.Id);
Neither work. I understand what the error is saying and have also checked the actual HTML for my hidden Addressid field and it is blank... See code in my BarForm View:
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.AddressId)
#Html.AntiForgeryToken()
Remove the new BarFormViewModel() as the second parameter in your mapping calls as it is not necessary.
In your post action, inside your if statement that checks if the ModelState is valid and if bar.Id == 0, bar is already a view model, so no need to mapping.
And when you create your AutoMapper mapping, you must create a custom property mapping because the Address.Id property will not map automatically to the AddressId property as the name is not the same.
AutoMapper.Mapper.CreateMap<Address, BarFormViewModel>()
.ForMember(dest => dest.AddressId, o => o.MapFrom(source => source.Id));
And then do the same for the inverse mapping.

Invalid Column Name Error When Accessing Navigation Properties From View

When calling a view which displays properties for ASP.NET's ApplicationUser class which I have extended, I receive the below error when the code tries to render a line where I dive into a ApplicationUser class:
#model BaseballStatTracker.Models.ApplicationUser
#foreach (var stats in Model.GameStatistics)
{
<tr>
<td>
#Html.DisplayFor(modelItem => stat.Game.GameTime)
</td>
</tr>
}
...causes:
Any property accessed from within the "Game" property causes the error, and Razor gives no indication that there is a problem when editing the view. Just accessing regular properties on the GameStatistics works without issue.
I have two contexts; the standard ApplicationDbContext and my GamesContext which houses both the Game and GameStatistics entities. I have overridden the OnModelCreating method on the GamesContext per the following:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Game>().HasKey(i => i.GameId);
modelBuilder.Entity<Game>().HasOptional(i => i.HomeTeam).WithMany(i => i.HomeGames).HasForeignKey(i => i.HomeTeamId);
modelBuilder.Entity<Game>().HasOptional(i => i.AwayTeam).WithMany(i => i.AwayGames).HasForeignKey(i => i.AwayTeamId);
modelBuilder.Entity<Game>().HasOptional(i => i.Diamond).WithMany(i => i.Games).HasForeignKey(i => i.DiamondId);
modelBuilder.Entity<GameStatistics>().HasKey(i => i.GameStatisticsId);
modelBuilder.Entity<GameStatistics>().HasRequired(i => i.Game).WithMany(i => i.GameStatistics).HasForeignKey(i => i.GameId);
modelBuilder.Entity<GameStatistics>().HasRequired(i => i.Player).WithMany(i => i.GameStatistics).HasForeignKey(i => i.PlayerId);
...
}
The ApplicationUser, Game and GameStatistics classes look like the following:
ApplicationUser:
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
[Required]
[Display(Name = "First Name")]
[StringLength(155, ErrorMessage = "First Name can only be max 155 characters in length.")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
[StringLength(155, ErrorMessage = "Last Name can only be max 155 characters in length.")]
public string LastName { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return FirstName + " " + LastName;
}
}
[Display(Name = "Player Number")]
public int PlayerNumber { get; set; }
[Display(Name = "Team")]
public virtual Guid TeamId { get; set; }
public virtual Team Team { get; set; }
public virtual ICollection<GameStatistics> GameStatistics { get; set; }
}
Game:
public class Game
{
[Key]
public Guid GameId { get; set; }
public string GameName { get
{
return AwayTeam.Name + " # " + HomeTeam.Name + ", " + GameDate.Value.Date.ToLongDateString();
} }
[DataType(DataType.Date)]
[Display(Name = "Game Date")]
public DateTime? GameDate { get; set; }
[DataType(DataType.Time)]
[Display(Name = "Game Time")]
public DateTime? GameTime { get; set; }
[Display(Name = "Game Location")]
public virtual Guid? DiamondId { get; set; }
public virtual Diamond Diamond { get; set; }
[Display(Name = "Home Team")]
public virtual Guid? HomeTeamId { get; set; }
public virtual Team HomeTeam { get; set; }
[Display(Name = "Away Team")]
public virtual Guid? AwayTeamId { get; set; }
public virtual Team AwayTeam { get; set; }
public virtual ICollection<GameStatistics> GameStatistics { get; set; }
}
GameStatistics:
public class GameStatistics
{
[Key]
public Guid GameStatisticsId { get; set; }
public Guid GameId { get; set; }
public virtual Game Game { get; set; }
public string PlayerId { get; set; }
public virtual ApplicationUser Player { get; set; }
public int AtBats { get; set; }
public int Hits { get; set; }
public int Walks { get; set; }
}
I have tried redoing the OnModelCreate method but I can't see what I have done wrong with the Game<-GameStatistics relationship. Any help would be much appreciated. Thank you in advance -
This issue was resolved after adding a couple of "Inverse Property" annotations to my Games model on the HomeTeam and AwayTeam properties. I also moved away from using the OnModelCreating override and switched to using the annotations completely. The relevant part of the change on the code was:
[Display(Name = "Home Team")]
public virtual Guid? HomeTeamId { get; set; }
[ForeignKey("HomeTeamId")]
[InverseProperty("HomeGames")] // Added this -
public virtual Team HomeTeam { get; set; }
[Display(Name = "Away Team")]
public virtual Guid? AwayTeamId { get; set; }
[ForeignKey("AwayTeamId")]
[InverseProperty("AwayGames")] // Added this -
public virtual Team AwayTeam { get; set; }
I am not sure why I was not able to achieve the same result setting the configuration in the data contexts - I am sure it was just a misunderstanding on my part but I would love to know why. Should EF not have known via the configuration what the inverse properties were?
I had tried changing the configuration setup but to no effect - I could not see anything wrong with it in the first place, however. Nevertheless, it is now working using the data annotations.

C# Trying to add items into database, getting error

I am trying to add this information into my database. My SQL Server will generate the Id for each row in the table. However, for some reason my code is adding a "0" for Id and I cannot figure out why or where it is coming from and I need to remove it so that the database can just generate it.
Here is my code:
public class Contact
{
public Contact()
{
}
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Required]
[StringLength(50)]
public string Email { get; set; }
[Column(TypeName = "ntext")]
[Required]
public string Message { get; set; }
[Column(TypeName = "date")]
[Required]
public DateTime Date { get; set; }
[Column(TypeName = "time")]
[Required]
public TimeSpan Time { get; set; }
}
public class Context : DbContext
{
public Context()
: base("ContactConnectionString")
{
}
public DbSet<Contact> ContactForm { get; set; }
}
public class ImportToDataBase
{
public static void SubmitToDatabase(string theMessage, string fullName, string emailAddress)
{
using (var db = new Context())
{
var contact = new Contact()
{
Email = emailAddress,
Message = theMessage,
Name = fullName,
Date = DateTime.Now.Date,
Time = DateTime.Now.TimeOfDay,
// ID is not set in here
};
db.ContactForm.Add(contact);
db.SaveChanges();
}
}
}
}
Try decorating your Id property with the following:
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
The Key attribute tells Entity Framework that the Id property is the primary key and should not be included in the insert statement.
As a best practice, you need to mark your id with Key and Identity Option.
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
For fluent API you can use as:
HasKey(i => i.ID);
Property(i => i.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

Categories

Resources