I have 2 classes to map:
public class Note
{
public Guid UserId { get; set; }
public Guid Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public DateTime CreationDate { get; set; }
public DateTime EditDate { get; set; }
}
public class NoteDetailsVm : IMapWith<Note>
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public DateTime CreationDate { get; set; }
public DateTime EditDate { get; set; }
public void Mapping(Profile profile)
{
profile.CreateMap<Note, NoteDetailsVm>();
}
}
Here is the mapping profile and the mapping interface:
public class AssemblyMappingProfile : Profile
{
public AssemblyMappingProfile(Assembly assembly) =>
ApplyMappingsFromAssembly(assembly);
private void ApplyMappingsFromAssembly(Assembly assembly)
{
var types = (from t in assembly.GetExportedTypes()
where t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapWith<>))
select t).ToList();
foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod("Mapping");
methodInfo?.Invoke(instance, new object[] { this });
}
}
}
public interface IMapWith<T>
{
void Mapping(Profile profile) =>
profile.CreateMap(typeof(T), GetType());
}
I use this method to handle requests and get the viewmodel:
public async Task<NoteDetailsVm> Handle(GetNoteDetailsQuery request, CancellationToken cancellationToken)
{
var entity = await _dbContext.Notes.FirstOrDefaultAsync(note => note.Id == request.Id, cancellationToken);
if (entity == null || entity.UserId != request.UserId)
{
throw new NotFoundException(nameof(Note), request.Id);
}
return _mapper.Map<NoteDetailsVm>(entity);
}
So when I run the tests, I get such error though I have the necessary mappings:
AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.
AutoMapper.AutoMapperMappingException
Missing type map configuration or unsupported mapping.
Mapping types:
Note -> NoteDetailsVm
Notes.Domain.Note -> Notes.Application.Notes.Queries.GetNoteDetails.NoteDetailsVm
at lambda_method259(Closure , Object , NoteDetailsVm , ResolutionContext )
at Notes.Application.Notes.Queries.GetNoteDetails.GetNoteDetailsQueryHandler.Handle(GetNoteDetailsQuery request, CancellationToken cancellationToken)
Why isn't the mapping working and how can I fix this?
You most probably don't handle initialization in your tests. Check this guide here: https://www.thecodebuzz.com/unit-test-mock-automapper-asp-net-core-imapper/
The essence of it are these lines of code:
if (_mapper == null)
{
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new SourceMappingProfile());
});
IMapper mapper = mappingConfig.CreateMapper();
_mapper = mapper;
}
They make sure there is an instance of the automapper that is properly initialized with the correct profile.
Ok, I've managed to solve the problem, it had nothing to do with the code above. Just passed the wrong assembly to the profile constructor
Related
I got a small problem here. I got a course class and a User. I want to show all the Users inside a Course through the API.
the error i get,
'Object reference not set to an instance of an object.'
And this is my controller method,
var objList = _courseRepo.GetUsers(CourseId);
if (objList == null)
{
return NotFound();
}
var objToShow = new List<ViewCourseDetailsDTO>();
foreach (var obj in objList)
{
objToShow.Add(_mapper.Map<ViewCourseDetailsDTO>(obj));
}
return Ok(objToShow);
The Error i got is inside the Foreach-loop. It says that i need to create an object...
This is how my DTO classes looks like,
public class ViewCourseDetailsDTO
{
public int CourseId { get; set; }
public string CourseTitle { get; set; };
public ICollection<UserDTO>? Users { get; set; } = new List<UserDTO>();
}
And this one,
public class UserDTO
{
public string ID { get; set; }
public string UserName { get; set; }
public string Name { get; set; }
}
Do you think i have to break out the UserDTO somehow? Is it Therefore u think ?
if you want to see my CourseRepository than its here,
public ICollection<Course> GetUsers(int courseId)
{
return _db.Course.Where(c => c.CourseId == courseId).Include(a => a.Users).ToList();
}
Would be really grateful if you could help me out here.
Wohooo I found it, damnit!
On my controller, i forgot to put in mapper here,
public CourseController(ICourseRepository courseRepo, IMapper mapper)
{
_courseRepo = courseRepo;
_mapper = mapper;
}
I had injected it correct at the top but forgot to put it inside there ^
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
I have the following scenario
Entity framework classes classes:
public class Block
{
public Guid Id { get; set; }
public ICollection<BlockLocation> BlockLocations { get; set; }
public BlockType Type { get; set; }
}
public class BlockLocation
{
public Guid Id { get; set; }
public Guid BlockId { get; set; }
public Block Block { get; set; }
}
And my Domain Entities look like
public class Block
{
public Block(BlockType type = BlockType.None) : this()
{
Type = type;
}
private Block() { }
public Guid Id { get; set; }
public List<BlockLocation> BlockLocations { get; set; }
public BlockType Type { get; set; }
}
public class LiveBlock : Block
{
public LiveBlock() : base(BlockType.Live) { }
}
public class UnsequencedBlock : Block
{
public UnsequencedBlock() : base(BlockType.Unsequenced) { }
}
public class BlockLocation
{
public Guid Id { get; set; }
public Guid BlockId { get; set; }
public Block Block { get; set; }
}
public enum BlockType
{
None = 0,
Live,
Unsequenced
}
And what I want to do is map from Entity Framework to a Domain entity to the child type and also preserve the reference so that I don't get a stack overflow
My mappings are
cfg.CreateMap<Data.Block, Domain.LiveBlock>();
cfg.CreateMap<Data.Block, Domain.UnsequencedBlock>();
cfg.CreateMap<Data.Block, Domain.Block>().PreserveReferences().ConstructUsing((block, context) =>
{
if (block.Type == BlockType.Live)
{
// This loops until stack overflow
return context.Mapper.Map<Domain.LiveBlock>(block);
}
if (block.Type == BlockType.Unsequenced)
{
return context.Mapper.Map<Domain.LiveBlock>(block);
}
return context.Mapper.Map<Domain.Block>(block);
});
cfg.CreateMap<Data.BlockLocation, Domain.BlockLocation>();
And I'm trying to do the following:
// This is the EF entity
var block = new Data.Block
{
Id = Guid.NewGuid(),
Type = BlockType.Live,
BlockLocations = new List<Data.BlockLocation>
{
new BlockLocation {Id = Guid.NewGuid()},
new BlockLocation {Id = Guid.NewGuid()}
}
};
block.BlockLocations[0].Block = block;
block.BlockLocations[1].Block = block;
// Trying to create a Domain entity
var domainBlock = Mapper.Map<Data.Block, Domain.Block>(block);
The result that I want to achieve is for domainBlock to be of type LiveBlock and have a list of BlockLocations which in turn have the same LiveBlock entity as their Block property
What I get is a loop in ConstructUsing, until I get stack overflow.
Now, my questions are:
Can this be achieved with AutoMapper?
If yes, can it be done with ContructUsing? I've also tried ConvertUsing, but I get the same result.
Some other approach maybe?
I know that a way of doing to would be to Ignore the BlockLocations property from Domain.Block and map them separately, but I would like to have Automapper to that automatically.
Thank you for your help.
Got it working with Lucian's help
I changed the mapper to the following
cfg.CreateMap<Data.Block, Domain.LiveBlock>().PreserveReferences();
cfg.CreateMap<Data.Block, Domain.UnsequencedBlock>().PreserveReferences();
cfg.CreateMap<Data.Block, Domain.Block>().PreserveReferences().ConstructUsing((block, context) =>
{
if (block.Type == BlockType.Live)
{
var b = new LiveBlock();
return context.Mapper.Map(block, b, context);
}
if (block.Type == BlockType.Unsequenced)
{
var unsequencedBlock = new UnsequencedBlock();
return context.Mapper.Map(block, unsequencedBlock, context);
}
return context.Mapper.Map<Domain.Block>(block);
});
cfg.CreateMap<Data.BlockLocation, Domain.BlockLocation>().PreserveReferences();
The secred was usint the Map method that takes the context as a parameter
context.Mapper.Map(block, unsequencedBlock, context);
I'm currently working on an ASP.NET application in .NET 4.5. Currently I'm retrieving 2 View results from my database using an IQueryable:
MainProject
SubProject
MainProjects and SubProjects have different properties, but do have 1 property in common, an ObjectNumber, which I defined in an interface ISearchResult.
I would like to write an Expression to filter both View results with only 1 method, instead of writing the same coding twice.
My View result classes and the interface look like this:
public interface ISarchResult
{
int ID { get; set; }
string ObjectNr { get; set; }
}
public class MainProject : ISearchResult
{
public int ID { get; set; }
public DateTime? CreationDate { get; set; }
public string Title { get; set; }
public string ObjectNr { get; set; }
}
public class SubProject : ISuchResultat
{
public int ID { get; set; }
public string ObjectNr { get; set; }
public short State { get; set; }
public int? Anything { get; set; }
public int? Something { get; set; }
}
In my Code Behind class I have my filter method, which looks like this:
private IQueryable<MainProject> FilterObjectNumbers(IQueryable<MainProject > result)
{
var objectNumbers = this.objectNrField.Text.Split(',').Select(objNr => objNr.Trim()).Where(t => !string.IsNullOrEmpty(t)).ToList();
if (objectNumbers.Count > 0)
{
var projectIDs = objectNumbers.Select(objNr => Util.StringToInt(objNr)).Where(objNr => objNr > 0).ToList();
result = result.Where(i => objectNumbers.Contains(i.ObjectNr) || projectIDs.Contains(i.PK_ID));
}
return result;
}
Do you know how to transform this Method into a query Expression, to filter my IQueryable for type MainProject as well as type SubProject?
Is it possible to put this Expression Filter method into an extension method?
Create a generic extension method with a ISarchResult constraint
public static IQueryable<T> FilterObjectNumbers<T>(this IQueryable<T> result, string objectNrFieldText)
where T : ISarchResult {
var objectNumbers = objectNrFieldText.Split(',').Select(objNr => objNr.Trim()).Where(t => !string.IsNullOrEmpty(t)).ToList();
if (objectNumbers.Count > 0) {
var projectIDs = objectNumbers.Select(objNr => Util.StringToInt(objNr)).Where(objNr => objNr > 0).ToList();
result = result.Where(i => objectNumbers.Contains(i.ObjectNr) || projectIDs.Contains(i.ID));
}
return result;
}
It can then be used
//assuming IQueriable<MainProject> mainProjectResult
var filteredResult = mainProjectResult.FilterObjectNumbers(this.objectNrField.Text);
I am building WebApi2 project to expose some RESTful service. Let's say I have following model objects.
public class Person
{
public string Name { get; set; }
public DateTime? Birthdate { get; set; }
public int Status { get; set; }
public List<Account> Accounts { get; set; }
}
public class Account
{
public decimal Amount { get; set; }
public string Code { get; set; }
public DateTime Expiry { get; set; }
}
In my service I have to go to 2 different systems to retrieve data for Person and the account info of the Person. Obviously the service implementation looks like
[HttpGet]
[Route("Person/{id:int}")]
public IHttpActionResult Get(string id)
{
var person = new Person();
person = GetPersonFromSystemA(id);
if (person.Status == 2)
{
person.Accounts = GetPersonAccountsFromSystemB(id);
}
return this.Ok(person);
}
I cannot use EF at all in this case, so OData is very tricky.
I have some requirement that I need to provide the filtering capability to the service client. The client can decide which fields of the objects to return, it also means that if the client does not like to include Accounts info of the person I should skip the second call to system B to avoid entire child object.
I did some quick search but I could not find some similar solution yet. I know I can implement my own filtering syntax, and have all custom codes to use the filtering (by having lots of if/else).
I am looking for some ideas of more elegant solution.
Entity Framework is not required for building an OData Service. If you do not use OData, you will probably have to implement your own IQueryable which is what OData does out of the box.
Some sample code.
Model classes with some added properties
public class Person
{
[Key]
public String Id { get; set; }
[Required]
public string Name { get; set; }
public DateTime? Birthdate { get; set; }
public int Status { get; set; }
public List<Account> Accounts { get; set; }
}
public class Account
{
[Key]
public String Id { get; set; }
[Required]
public decimal Amount { get; set; }
public string Code { get; set; }
public DateTime Expiry { get; set; }
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapODataServiceRoute("odata", null, GetEdmModel(), new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
config.EnsureInitialized();
}
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.Namespace = "YourNamespace";
builder.ContainerName = "DefaultContainer";
builder.EntitySet<Person>("People");
builder.EntitySet<Account>("Accounts");
var edmModel = builder.GetEdmModel();
return edmModel;
}
}
Controller method
[EnableQuery]
public class PeopleController : ODataController
{
public IHttpActionResult Get()
{
return Ok(SomeDataSource.Instance.People.AsQueryable());
}
}
You will need to include the Microsoft.AspNet.OData Nuget package.
Refer to the following for more guidance. It uses an in memory data source, but the concept is the same regardless.
http://www.odata.org/blog/how-to-use-web-api-odata-to-build-an-odata-v4-service-without-entity-framework/
When building a web api you would often want to filter your response and get only certain fields. You could do it in many ways, one of which as suggested above. Another way, you could approach it is using data shaping in your web api.
If you had a controller action as such:
public IHttpActionResult Get(string fields="all")
{
try
{
var results = _tripRepository.Get();
if (results == null)
return NotFound();
// Getting the fields is an expensive operation, so the default is all,
// in which case we will just return the results
if (!string.Equals(fields, "all", StringComparison.OrdinalIgnoreCase))
{
var shapedResults = results.Select(x => GetShapedObject(x, fields));
return Ok(shapedResults);
}
return Ok(results);
}
catch (Exception)
{
return InternalServerError();
}
}
And then your GetShapedData method can do the filtering as such:
public object GetShapedObject<TParameter>(TParameter entity, string fields)
{
if (string.IsNullOrEmpty(fields))
return entity;
Regex regex = new Regex(#"[^,()]+(\([^()]*\))?");
var requestedFields = regex.Matches(fields).Cast<Match>().Select(m => m.Value).Distinct();
ExpandoObject expando = new ExpandoObject();
foreach (var field in requestedFields)
{
if (field.Contains("("))
{
var navField = field.Substring(0, field.IndexOf('('));
IList navFieldValue = entity.GetType()
?.GetProperty(navField, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public)
?.GetValue(entity, null) as IList;
var regexMatch = Regex.Matches(field, #"\((.+?)\)");
if (regexMatch?.Count > 0)
{
var propertiesString = regexMatch[0].Value?.Replace("(", string.Empty).Replace(")", string.Empty);
if (!string.IsNullOrEmpty(propertiesString))
{
string[] navigationObjectProperties = propertiesString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
List<object> list = new List<object>();
foreach (var item in navFieldValue)
{
list.Add(GetShapedObject(item, navigationObjectProperties));
}
((IDictionary<string, object>)expando).Add(navField, list);
}
}
}
else
{
var value = entity.GetType()
?.GetProperty(field, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public)
?.GetValue(entity, null);
((IDictionary<string, object>)expando).Add(field, value);
}
}
return expando;
}
Check my blog for a detailed post: https://jinishbhardwaj.wordpress.com/2016/12/03/web-api-supporting-data-shaping/