Map two identical models, nested with automapper - asp.net-core-mvc

I did some research but I couldn't find exactly what I wanted.
I have an endless menu. I have a MenuDTO and a MenuViewModel that I use for this menu. I had no problem matching between model and DTO, but am having trouble mapping DTO to ViewModel. Obviously I couldn't find the solution, can you help?
My MenuDTO Object
public class MenuDto : BaseDto
{
public string Name { get; set; }
public string Icon { get; set; }
public string Order { get; set; }
public string Url { get; set; }
public bool IsVisible { get; set; }
public int ParentId { get; set; }
public MenuDto ParentMenu { get; set; }
public List<MenuDto> Menus { get; set; }
}
And MenuViewModel
public class MenuViewModel
{
public int Id { get; set; }
public bool IsActive { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
public string Order { get; set; }
public string Url { get; set; }
public bool IsVisible { get; set; }
public int ParentId { get; set; }
public MenuViewModel ParentMenu { get; set; }
public List<MenuViewModel> Menus { get; set; }
}
This is how I mapped the MenuDTO and MenuViewModel objects.
public class WebProfile : Profile
{
public WebProfile()
{
CreateMap<MenuDto, MenuViewModel>();
CreateMap<MenuViewModel, MenuDto>();
}
}
I call this way in the controller
var navMenuItems = _mapper.Map<List<MenuViewModel>(_menuService.GetNavMenus());
Although all fields are mapped, I get an error on the Menus field.
The error message I get is;
AutoMapperMappingException: Missing type map configuration or unsupported mapping.
Mapping types:
MenuDto -> MenuViewModel
BiPortal2020.Business.ServiceDTOs.Menu.MenuDto -> BiPortal2020.WebUI.Areas.Admin.Models.Menu.MenuViewModel
lambda_method(Closure , MenuDto , MenuViewModel , ResolutionContext )
AutoMapperMappingException: Error mapping types.
Mapping types:
Object -> List`1
System.Object -> System.Collections.Generic.List`1

The error message implies - AutoMapper, either cannot map between MenuDto and MenuViewModel, or it cannot locate the defined mappings.
I've tested your mappings and they are totally fine. So, what possibility remains is AutoMapper cannot locate your mappings.
I'm Assuming the Business Layer and UI Layer you mentioned in the comment section are two separate projects. Since the WebProfile is defined in the UI Layer, you have to tell AutoMapper that it should search that assembly to find the mappings. Since your mappings between Models and DTOs are working, I can guess you've already done the same for BusinessProfile which is defined in the Business Layer.
I don't know about your existing code, but you could do something like this - in the Startup.Configure method add/modify the following line -
services.AddAutoMapper(typeof(IDtoMapping), typeof(IViewModelMapping));
where IDtoMapping and IViewModelMapping are two marker interface (empty interface, used only to identify the assembly they are declared in) declared in the Business Layer and UI Layer, respectively.

Related

AutoMapperConfigurationException: AutoMapper throwing exception without showing property name

I am creating POC using Asp.Net Web API. For mapping one object type to another i am using AutoMapper(v5.1.1). Here are the types which is being used for mapping:
//Entity
public class Goal : IVersionedEntity
{
public virtual int GoalId { get; set; }
public virtual string Title { get; set; }
public virtual string Description { get; set; }
public virtual DateTime StartDate { get; set; }
public virtual DateTime EndDate { get; set; }
public virtual string Reward { get; set; }
public virtual DateTime? DisabledDate { get; set; }
public virtual byte[] Version { get; set; }
public virtual User User { get; set; }
public virtual ICollection<Schedule> Schedules { get; set; }
}
//Model
public class Goal
{
private List<Link> _links;
public int GoalId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
//public Status Status { get; set; }
public string Reward { get; set; }
public DateTime? DisabledDate { get; set; }
public User User { get; set; }
public ICollection<Schedule> Schedules { get; set; }
public List<Link> Links
{
get { return _links ?? (_links = new List<Link>()); }
set { _links = value; }
}
public void AddLink(Link link)
{
_links.Add(link);
}
}
I am mapping Goal Entity to Goal model type object as following:
public async System.Threading.Tasks.Task Configure()
{
Mapper.Initialize(cfg => cfg.CreateMap<Data.Entities.Goal, Models.Goal>()
.ForMember(m => m.Links, i => i.Ignore()));
}
and here is the 'AutoMapperConfigurator' class in 'App_Start':
public void Configure(IEnumerable<IAutoMapperTypeConfigurator> autoMapperTypeConfigurations)
{
autoMapperTypeConfigurations.ToList().ForEach(m => m.Configure());
Mapper.AssertConfigurationIsValid();
}
But it is throwing following exception:
The following property on TestApp.Web.Api.Models.Goal cannot be
mapped: Add a custom mapping expression, ignore, add a custom
resolver, or modify the destination type TestApp.Web.Api.Models.Goal.
Context: Mapping from type TestApp.Data.Entities.Goal to
TestApp.Web.Api.Models.Goal Exception of type
'AutoMapper.AutoMapperConfigurationException' was thrown.
See it's not showing which property is not getting mapped.
Any help for this isssue.
After spending hours on this, my final findings are follows:
You must have all your entity models and service models mapping correct to make it work. Even if one fails, the mentioned exception will be thrown. And if your complex type mappings are not correct you will get the above error.
In my case, I was missing how to configure the Complex Type with AutoMapper.
To configure Complex Type, either add .ForMember(m => m.Property, i => i.Ignore()) to ignore the complex type mapping if not needed or .ForMember(m => m.Property, i => i.MapFrom(j => Mapper.Map<Entity,ServiceModel>(j.Property))) for nested mapping (refer: http://www.softwarerockstar.com/2011/05/complex-object-mapping-using-automapper/) or use CustomMapping if there is come specific requirement during the mapping

Bind attribute or ViewModel?

I'm learning about the Bind attribute and I have a doubt.
I can use the Bind attribute to include/exclude the data that will be posted, so.
Would it not be better to use a specific ViewModel instead of the Bind attribute?
Think about what happened if your entity changes overtime, then you might force to change all your different viewModels which you have created instead of using Include or Exclude. it will get hard to maintain your code.
Suppose you have this :
public class PersonalViewModel
{
private int PersonalID { get; set; }
public string PersonalName { get; set; }
public string PersonalFamily { get; set; }
public byte? GenderID { get; set; }
public string PersonalPhone { get; set;}
}
Consider these :
public string ShowPersonalToAll(
[Bind(Exclude = "PersonalPhone")]PersonalViewModel newPersonal)
{...}
OR
public class PersonalViewModel
{
private int PersonalID { get; set; }
public string PersonalName { get; set; }
public string PersonalFamily { get; set; }
public byte? GenderID { get; set; }
}
Now What if saving personal's mobile become important! and if you have created different customized ViewModel for several action (depends on application's business)?
Then you have to change the main ViewModel and all the other Customize ViewModel, While by using Exclude there is no need to change ViewModels, no need to change actions and the main ViewModel just changes.

scaffolding seems not to work properly

I created a new asp.net MVC3 application (internet application), and then I added a new model with 3 classes:
public class BizCard
{
[Required]
public string BizCardID { get; set; }
[Required]
public string Name { get; set; }
public string Address { get; set; }
public List<string> PhoneNumbers { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
public BizType type { get; set; }
public List<BizService> OfferedServices { get; set; }
public string Description { get; set; }
}
public class BizType
{
public int BizTypeID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double Price { get; set; }
}
public class BizService
{
public int BizServiceID { get; set; }
public List<BizType> AllowedBizTypes { get; set; }
public string Name { get; set; }
}
After that, I created a new controller, using the template "Controller with read/write actions and views using entity framework", I set the Model class to be "BizCard" and the data context class to be a new class which is called "BizDB". I was expecting to get a new class named BizDB that inherits from DbContext and includes 3 instances of DbSet:
DbSet<BizCard>, DbSet<BizType>, DbSet<BizService>.
In spite of that, I get the class with only one:
DbSet<BizCard>.
Am I missing something?
You are doing this using EF Code First approach.
1. So, you have to create a context class which should inherit DbContext containing required models as DbSet
2. Build the solution. Otherwise it will not be displayed at controller creation
Then you can create the controller using necessary model and its dbcontext.

What is the proper sequence of method calls when using a multi layered architecture?

I have built a simple survey-tool using MVC 3 with only 1 layer (MVC). I regret this now. All my database access and mapping is handled in the controllers, and some other mapping classes.
I would like to switch over to using three layers:
Presentation (MVC)
Business Logic
Data / Persistence (EF)
I am using the Entity Framework to handle everything with the database. The entity framework creates it's own domain classes. Where should the mapping between the Models that MVC uses and the models that EF creates go?
If the mapping is in the business layer, is there a need for the Models folder in the MVC project?
A survey-question consists of the Question itself, Rows and Columns. Theese are the models that i use:
public class Question {
public int Question_ID { get; set; }
public Boolean Condition_Fullfilled;
[Required(ErrorMessage = "Dette felt er påkrævet")]
public String Question_Wording { get; set; }
public String Question_Type { get; set; }
[Required(ErrorMessage = "Dette felt er påkrævet")]
public String Question_Order { get; set; }
public String Left_scale { get; set; }
public String Right_scale { get; set; }
public int Scale_Length { get; set; }
public String Left_Scale_HelpText { get; set; }
public String Right_Scale_HelpText { get; set; }
public Boolean Visible { get; set; }
public Boolean IsAnswered { get; set; }
public String Question_HelpText { get; set; }
public int Category_ID { get; set; }
}
public class MatrixColumns
{
public int Column_ID { get; set; }
public int Column_Number { get; set; }
public String Column_Description { get; set; }
public Boolean IsAnswer { get; set; }
public int? Procent { get; set; }
public bool Delete { get; set; }
public bool Visible { get; set; }
public int? Numbers { get; set; }
public String Help_Text { get; set; }
}
public class MatrixRows
{
public bool Delete { get; set; }
public bool Visible { get; set; }
public int Row_Id { get; set; }
public String Row_Number { get; set; }
public String Row_Description { get; set; }
public String Special_Row_CSS { get; set; }
public String Help_Text { get; set; }
// Dette er summen af procenterne af alle kolonner i rækken
public int RowSum { get; set; }
}
All the data for theese models is retrieved in the Controller, based upon a QuestionID, and mapped to a ViewModel that looks like this:
public class ShowMatrixQuestionViewModel : Question
{
public Dictionary<MatrixRows, List<MatrixColumns>> columnrow { get; set; }
public List<MatrixColumns> columns { get; set; }
public List<MatrixRows> rows { get; set; }
public ShowMatrixQuestionViewModel()
{
columns = new List<MatrixColumns>();
rows = new List<MatrixRows>();
columnrow = new Dictionary<MatrixRows, List<MatrixColumns>>();
}
}
So when i want to send a ShowMatrixQuestionViewModel to a View from my Controller, what is the route i should take?
This is my suggestion:
-> Controller calls a method in the business layer called
public ShowMatrixViewModel GetQuestion(int QuestionID) {}
-> GetQuestion calls the following methods in the data layer:
public Question GetQuestion(int QuestionId) {}
public MatrixRows GetRows(int QuestionId) {}
public MatrixColumns GetColumns(int id) {}
-> Entity framework returns "pure" objects, which i want to map over to the ones i posted above
-> GetQuestion calls methods to map the EF models to my own models
-> Last GetQuestion calls a method that maps the Questions, Rows and Columns:
ShowMatrixQuestionViewModel model = MapShowMatrixQuestionViewModel(Question, MatrixRows, MatrixColumns)
return model;
Is this correct?
Thanks in advance
To answer the first part of your question:
"Where should the mapping between the Models that MVC uses and the models that EF creates go?"
The answer is that the models MVC uses are the models created by the EF. Your EF tool in the ASP.NET MVC project is either Linq to SQL Classes or the ADO.NET Entity Framework Model. You should create these inside the Models folder in your project and they provide your data / persistence (EF).

EF 4.1 - Update Properties On Child Collection

I have searched hi and low and I am stuck here.
I am using EF 4.1 in an MVC3 app, with the Service/Repository/UnitOfWork pattern and AutoMapper to map my models and entities.
So I have a really basic situation; I have a collection of ChildProducts that have a collection of PriceTiers.
My view models look like this:
AddEditChildProductModel
public class AddEditChildProductModel
{
#region "Fields/Properties"
public ActionType ActionType { get; set; }
public string FormAction { get; set; }
public int ID { get; set; }
public int ProductID { get; set; }
public string Sku { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Cost { get; set; }
public decimal MSRP { get; set; }
public decimal RetailPrice { get; set; }
public int Servings { get; set; }
public decimal Weight { get; set; }
public bool Display { get; set; }
public int DisplayIndex { get; set; }
public IEnumerable<AddEditPriceTierModel> PriceTiers { get; set; }
#endregion
#region "Constructor(s)"
#endregion
#region "Methods"
#endregion
}
AddEditPriceTierModel
public class AddEditPriceTierModel
{
#region "Fields/Properties"
public int ID { get; set; }
public int ChildProductID { get; set; }
public decimal Price { get; set; }
public int QuantityStart { get; set; }
public int QuantityEnd { get; set; }
public bool IsActive { get; set; }
#endregion
#region "Constructor(s)"
#endregion
#region "Methods"
#endregion
}
In the controller action, I am simply trying to map the changed PriceTier properties:
public ActionResult EditChildProduct(AddEditChildProductModel model)
{
if (!ModelState.IsValid)
return PartialView("AddEditChildProduct", model);
ChildProduct childProduct = productService.GetChildProductByID(model.ID);
AutoMapper.Mapper.Map<AddEditChildProductModel, ChildProduct>(model, childProduct);
UnitOfWork.Commit();
return ListChildProducts(model.ProductID);
}
And I am getting this error:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
When stepping into the action, the models/entities are mapped correctly, I don't get it!!
Eranga is right. I'm guessing your productService does not call AsNoTracking on the ef context before returning the ChildProduct. If not, this means what it returns is still attached to the context. When automapper does its thing, it replaces the whole collection, which can orphan the attached child entities that were not part of the form submission. Since the orphans don't have a non-null foreign key, they must be deleted from the context before calling SaveChanges. If they are not, you get this infamous exception.
On the other hand, if your productService calls AsNoTracking on the context before returning the entity, it will not track changes, and will not try to delete any orphaned items that do not exist in the collection created by automapper.

Resources