I'm new to AutoMapper and trying to map Array class ItemLink[].
public class ViewModel
{
public ItemLink[] ItemLinks { get; set; }
}
public class ItemLink
{
public string Description { get; set; }
}
I tried:
Mapper.Map<viewModel.ItemLink>(db.ItemLinks);
Error:
"Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance."
Can't it be simple mapping?
EDIT 1
To clarify more, I'm getting similar class structure from database. Example,
public class Db
{
public ItemLink[] ItemLinks { get; set; }
}
So, I want to map ViewModel.ItemLink[] with Db.ItemLink[].
You cannot provide variable to a generic parameter like you do in Mapper.Map<viewModel.ItemLink>(db.ItemLinks);. It is called Type parameter and must be a type.
As #gisek mentioned in his answer you need to configure mapper first. Normally it is done at application startup.
You can consider to fetch full objects from db, but you also have an option to use Queryable Extensions which are there to only fetch data you need for your view model.
The configuration. I assume that you have namespace DB for entity in database, and View namespace for view model.
Mapper.Initialize(cfg => {
cfg.CreateMap<DB.ItemLink, View.ItemLink>();
});
Mapper.Configuration.AssertConfigurationIsValid();
Fetch full entity from DB and then map it to property:
var viewModel = new View.Item();
viewModel.ItemLinks = Mapper.Map<View.ItemLink[]>(db.ItemLinks.ToArray());
Project entity from DB to view model:
var viewModel = new View.Item();
viewModel.ItemLinks = db.ItemLinks.ProjectTo<View.ItemLink>().ToArray();
I assumed you are using .net mvc
Firstly you need Initialize your mapping on Application Start, there is no need to initialize every time in your controller etc.
Following error means you didn't initialize mapper, automapper doesn't know your source and destination object.
Error:
"Mapper not initialized. Call Initialize with appropriate
configuration. If you are trying to use mapper instances through a
container or otherwise, make sure you do not have any calls to the
static Mapper.Map methods, and if you're using ProjectTo or
UseAsDataSource extension methods, make sure you pass in the
appropriate IConfigurationProvider instance."
For Solution:
Create an AutoMapperConfig object in your App_Start folder.
public class AutoMapperConfig
{
public static void Register()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<source,destination>(); /*If source and destination object have same propery */
cfg.CreateMap<source, destination>()
.ForMember(dest => dest.dId, opt => opt.MapFrom(src => src.sId)); /*If source and destination object haven't same property, you need do define which property refers to source property */
});
}
}
In your Global.asax.cs
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AutoMapperConfig.Register(); // Call Register method on App_Start
}
In your controller
var mappedArray = Mapper.Map<viewmodel.ItemLink[]>(db.ItemLinks);
or
var mappedArray = Mapper.Map<viewmodel.ItemLink[],ItemLinks[]>(db.ItemLinks); //ItemLinks[] refers to your dto.
You need to configure the mapper first.
There are 2 possible approaches, static and non-static. I lean towards non-static as it allows you to create multiple mappers, which can use different mapping strategies.
Non-static example:
using AutoMapper;
namespace Experiments
{
class Program
{
static void Main(string[] args)
{
var links = new ItemLink[]
{
new ItemLink {Description = "desc 1"},
new ItemLink {Description = "desc 2"},
};
var item = new Item
{
ItemLinks = links,
};
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ItemLink, ItemLink>(); // you can extend this part of the configuration here
cfg.CreateMap<Item, Item>();
cfg.CreateMap<ItemLink, MyCustomClass>()
.ForMember(myCustomClass => myCustomClass.DescriptionWithDifferentName,
expression => expression.MapFrom(itemLink => itemLink.Description)); // to map to a different type
// more configs can do here
// e.g. cfg.CreateMap<Item, SomeOtherClass>();
});
IMapper mapper = new Mapper(config);
ItemLink linkClone = mapper.Map<ItemLink>(links[0]);
ItemLink[] linkArrayClone = mapper.Map<ItemLink[]>(item.ItemLinks);
Item itemClone = mapper.Map<Item>(item);
MyCustomClass myCustomClassObject = mapper.Map<MyCustomClass>(links[0]);
}
}
public class Item
{
public ItemLink[] ItemLinks { get; set; }
}
public class ItemLink
{
public string Description { get; set; }
}
public class MyCustomClass
{
public string DescriptionWithDifferentName { get; set; }
}
}
Static example:
using AutoMapper;
namespace Experiments
{
class Program
{
static void Main(string[] args)
{
var links = new ItemLink[]
{
new ItemLink {Description = "desc 1"},
new ItemLink {Description = "desc 2"},
};
var item = new Item
{
ItemLinks = links,
};
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ItemLink, ItemLink>(); // you can extend this part of the configuration here
cfg.CreateMap<Item, Item>();
cfg.CreateMap<ItemLink, MyCustomClass>()
.ForMember(myCustomClass => myCustomClass.DescriptionWithDifferentName,
expression => expression.MapFrom(itemLink => itemLink.Description));
// to map to a different type
// more configs can do here
// e.g. cfg.CreateMap<Item, SomeOtherClass>();
});
ItemLink linkClone = Mapper.Map<ItemLink>(links[0]);
ItemLink[] linkArrayClone = Mapper.Map<ItemLink[]>(item.ItemLinks);
Item itemClone = Mapper.Map<Item>(item);
MyCustomClass myCustomClassObject = Mapper.Map<MyCustomClass>(links[0]);
}
public class Item
{
public ItemLink[] ItemLinks { get; set; }
}
public class ItemLink
{
public string Description { get; set; }
}
public class MyCustomClass
{
public string DescriptionWithDifferentName { get; set; }
}
}
}
You can also configure Automapper to create missing maps automatically with cfg.CreateMissingTypeMaps = true;
using AutoMapper;
namespace Experiments
{
class Program
{
static void Main(string[] args)
{
var links = new ItemLink[]
{
new ItemLink {Description = "desc 1"},
new ItemLink {Description = "desc 2"},
};
var item = new Item
{
ItemLinks = links,
};
Mapper.Initialize(cfg =>
{
// now AutoMapper will try co create maps on it's own
cfg.CreateMissingTypeMaps = true;
// we can still add custom maps like that
cfg.CreateMap<ItemLink, MyCustomClass>()
.ForMember(myCustomClass => myCustomClass.DescriptionWithDifferentName,
expression => expression.MapFrom(itemLink => itemLink.Description));
});
ItemLink linkClone = Mapper.Map<ItemLink>(links[0]);
ItemLink[] linkArrayClone = Mapper.Map<ItemLink[]>(item.ItemLinks);
Item itemClone = Mapper.Map<Item>(item);
// without custom map myCustomClassObject.DescriptionWithDifferentName would be null
MyCustomClass myCustomClassObject = Mapper.Map<MyCustomClass>(links[0]);
}
public class Item
{
public ItemLink[] ItemLinks { get; set; }
}
public class ItemLink
{
public string Description { get; set; }
}
public class MyCustomClass
{
public string DescriptionWithDifferentName { get; set; }
}
}
}
I am not sure that I understood what you need,
I guess that you are trying to map from a list of ItemLink to a list of viewModel.ItemLink
so Mapper.Map<viewModel.ItemLink>(db.ItemLinks);
become
var listOfViewModelItemLink = Mapper.Map<List<viewModel.ItemLink>>(db.ItemLinks);
you can call ToArray() on listOfViewModelItemLink then assign to ItemLinks property of Item class
I am not sure but I guess that you are trying to map from an array of ItemLink to an array of viewModel.ItemLink. You should do it like this:
var viewModels = db.ItemLinks
.ToArray()
.Select(x=>Mapper.Map<viewModel.ItemLink>(x))
.ToArray();
Related
Swagger exposes by default any schema that is used by an exposed controller (API end point). How can a schema (class) be exposed if it is not used by a controller?
For example, Swagger is showing the following Schemas:
But, the Song Schema (below) needs to be exposed. It is not exposed because it is not used by a controller (API end point).
using System;
namespace ExampleNamespace
{
public class Song
{
[Key][Required]
public int SongID { get; set; }
[Required]
public string SongName { get; set; }
public string SongDescription { get; set; }
public int SongLength { get; set; } //seconds
[Required]
public int AlbumID { get; set; }
}
}
How can this be accomplished?
You can add a schema using a DocumentFilter
public class AddSongSchemaDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
var songSchema = new OpenApiSchema {...};
songSchema.Properties.Add(new KeyValuePair<string, OpenApiSchema>("songName", new OpenApiSchema { ... }));
...
context.SchemaRepository.Schemas.Add("Song", songSchema);
}
}
The class OpenApiSchema is used for the song schema itself, and for property schemas. This type contains a number of documentation related properties you can set such as Description.
You register AddSongSchemaDocumentFilter like so
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(options =>
{
options.DocumentFilter<AddSongSchemaDocumentFilter>();
});
}
This could be a bit tedious if there are many properties. Using reflection, you can iterate on the properties, and even reflect on associated attributes attached to those properties.
var songSchema = new OpenApiSchema() { };
var song = new Song();
var properties = typeof(Song).GetProperties();
foreach (var p in properties)
songSchema.Properties.Add(new KeyValuePair<string, OpenApiSchema(
p.Name,
new OpenApiSchema()
{
Type = p.PropertyType.ToString(),
Description = // get [Description] attribute from p,
// ... etc. for other metadata such as an example if desired
}));
context.SchemaRepository.Schemas.Add("Song", songSchema);
Full Swashbuckle documentation.
Is it possible to map from a source-object to a specific implementation of a target object using automapper?
In the following code-sample, I want to map from SourceItem to TargetSampleItem and not to TargetBaseItem. Important: TargetBaseItem can't be abstract. But if I use the following mapper-configuration, always TargetBaseItem is used.
To summarize things up, the following collection should contain items of type TargetSampleItem, which is derriving from TargetBaseItem:
public IList<TargetItemBase> Items { get; set; } = new List<TargetItemBase>();
Complete code
using AutoMapper;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MapperTest
{
class Program
{
static void Main(string[] args)
{
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceRoot, TargetRoot>();
cfg.CreateMap<SourceItem, TargetItemBase>()
.IncludeAllDerived();
});
configuration.AssertConfigurationIsValid();
var mapper = configuration.CreateMapper();
var source = new SourceRoot
{
Id = 1,
Items = new List<SourceItem>
{
new SourceItem { A = "a1", B = "b1" },
new SourceItem { A = "a2", B = "b2" }
}
};
var result = mapper.Map<TargetRoot>(source);
// Should retur true
Console.WriteLine(result.Items.First() is TargetSampleItem);
Console.ReadLine();
}
/// <summary>
/// Source model
/// </summary>
public class SourceRoot
{
public int Id { get; set; }
public IList<SourceItem> Items { get; set; } = new List<SourceItem>();
}
public class SourceItem
{
public string A { get; set; }
public string B { get; set; }
}
/// <summary>
/// Target model
/// </summary>
public class TargetRoot
{
public int Id { get; set; }
public IList<TargetItemBase> Items { get; set; } = new List<TargetItemBase>();
}
public class TargetItemBase
{
public string A { get; set; }
}
public class TargetSampleItem : TargetItemBase
{
public string B { get; set; }
}
}
}
EDIT
using As<> is not working, because than AutoMapper is not mapping to the type, rather than just casting it:
cfg.CreateMap<SourceItem, TargetItemBase>()
.As<TargetSampleItem>();
EDIT 2/Solution
Using As<> is working, if a map between SourceItem and TargetSampleItem is added too:
cfg.CreateMap<SourceItem, TargetItemBase>().As<TargetSampleItem>();
cfg.CreateMap<SourceItem, TargetSampleItem>();
As does work for me:
cfg.CreateMap<SourceItem, TargetItemBase>().As<TargetSampleItem>();
cfg.CreateMap<SourceItem, TargetSampleItem>();
If As<> doesn't work for you, then a possible solution might be using AfterMap like -
CreateMap<SourceRoot, TargetRoot>()
.AfterMap((s, d) =>
{
s.Items.ToList().ForEach(p => d.TargetItems.Add(new TargetSampleItem { A = p.A, B = p.B }));
});
(Its not an elegant solution, but since TargetSampleItem is not the target of any of your maps, I don't see any reason why AutoMapper would create an instance of it).
You have to rename either of the Items properties so that AutoMapper doesn't try to map them automatically (I renamed the one in TargetRoot class to TargetItems). And of course you don't need the mapping between SourceItem and TargetItemBase.
Got a mapper configuration which includes:
this.CreateMap<MyProp1, MyDesintationProp>();
this.CreateMap<MyProp2, MyDesintationProp>();
Then, inside the mapper for a more complex object, both these are used inside an inline ResolveUsing to merge into a list of MyDestinationProp:
.ForMember(dest => dest.MyDesintationPropList, opt => opt.ResolveUsing(tc =>
{
var results = new List<MyDesintationProp>();
if (tc.MyProp1 != null)
{
results.Add(Mapper.Map<MyDestinationProp>(tc.MyProp1));
}
if (tc.MyProp2 != null)
{
results.Add(Mapper.Map<MyDestinationProp>(tc.MyProp2));
}
return results;
}))
This works fine in production, but causes tests on this mapping to blow up, complaining
Property:
MyDestinationProp
---- System.InvalidOperationException : Mapper not initialized. Call Initialize with appropriate configuration. If you are trying to use mapper instances through a container or otherwise, make sure you do not have any calls to the static Mapper.Map methods, and if you're using ProjectTo or UseAsDataSource extension methods, make sure you pass in the appropriate IConfigurationProvider instance.
Which I guess makes sense because the mapper inside the mapper hasn't been initialised. But what's the best way to go about fixing this?
This is a very trivial example, that really doesn't need to use the context, cause it would also work with simple mappings.
public class Source
{
public string Name { get; set; }
public SourceChild Child { get; set; }
public override string ToString() => $"{typeof(Source)} -> {Name}";
}
public class Destination
{
public string Name { get; set; }
public DestinationChild Child { get; set; }
public override string ToString() => $"{typeof(Destination)} -> {Name}";
}
public class SourceChild
{
public string Name { get; set; }
public override string ToString() => $"{typeof(SourceChild)} -> {Name}";
}
public class DestinationChild
{
public string Name { get; set; }
public override string ToString() => $"{typeof(DestinationChild)} -> {Name}";
}
public class MappingContextProfile : Profile
{
public MappingContextProfile()
{
CreateMap<Source, Destination>()
.ForMember(source => source.Child, conf => conf.MapFrom((source, destination, destinationChild, context) =>
{
// Not really needed in this case, cause it does just some simple mapping.
// But if you would need to add some informations from outside
// (e.g. some interface mapping, etc)
// this would be the place without losing the magic of AutoMapper.
return context.Mapper.Map<DestinationChild>(source.Child);
}));
CreateMap<SourceChild, DestinationChild>();
}
}
public class MappingSimpleProfile : Profile
{
public MappingSimpleProfile()
{
CreateMap<Source, Destination>();
CreateMap<SourceChild, DestinationChild>();
}
}
public static class Program
{
public static async Task<int> Main(string[] args)
{
var config = new MapperConfiguration(cfg => cfg.AddProfile<MappingContextProfile>());
var mapper = config.CreateMapper();
var sources = Enumerable.Range(1, 10)
.Select(i => new Source { Name = $"{i}", Child = new SourceChild { Name = $"{i * 100}" } })
.ToList();
var destinations = mapper.Map<List<Destination>>(sources);
foreach (var item in destinations)
{
Console.WriteLine($"{item} -> {item.Child}");
}
return 0;
}
}
I'm dealing with a really awful set of generated classes which have a ton of properties of type object that contain various types I want to map. The class mappings seem to work however the property references are just copied directly without mapping the referenced objects.
How can I define a map which will map the objects inside the Items property? I have a ton of objects like this so hoping I can define this fairly simply...
Example:
class Program
{
static void Main(string[] args)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<TerribleType1, TerribleType1Dto>();
cfg.CreateMap<TerribleType2, TerribleType2Dto>();
cfg.CreateMap<TerribleObject, TerribleObjectDto>();
});
var mapper = config.CreateMapper();
var terribleObject = new TerribleObject
{
Items = new object[] { new TerribleType1 { PropA = "Test1" }, new TerribleType2 { PropA = "Test2" } }
};
var terribleObjectDto = mapper.Map<TerribleObjectDto>(terribleObject);
//Want a TerribleType1Dto but instead I get a TerribleType1
Console.WriteLine(terribleObjectDto.Items[0].GetType().Name);
}
}
class TerribleObject
{
// Contains some TerribleType1 and TerribleType2 objects, these don't share a common base.
public object[] Items { get; set; }
}
class TerribleObjectDto
{
//Want this to have some TerribleType1Dto and TerribleType2Dto objects.
public object[] Items { get; set; }
}
public class TerribleType1
{
public string PropA { get; set; }
}
public class TerribleType1Dto
{
public string PropA { get; set; }
}
public class TerribleType2Dto
{
public string PropA { get; set; }
}
public class TerribleType2
{
public string PropA { get; set; }
}
Based on How can I use Automapper to map an object to an unknown destination type? it is possible to get the configured destination type for a mapping when you know the source type at runtime only. With the help of MapFrom() it is possible to build this ugly mapping for the inner object type objects:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<TerribleType1, TerribleType1Dto>();
cfg.CreateMap<TerribleType2, TerribleType2Dto>();
cfg.CreateMap<TerribleObject, TerribleObjectDto>()
.ForMember(t => t.Items, m => m.MapFrom((source, target, data, context) =>
{
object[] items = source.Items;
object[] targetArray = new object[items.Length];
for (int i = 0; i < items.Length; i++)
{
object fieldEntry = items[i];
Type destinationType = context.Mapper.ConfigurationProvider
.GetAllTypeMaps()
.Single(it => it.SourceType == fieldEntry.GetType())
.DestinationType;
targetArray[i] = context.Mapper.Map(fieldEntry,
fieldEntry.GetType(),
destinationType);
}
return targetArray;
}));
});
This will convert each object in the array to the configured destination type. When you run your code now:
Console.WriteLine(terribleObjectDto.Items[0].GetType().Name);
Console.WriteLine(terribleObjectDto.Items[1].GetType().Name);
you will get the following output:
TerribleType1Dto
TerribleType2Dto
I have 2 classes I want to map with Automapper:
namespace AutoMapperApp
{
public class Class1
{
public string Test { get; set; }
public string property_name { get; set; }
}
}
namespace AutoMapperApp
{
public class Class2
{
public string Test { get; set; }
public string PropertyName { get; set; }
}
}
This is my Automapper config:
using AutoMapper;
namespace AutoMapperApp
{
public static class AutoMapperConfig
{
public static MapperConfiguration MapperConfiguration;
public static void RegisterMappings()
{
MapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Class1, Class2>();
cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
cfg.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
});
}
}
}
According the Wiki of Automapper this should work:
https://github.com/AutoMapper/AutoMapper/wiki/Configuration
But my unittest fails:
using Xunit;
using AutoMapperApp;
namespace AutoMapperTest
{
public class Test
{
[Fact]
public void AssertConfigurationIsValid()
{
AutoMapperConfig.RegisterMappings();
AutoMapperConfig.MapperConfiguration.AssertConfigurationIsValid();
}
}
}
Exception:
AutoMapper.AutoMapperConfigurationException:
Unmapped members were found. Review the types and members below.
Add a custom mapping expression, ignore, add a custom resolver, or modify the source/destination type
=============================================
Class1 -> Class2 (Destination member list)
AutoMapperApp.Class1 -> AutoMapperApp.Class2 (Destination member list)
Unmapped properties:
PropertyName
Why?
With help from the AutoMapper project in GitHub:
Try the CreateMap after you set the convention.
public static void RegisterMappings()
{
MapperConfiguration = new MapperConfiguration(cfg =>
{
cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
cfg.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
cfg.CreateMap<Class1, Class2>();
});
}
public class AutoMapperConfig
{
public static void RegisterMappings()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Class1, Class2>();
cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
cfg.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
});
}
}
I'm assuming you are calling this in your app_start method. AutoMapperConfig.RegisterMappings();
For organizational purposes you can separate your mappings into profiles, register them and set your conventions on a profile-by-profile basis if you don't need the convention to be global like in your example.
To answer your question, it looks like you created a mapper configuration but did not initialize it so Automapper doesn't know what mapping you're talking about.