Automapper complex types mapping exception - c#

I'm trying to implement the AutoMapper for a new module.
I have the MVC model at the web site, I'm working on, and it looks like this:
public class MvcModel
{
public Params Params { get; set; }
public Steps Steps { get; set; }
}
public class Params
{
public int? RequestId { get; set; }
public bool NewClient { get; set; }
}
public class Steps
{
public Step1 Step1 { get; set; }
public Step2 Step2 { get; set; }
public Step3 Step3 { get; set; }
}
public class Step1
{
public int Name { get; set; }
}
public class Step2
{
public int Phone { get; set; }
}
public class Step3
{
public int Email { get; set; }
}
For the other hand I have next class, that I have to pass to some service:
public class Request
{
public Parameters Parameters { get; set; }
public RequestContent RequestContent { get; set; }
}
public class Parameters
{
public int NewClient { get; set; }
}
public class RequestContent
{
public int Id { get; set; }
public int InnerId { get; set; }
public string Session { get; set; }
public string Clerk { get; set; }
public bool Private { get; set; }
public PersonalDetails PersonalDetails { get; set; }
public Phones Phones { get; set; }
public ElectonicCommunication ElectonicCommunication { get; set; }
}
public class PersonalDetails
{
public int Name { get; set; }
}
public class Phones
{
public int Phone { get; set; }
}
public class ElectonicCommunication
{
public int Email { get; set; }
}
I've created a new AutoMapper configuration class (that is called from Global.asax):
public class AutoMapperConfig
{
public static void Configure()
{
MapperConfiguration MapperConfiguration = new MapperConfiguration(cfg => {
cfg.AddProfile<Out>();
cfg.AddProfile<In>();
cfg.CreateMap<MvcModel, Request>();
});
MapperConfiguration.AssertConfigurationIsValid();
}
}
public class Out: Profile
{
protected override void Configure()
{
CreateMap<MvcModel, Request>()
.ForMember(dest => dest.Parameters, opt => opt.MapFrom(src => src.Params))
.ForMember(dest => dest.RequestContent, opt => opt.MapFrom(src => src.Steps));
}
}
public class In: Profile
{
protected override void Configure()
{
CreateMap<Request, MvcModel>()
.ForMember(dest => dest.Params, opt => opt.MapFrom(src => src.Parameters))
.ForMember(dest => dest.Steps, opt => opt.MapFrom(src => src.RequestContent));
}
}
Those are schematically similar, though the real object are a bit bigger and have different names.
Excpectations: MvcModel to be mapped to Request and backwards.
To be more accurate:
MvcModel.Params to be mapped to Request.Parameters and backwards
MvcModel.Steps to be mapped to Request.RequestContent and backwards
InnerId, Session, Clerk, Private from Request.RequestContent must be ignored
The problem: I'm getting an error, when on application start:
The following property on ... cannot be mapped:
Add a custom mapping expression, ignore, add a custom resolver, or modify
the destination type ...
Context:
Mapping to property ... from ... to ...
Mapping from type ... to ...
from source type AutoMapperMessageBugTests.SourceChild Mapping to type
Exception of type 'AutoMapper.AutoMapperConfigurationException' was thrown.
I didn't find good documentation on how to use AutoMapper globally for the application, and just can't figure how to use it properly and what exactly I'm doing wrong.
I couldn't find a good example of complex objects mapping ether...
Can someone help me with this?

Thank's to stuartd this are working now!
The thing that I didn't understand was, that I have to map bottom to top! All the sub-objects have to mapped first, so they would be recognized at the moment parent objects are mapped!
Now profile's looks like this:
public class Out: Profile
{
protected override void Configure()
{
CreateMap<Step1, PersonalDetails>();
CreateMap<Step2, Phones>();
CreateMap<Step3, ElectonicCommunication>();
CreateMap<Params, Parameters>();
CreateMap<Params, RequestContent>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.RequestId))
.ForAllMembers(opt => opt.Ignore());
CreateMap<Steps, RequestContent>()
.ForMember(dest => dest.PersonalDetails, opt => opt.MapFrom(src => src.Step1))
.ForMember(dest => dest.Phones, opt => opt.MapFrom(src => src.Step2))
.ForMember(dest => dest.ElectonicCommunication, opt => opt.MapFrom(src => src.Step3))
.ForAllMembers(opt => opt.Ignore());
CreateMap<MvcModel, Request>()
.ForMember(dest => dest.Parameters, opt => opt.MapFrom(src => src.Params))
.ForMember(dest => dest.RequestContent, opt => opt.MapFrom(src => src.Steps));
}
}

Related

Automapper map list of object to another object in different list of objects, nested mapping

public class First
{
public List<ClassA> AList{ get; set; }
}
public class ClassA
{
public string FirstName{ get; set; }
public string LastName{ get; set; }
}
public class Second
{
public List<ClassB> BList{ get; set; }
}
public class ClassB
{
public ClassC Cobj { get; set; }
public string Email { get; set; }
}
public class ClassC
{
public string FirstName{ get; set; }
public string LastName{ get; set; }
}
unable to get results for converting ClassA to ClassC, while trying to map First and Second
I think the Reverse Mapping and Unflattening could help
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<First, Second>()
.ForMember(dest => dest.BList, opt => opt.MapFrom(src => src.AList));
cfg.CreateMap<ClassB, ClassA>()
.ForMember(dest => dest.FirstName, opt => opt.MapFrom(src => src.Cobj.FirstName))
.ForMember(dest => dest.LastName, opt => opt.MapFrom(src => src.Cobj.LastName))
.ReverseMap();
});

Automapper map nested different object properties

I have the following Company and its nested object CompanyEmployee:
public class Company
{
public string Id { get; set; }
public string LogoPath { get; set; }
public string RammeId { get; set; }
public List<CompanyEmployee> Employees { get; set; }
}
public class CompanyEmployee
{
public string Id { get; set; }
[ForeignKey("Company")]
public string CompanyId { get; set; }
public Company Company { get; set; }
public string EmployeeId { get; set; }
}
Now I want to map the Entities to Dtos defined as the following objects CompanyDto and its nested object EmployeeDto:
public class CompanyDto
{
[Required]
public string Id { get; set; }
[Required]
public string Name { get; set; }
public string LogoPath { get; set; }
public string RammeId { get; set; }
public IFormFile FormFile { get; set; }
public List<EmployeeDto> Employees { get; set; }
}
public class EmployeeDto
{
public string Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public List<RoleDto> Roles { get; set; }
}
My problem is the CompanyEmployee to EmployeeDto mapping.
How can I create a map that can take the property EmployeeId and map it to the Id property of EmployeeDto?
Currently, I have the following maps:
CreateMap<EmployeeDto, CompanyEmployee>(MemberList.Destination)
.ForMember(emp => emp.EmployeeId, opt => opt.MapFrom(ce => ce.Id));
CreateMap<CompanyDto, Company>(MemberList.Destination)
.ForMember(c => c.Employees.Select(e => e.CompanyId), opt => opt.MapFrom(cd => cd.Id));
CreateMap<Company, CompanyDto>(MemberList.Destination)
.ForMember(c => c.Id, opt => opt.MapFrom(cd => cd.Employees.First().CompanyId));
You want to create an AutoMapper Profile to configure each property mapping.
Create classes that inherit from Profile and put the configuration in the constructor.
For example:
public class EmployeeProfile : Profile
{
//Constructor
public EmployeeProfile()
{
//Mapping properties from CompanyEmployee to EmployeeDto
CreateMap<CompanyEmployee, EmployeeDto>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.EmployeeId));
//Mapping properties from EmployeeDto to CompanyEmployee
CreateMap<EmployeeDto, CompanyEmployee>()
.ForMember(dest => dest.EmployeeId, opt => opt.MapFrom(src => src.Id));
}
}
public class CompanyProfile : Profile
{
//Constructor
public CompanyProfile()
{
//Mapping properties from Company to CompanyDto
CreateMap<Company, CompanyDto>()
.ForMember(dest => dest.Employees, opt => opt.MapFrom(src => src.Employees));
//Mapping properties from CompanyDto to Company
CreateMap<CompanyDto, Company>()
.ForMember(dest => dest.Employees, opt => opt.MapFrom(src => src.Employees))
//Setting CompanyId
.AfterMap((src, dest) => {
foreach (var employee in dest.Employees)
{
employee.CompanyId = dest.Id;
}
});
}
}
AutoMapper Profile Configuration Documentation
Just create Profile and all properties which have the same name will mapped automatically. However, properties which do not have the same names, they should have custom mapping:
public class FromModelToDto : Profile
{
public FromModelToDto ()
{
CreateMap<CompanyEmployee, EmployeeDto>()
.ForMember(dest.Id, opts => opts.MapFrom(model => model.EmployeeId))
}
}
UPDATE:
If you want to map from Dto to Model, then you should create another mapping class:
public class FromDtoToModel : Profile
{
public FromDtoToModel ()
{
CreateMap<EmployeeDto, CompanyEmployee>()
.ForMember(dest.EmployeeId, opts => opts.MapFrom(model => model.Id))
}
}
You can read more about Automapper here.

How to map a single object from multiple ones, using AutoMapper?

AssetAvailability
public class AssetAvailabilityDto
{
public int Id { get; set; }
public int AssetId { get; set; }
public int CalendarId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
Asset
public class AssetDto
{
public int Id { get; set; }
public int CategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool IsActive { get; set; }
public int OwnedBy { get; set; }
public DateTime LastModifiedAt { get; set; }
}
BookingSchedule
public class BookingScheduleDto
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
The destination to hold them all
public class AssetInformationDto
{
public int AssetId { get; set; }
public int CategoryId { get; set; }
public string AssetName { get; set; }
public string AssetDescription { get; set; }
public bool AssetIsActive { get; set; }
public int AssetOwnedBy { get; set; }
public DateTime AssetLastModifiedAt { get; set; }
// End of Assets
public int AvailabilityId { get; set; }
public DateTime AvailabilityStartDate { get; set; }
public DateTime AvailabilityEndDate { get; set; }
// End of Availability
public List<DateTime> BookingStartDate { get; set; } = new List<DateTime>();
public List<DateTime> BookingEndDate { get; set; } = new List<DateTime>();
// End of Booking schedule
}
Where the mapping takes place
AssetInformationDto information = new AssetInformationDto();
AssetDto asset = _assetService.GetAssetById(id);
AssetAvailabilityDto assetAvailability = _assetService.GetAssetAvailabilityByAssetId(id);
IEnumerable<BookingScheduleDto> schedule = _assetService.GetAssetBookingSchedule(id, true);
information = _mapper.Map<AssetInformationDto>(asset);
information = _mapper.Map<AssetInformationDto>(assetAvailability);
information = _mapper.Map<AssetInformationDto>(schedule);
The configuration of AutoMapper
CreateMap<AssetDto, AssetInformationDto>()
.ForMember(dest => dest.AssetName, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.AssetDescription, opt => opt.MapFrom(src => src.Description))
.ForMember(dest => dest.AssetLastModifiedAt, opt => opt.MapFrom(src => src.LastModifiedAt))
.ForMember(dest => dest.AssetIsActive, opt => opt.MapFrom(src => src.IsActive))
.ForMember(dest => dest.AssetOwnedBy, opt => opt.MapFrom(src => src.OwnedBy))
.ForMember(dest => dest.AssetId, opt => opt.MapFrom(src => src.Id))
.ForAllOtherMembers(m => m.Ignore());
CreateMap<AssetAvailabilityDto, AssetInformationDto>()
.ForMember(dest => dest.AvailabilityStartDate, opt => opt.MapFrom(src => src.StartDate))
.ForMember(dest => dest.AvailabilityEndDate, opt => opt.MapFrom(src => src.EndDate))
.ForMember(dest => dest.AvailabilityId, opt => opt.MapFrom(src => src.Id))
.ForAllOtherMembers(m => m.Ignore());
CreateMap<IEnumerable<BookingScheduleDto>, AssetInformationDto>().AfterMap((s, d) =>
{
foreach (var item in s)
{
d.BookingStartDate.Add(item.StartDate);
d.BookingEndDate.Add(item.EndDate);
}
}).ForAllOtherMembers(m => m.Ignore());
This is being done on an ASP.NET Core 2.2 project, hence why AutoMapper is always injected with dependency injection (_mapper is the injected object)
The post shared here https://stackoverflow.com/a/38257912/11272124 is a solution to the above problem, by making a function "EntityMapper" and by slightly modifying it to accept the injected _mapper object, but I'd like to learn an alternative solution.
Isn't this possible with simply configuring AutoMapper to do so?
Maybe the reason for it not to work, is due to my poorly written out configuration.
The problem here is that after each mapped object, (After every information = ...) the previously assigned properties are reverted to their type's default values. That's why I included ForAllOtherMembers.Ignore() in the respective class where I configure how the mapping is done.
What I've been looking for was the usage of Mapper.Map(TSource, TDestination) (Mapper in my case would be _mapper, due to dependency injection.)
It executes a mapping from the source object to the existing destination object.
The method I previously used (Mapper.Map<TDestination>(TSource)) executes a mapping from the source object to a new destination object.
The code before:
AssetInformationDto information = new AssetInformationDto();
AssetDto asset = _assetService.GetAssetById(id);
AssetAvailabilityDto assetAvailability = _assetService.GetAssetAvailabilityByAssetId(id);
IEnumerable<BookingScheduleDto> schedule = _assetService.GetAssetBookingSchedule(id, true);
information = _mapper.Map<AssetInformationDto>(asset);
information = _mapper.Map<AssetInformationDto>(assetAvailability);
information = _mapper.Map<AssetInformationDto>(schedule);
The code that fixed my issue
AssetInformationDto information;
AssetDto asset = _assetService.GetAssetById(id);
AssetAvailabilityDto assetAvailability =
_assetService.GetAssetAvailabilityByAssetId(id);
IEnumerable<BookingScheduleDto> schedule =
_assetService.GetAssetBookingSchedule(id, true);
information = _mapper.Map<AssetInformationDto>(asset);
_mapper.Map(assetAvailability, information);
_mapper.Map(schedule, information);

Automapper many to many mapping in 3-Tier architecture

I have two Entities model on DataAccessLayer:
Consumer
public class Consumer
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public virtual ICollection<Contractor> Contractors { get; set; }
public Consumer()
{
Contractors = new List<Contractor>();
}
}
Contractor
public class Contractor
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public virtual ICollection<Consumer> Consumers { get; set; }
public Contractor()
{
Consumers = new List<Consumer>();
}
}
...and two class on Transfer Data Layer:
ConsumerTransfer
public class ConsumerTransfer
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public IList<ContractorTransfer> ContractorTransfer { get; set; }
}
ContractorTransfer
public class ContractorTransfer
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
}
**
How can I get data from Consumer to ConsumerTransfer?
**
I tried to do it this way
public IEnumerable<ConsumerTransfer> GetСonsumers()
{
Mapper.Initialize(cfg => cfg.CreateMap<Consumer, ConsumerTransfer>()
.ForMember(dto=>dto.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dto => dto.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(dto => dto.PhoneNumber, opt => opt.MapFrom(src => src.PhoneNumber))
.ForMember(dto => dto.ContractorTransfer, opt => opt.MapFrom(src => src.Contractors))
);
return Mapper.Map<IEnumerable<Consumer>, IEnumerable<ConsumerTransfer>>(Database.Consumers.GetAll());
}
First of all, I'd rename your "ContractorTransfer" property to "Contractors". Generally I keep property names the same between DTOs and source types. Not a 100% rule, but unless I have a REALLY good reason on the client side (serialization concerns, whatever), then I don't change property names.
Second, your AutoMapper config is in the wrong place. You need to put the Initialize in the startup of your application, not right next to your mapping.
Third, your configuration is way too verbose. AutoMapper automatically maps properties with the same name.
Fourth, you're missing a map for the second set of source/destination types. Assuming you've fixed your inconsistent property name, here's your config:
Mapper.Initialize(cfg => {
cfg.CreateMap<Consumer, ConsumerTransfer>();
cfg.CreateMap<Contract, ContractTransfer>();
});
Then later on when you execute the map:
Mapper.Map<Consumer, ConsumerTransfer>(consumer);
That's it.
public IEnumerable<ConsumerTransfer> GetСonsumers()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<Consumer, ConsumerTransfer>()
.ForMember(dto => dto.Id, opt => opt.MapFrom(src => src.Id))
.ForMember(dto => dto.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(dto => dto.PhoneNumber, opt => opt.MapFrom(src => src.PhoneNumber))
.ForMember(dto => dto.ContractorTransfer, opt => opt.MapFrom(src => src.Contractors));
cfg.CreateMap<Contractor, ContractorTransfer>();
});
return Mapper.Map<IEnumerable<ConsumerTransfer>>(Database.Consumers.GetAll());
}

AutoMapper referencing a map within a map

Really struggling with how I might accomplish this, I'm basically trying to feed my Index view a list of ClubViewModel but to do this I need to match up my list of Club. I'm using AutoMapper and everything is working well apart from the CalendarEntry stuff which is a List in my database model and a flattened class in my view model.
I don't really want to change my view model around as I have the Create view in the way I want it (bools for each day and associated start/finish times).
First off here is the controller which is successfully mapping most of the class:
public ActionResult Index()
{
var clubsList = _service.GetAllClubs();
IList<ClubViewModel> clubsViewModelList = new List<ClubViewModel>();
foreach (var item in clubsList)
{
clubsViewModelList.Add(AutoMapper.Mapper.Map<ClubViewModel>(item));
}
return View(clubsViewModelList);
}
Here are my Repository Models and Enums and also my Web ViewModels
public class Club
{
public int ClubId { get; set; }
public string Organisation { get; set; }
public List<CalendarEntry> MeetingDays { get; set; }
}
public class CalendarEntry
{
public int CalendarEntryId { get; set; }
public int ClubId { get; set; }
public Days Day { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
}
public enum Days
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
public class ClubViewModel
{
public int ClubId { get; set; }
public string Organisation { get; set; }
public CalendarEntryViewModel CalendarEntryViewModel { get; set; }
}
public class CalendarEntryViewModel
{
public bool IsMonday { get; set; }
public bool IsTuesday { get; set; }
public bool IsWednesday { get; set; }
public bool IsThursday { get; set; }
public bool IsFriday { get; set; }
public DateTime? MondayFrom { get; set; }
public DateTime? MondayTo { get; set; }
public DateTime? TuesdayFrom { get; set; }
public DateTime? TuesdayTo { get; set; }
public DateTime? WednesdayFrom { get; set; }
public DateTime? WednesdayTo { get; set; }
public DateTime? ThursdayFrom { get; set; }
public DateTime? ThursdayTo { get; set; }
public DateTime? FridayFrom { get; set; }
public DateTime? FridayTo { get; set; }
}
Finally here is my AutoMapper configuration method:
public static void RegisterMappings()
{
Mapper.CreateMap<Club, ClubViewModel>()
.ForMember(dest => dest.CalendarEntryViewModel,
opts => opts.Ignore());
Mapper.CreateMap<CalendarEntry, CalendarEntryViewModel>()
.ForMember(dest => dest.IsMonday, opts => opts.Equals(true))
.ForMember(dest => dest.IsTuesday, opts => opts.Equals(true))
.ForMember(dest => dest.IsWednesday, opts => opts.Equals(true))
.ForMember(dest => dest.IsThursday, opts => opts.Equals(true))
.ForMember(dest => dest.IsFriday, opts => opts.Equals(true))
.ForMember(dest => dest.MondayFrom, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.MondayTo, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.TuesdayFrom, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.TuesdayTo, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.WednesdayFrom, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.WednesdayTo, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.ThursdayFrom, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.ThursdayTo, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.FridayFrom, opts => opts.Equals(DateTime.Now))
.ForMember(dest => dest.FridayTo, opts => opts.Equals(DateTime.Now));
Mapper.AssertConfigurationIsValid();
}
For the time being I have just hardcoded the values of the bools/dates whilst I try and get things working.
Is there anyway I can somehow add the second CalendarEntry to CalendarEntryViewModel Map to the Club to ClubViewModel Map as an additional member or am I doing this completely wrong?
You should unpack the CalendarEntryViewModel so that it more closely represents a calendar entry in your domain. You're currently trying to shove a list into a flattened object .. which will set you up for even more pain later.
Before I get to that, I have to mention:
foreach (var item in clubsList)
{
clubsViewModelList.Add(AutoMapper.Mapper.Map<ClubViewModel>(item));
}
Automapper can handle lists ... so that can be:
var clubsViewModelList = Mapper.Map<IList<Club>, IList<ClubViewModel>>(clubsList);
Unpack the view model
I know this is what you said you didn't want to do .. but you really should. It will make your mapping much simpler between your domain and your view. I am all for keeping them separate for many many reasons, but I think you're not doing yourself any favours by trying to flatten this sort of domain model into a single view model. So unpack it to multiple view models:
public class ClubViewModel
{
public int ClubId { get; set; }
public string Organisation { get; set; }
public IList<CalendarEntryViewModel> CalendarEntries { get; set; } // <--- a list of them
}
Your map then becomes:
.ForMember(dest => dest.IsMonday, opt => opt.MapFrom(src => src.Day == Days.Monday))
.ForMember(dest => dest.IsTuesday, opt => opt.MapFrom(src => src.Day == Days.Tuesday))
/* etc... */
Then, remove all of the nullable dates and just represent this entry with two fields:
public DateTime? From { get; set; }
public DateTime? To { get; set; }
You can then order them by the day:
foreach (var club in clubsViewModelList) {
club.CalendarEntries = club.CalendarEntries.OrderBy(x => x.Day).ToList();
}
Then in your view .. just render them in order:
#foreach (var calendarEntry in club.CalendarEntries) {
Day: #calendarEntry.Day.ToString()
From: #calendarEntry.From.ToString("D")
}
..etc.
When it comes time to create.. you just render your entries as required:
public class ClubCalendarEntryCreateViewModel {
/* other properties here */
public IList<CalendarEntryViewModel> Days { get; set; }
}
Then in your controller:
viewModel.CalendarEntries = new ClubCalendarEntryCreateViewModel {
Days = new List<CalendarEntryViewModel>() {
new CalendarEntryViewModel {
IsMonday = true
},
new CalendarEntryViewModel {
IsTuesday = true
},
/* etc... */
}
};
Then you just render those and accept it back in your POST method. Your mapping on that method becomes:
club.CalendarEntries = Mapper.Map<IList<CalendarEntryViewModel>, IList<CalendarEntry>>(viewModel.CalendarEntries.Days);
I left out a little bit here (like how you would remodel your CalendarEntry domain object) .. hopefully you can fill in the blanks and understand what I mean. I know that seems like a lot of work - but you will be better for it later on. Your mappings won't be your enemy like they are now (they shouldn't ever be your enemy) and your code will become much clearer.
I hope that helps .. its probably not what you want to hear, but its definitely how I would do it (or similar, given more requirements).

Categories

Resources