Why I get exception when I try to use AutoMapper? - c#

I use AutoMapper in my .NET CORE 2.2 project.
I get this exception:
Missing type map configuration or unsupported mapping.
Mapping types:
SaveFridgeTypeModel -> FridgeType
College.Refrigirator.Application.SaveFridgeTypeModel ->
College.Refrigirator.Domain.FridgeType
On This row:
var fridgeType = _mapper.Map<SaveFridgeTypeModel, FridgeType>(model);
Here is defenition of FridgeType class:
public class FridgeType : IEntity , IType
{
public FridgeType()
{
Fridges = new HashSet<Fridge>();
}
public int ID { get; set; }
//Description input should be restricted
public string Description { get; set; }
public string Text { get; set; }
public ICollection<Fridge> Fridges { get; private set; }
}
Here is defenition of SaveFridgeTypeModel class:
public class SaveFridgeTypeModel
{
public string Description { get; set; }
public string Text { get; set; }
}
I add this row:
services.AddAutoMapper(typeof(Startup));
To ConfigureServices function in Startup class.
UPDATE
I forgot to add mappin configuration to the post.
Here is mapping configs class:
public class ViewModelToEntityProfile : Profile
{
public ViewModelToEntityProfile()
{
CreateMap<SaveFridgeTypeModel, FridgeType>();
}
}
Any idea why I get the exception above?

You need to use the type from the assembly where your maps are when registering automapper with DI.
AddAutomapper(typeof(ViewModelToEntityProfile));
If you had multiple assemblies with maps - you could use another overload:
AddAutomapper(typeof(ViewModelToEntityProfile), typeof(SomeOtherTypeInOtherAssembly));

After creating mapping config class you need to add the AutoMapperConfiguration in the Startup.cs as shown below:
public void ConfigureServices(IServiceCollection services) {
// .... Ignore code before this
// Auto Mapper Configurations
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new ViewModelToEntityProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
services.AddMvc();
}

Related

AutoMapper - Get error when trying to map two classes

I am trying to use AutoMapper to map a DTO to an Entity class but I keep getting an error.
Here is the DTO Class:
public class Product
{
public string ID { get; set; }
public string SKU { get; set; }
public string Name { get; set; }
public PriceTiers PriceTiers { get; set; }
}
and here is the Entity:
public partial class Product
{
public Product()
{
PriceTiers = new List<PriceTiers>();
}
[Key]
public string ID { get; set; }
public string SKU { get; set; }
public string Name { get; set; }
public virtual ICollection<PriceTiers> PriceTiers { get; set; }
}
Why do I keep getting the following error?
{"Missing type map configuration or unsupported
mapping.\r\n\r\nMapping types:\r\nPriceTiers ->
ICollection1\r\nWeb.Areas.DEAR.DTOs.PriceTiers -> System.Collections.Generic.ICollection1[[Web.Areas.DEAR.Data.PriceTiers,
Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]\r\n\r\n
Destination Member:\r\nPriceTiers\r\n"}
This is what I have in the Profile class:
AllowNullCollections = true;
CreateMap<DTOs.Product, Data.Product>();
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
and this is what I use to map the classes:
var products = _mapper.Map<IEnumerable<Product>>(result.Products);
This is what is in the Program.cs:
builder.Services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);
The exception message is quite clear, the AutoMapper doesn't know how to map the data from DTOs.PriceTiers to ICollection<Data.PriceTiers>.
Solution 1: Map from DTOs.PriceTiers to ICollection<Data.PriceTiers>
I believe that Custom Type Converters is what you need.
Create Custom Type Converters.
public class ICollectionDataPriceTiersTypeConverter : ITypeConverter<DTOs.PriceTiers, ICollection<Data.PriceTiers>>
{
public ICollection<Data.PriceTiers> Convert(DTOs.PriceTiers src, ICollection<Data.PriceTiers> dest, ResolutionContext context)
{
if (src == null)
return default;
var singleDest = context.Mapper.Map<Data.PriceTiers>(src);
return new List<Data.PriceTiers>
{
singleDest
};
}
}
Add to mapping profile.
CreateMap<DTOs.PriceTiers, ICollection<Data.PriceTiers>>()
.ConvertUsing<ICollectionDataPriceTiersTypeConverter>();
Demo # .NET Fiddle
Solution 2: Map from ICollection<DTOs.PriceTiers> to ICollection<Data.PriceTiers>
If the PriceTiers in DTOs.Product supports multiple items and mapping with many to many (to ICollection<Data.ProductTiers>), then consider modifying the property as the ICollection<DTOs.PriceTiers> type.
namespace DTOs
{
public class Product
{
...
public ICollection<PriceTiers> PriceTiers { get; set; }
}
}
Did you added "CreateMapper()" method after your configurations?
Try something like that.
public class MappingProfile : Profile
{
public MappingProfile {
AllowNullCollections = true;
CreateMap<DTOs.Product, Data.Product>();
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
}
}
After that, on your container service, inject this dependency:
var mappingConfig = new MapperConfiguration(cfg =>
{
cfg.AddProfile(new MappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
builder.Services.AddSingleton(mapper);
After some more research I found out that my mapping profile was not in the right order. These are the changes I made.
public class AutoMapperProfiles : Profile
{
public AutoMapperProfiles()
{
AllowNullCollections = true;
CreateMap<DTOs.PriceTiers, Data.PriceTiers>();
CreateMap<DTOs.Product, Data.Product>()
.ForMember(dto => dto.PriceTiers, opt => opt.MapFrom(x => x.PriceTiers));
}
}
Now it maps perfectly

_mapper.Map<IDataReader, IEnumerable<T>>(dt.CreateDataReader()); gives error Missing type map configuration or unsupported mapping

I am creating an automap from Datatable to My Class. the data column matches to class field names. Data table contains more columns than class.
I have created a profile and added it to IServiceCollection but still its not working.
public class DestClass
{
public string id_branch { get; set; }
public string city{ get; set; }
public string User{ get; set; }
}
public DestMappingProfile()
{
CreateMap<IDataReader, DestClass>();
}
In Startup ConfigureServices
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new DestMappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
services.AddSingleton(mapper);
At repository level
var mapper = _mapper.Map<IDataReader, IEnumerable<DestClass>>
(dt.CreateDataReader());
I want all the collection of returned data into this DestClass.

AutoMapper - Map Derived Class To Dto

Im trying to map a Class which inherits from a base class to a dto.
public class LaunchConfiguration : Document
{
public string Brand { get; set; }
public string SettingName{ get; set; }
}
public class LaunchConfigurationDto
{
public string Brand { get; set; }
public string SettingName{ get; set; }
}
The point of the dto is to hide the fields of the base document when it gets returned to the user. This is my Map configuration
public class DtoProfile : Profile
{
public DtoProfile()
{
CreateMap<LaunchConfiguration,LaunchConfigurationDto>();
}
};
The problem im having is that auto mapper complains about the base class properties which are not mapped . "Unmapped members were found." The properties are the ones on the base class. I have tried specifying this to be ignored in the profile to no avail . Can anyone specify the correct way to do this ?
My ConfigureServices Method incase anyone is wondering :
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = Configuration["ApiInformation:Name"], Version = Configuration["ApiInformation:Version"] });
c.DescribeAllEnumsAsStrings();
});
services.AddAutoMapper(mc =>
{
mc.AddProfile(new DtoProfile());
});
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
});
}
My Base Class :
public class Document : IDocument, IDocument<Guid>
{
public Document()
{
this.Id = Guid.NewGuid();
this.AddedAtUtc = DateTime.UtcNow;
}
/// <summary>The Id of the document</summary>
[BsonId]
public Guid Id { get; set; }
/// <summary>The datetime in UTC at which the document was added.</summary>
public DateTime AddedAtUtc { get; set; }
/// <summary>The version of the schema of the document</summary>
public int Version { get; set; }
}
My implementation where _mapper is my Injected mapper and _repo My Injected Repo. Exception Occurs on Map Method call
Task ILaunchConfigurationService<LaunchConfigurationDto >.InsertLaunchConfiguration(LaunchConfigurationDto model)
{
var mapped = _mapper.Map<LaunchConfiguration >(model);
return _repo.AddOneAsync(mapped);
}
Your problem should be solved by simply adding ReverseMap() to CreateMap call:
public class DtoProfile : Profile
{
public DtoProfile()
{
CreateMap<LaunchConfiguration, LaunchConfigurationDto>().ReverseMap();
}
};
Automapper creates one way map by default. ReverseMap is just a sugar for creating reverse map in case there are no peculiar mappings in one way. You could also do it like this:
public class DtoProfile : Profile
{
public DtoProfile()
{
CreateMap<LaunchConfiguration, LaunchConfigurationDto>();
CreateMap<LaunchConfigurationDto, LaunchConfiguration>();
}
};
You can read more about this in documentation
However I cannot guarantee you that you will not experience exceptions from database with your current implementation on commiting changes.

How to include read-only property in an OData query

I have the following models:
public class Employee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
[...]
public int OfficeId { get; set; }
public string OfficeInfo
{
get { return Office.Info; }
}
public Office Office { get; set; }
}
public class Office
{
public int OfficeId { get; set; }
public string Info { get; set; }
}
I have a grid in the client side which rows I want to feed with instances of Employee, including the OfficeInfo in one of the columns, so I'm consuming it through the following query:
"/odata/Employees?$expand=Office&$select=EmployeeId,Name,OfficeInfo"
I have both entities registered in the IEdmModel:
private static IEdmModel GetEDMModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Employee>("Employees");
builder.EntitySet<Office>("Offices");
[...]
}
and my Get action looks like this:
[EnableQuery]
public IQueryable<Employees> Get()
{
[...]
}
but I keep getting this Exception:
"Could not find a property named 'OfficeInfo' on type 'Xds.Entities.Employee'"
What am I missing here?
You could mark the property OfficeInfo as required or add this property explicitly:
Noting as required:
builder
.EntitySet<Employee>("Employees")
.EntityType
.Property(_ => _.OfficeInfo)
.IsRequired();
Adding explicitly:
builder
.EntitySet<Employee>("Employees")
.EntityType
.Property(_ => _.OfficeInfo)
.AddedExplicitly = true;
You can check your model metadata and see whether following appears under 'Xds.Entities.Employee' type.
<Property Name="OfficeInfo" Type="Edm.String" />
Since it is a readonly property, you should turn on isQueryCompositionMode to get it shown in the model, like (can pass real HttpConfiguration there):
ODataModelBuilder builder = new ODataConventionModelBuilder(new System.Web.Http.HttpConfiguration(), true);
After that, the query should work.
Note that flag is marked as for testing purpose, but it should be fine if you manually verify your metadata.

Custom mapping with asp.net boilerplate and Automapper

I am new of ASP.NET BoilerPlate (ABP) and I am trying to understand how to create custom mappings using AutoMapper and, maybe, the ABP automapper attributes: AutoMap, AutoMapFrom, AutoMapTo.
With ABP I can map two classes in this way:
[AutoMapTo(typeof(DestClass)]
public class SourceClass {
public string A { get; set; }
public string B { get; set; }
}
public class DestClass {
public string A { get; set; }
public string B { get; set; }
}
But if I have two classes like the following where I want the property AB to be automapped as a join of A and B:
[AutoMapTo(typeof(DestClass)]
public class SourceClass {
public string A { get; set; }
public string B { get; set; }
}
public class DestClass {
public string AB { get; set; }
}
Are there some attributes with ABP? Or do I need to use the "classical" AutoMapper code:
Mapper.CreateMap<SourceClass, DestClass>()
.ForMember(dest => dest.AB,
opts => opts.MapFrom(src => (src.A + ", " + src.B)));
And where do I have to place such init code?
I found a solution I share here.
In "MyProject.Application" project I defined my automapper customs (I used profiles):
public class MyProjectAutoMapperProfile : AutoMapper.Profile {
protected override void Configure() {
CreateMap<SourceClass, DestClass>()
.ForMember(dest => dest.AB,
opts => opts.MapFrom(src => (src.A + ", " + src.B)));
// other customs here...
}
Then I registered it for injection in the Initialize method of the class MyProjectApplicationModule:
[DependsOn(typeof(MyProjectCoreModule), typeof(AbpAutoMapperModule))]
public class MyProjectApplicationModule : AbpModule
{
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
// --- MY CODE for registering custom automapping
var mapperConfiguration = new MapperConfiguration(cfg => {
cfg.AddProfile(new MyProjectMapperProfile()); // <= here my custom mapping
});
var mapper = mapperConfiguration.CreateMapper();
IocManager.IocContainer.Register(
Castle.MicroKernel.Registration.Component.For<IMapper>().Instance(mapper)
);
// --- MY CODE
}
}
Note that I directly used the Castle IOC register methods because I did not find any useful registering method for objects in ABP. Do you know one?
Finally I used my custom mapping as injection in my Application Service and used it directly:
public class MyAppService : MyNewHouseAppServiceBase, IMyAppService {
// ...
public MyAppService(IRepository<SourceClass, long> myRepository, AutoMapper.IMapper mapper) {
_myRepo = myRepository;
_mapper = mypper;
}
public async Task<DestClass> GetSource(long id) {
var source = await _myRepo.Find(id);
// USE THE INJECTED MAPPER
return _mapper.Map<DestClass>(source);
}
public async Task<ListResultOutput<DestClass>> GetSources() {
var sources = await _myRepo.GetAllListAsync();
return new ListResultOutput<DestClass>(
// USE THE INJECTED MAPPER
_mapper.Map<List<DestClass>>(sources)
);
}
}
No need to list all the customer mapping on the Module. Just tell the module to find all the classes which extend AutoMapper.Profile:
Assembly thisAssembly = typeof(AbpProjectNameApplicationModule).GetAssembly();
IocManager.RegisterAssemblyByConvention(thisAssembly);
cfg.AddProfiles(thisAssembly);

Categories