I am using the following mapping to map my data object to viewmodel object.
ObjectMapper.cs
public static class ObjectMapper
{
public static void Configure()
{
Mapper.CreateMap<User, UserViewModel>()
.ForMember(dest => dest.Title,
opt => opt.ResolveUsing<TitleValueResolver>())
.ForMember(dest => dest.Name,
opt => opt.ResolveUsing<NameValueResolver >())
.ForMember(dest => dest.ShortName,
opt => opt.ResolveUsing<ShortNameValueResolver >());
}
}
Parser
public class Parser{
public string GetTitle(string title){
/* add some logic */
return title;
}
public string GetName(string title){
/* add some logic */
return name;
}
public string GetShortName(string title){
/* add some logic */
return shortname;
}
}
AutoMapperCustomResolvers.cs
public class TitleValueResolver : ValueResolver<User, string>
{
private readonly BaseValueResolver _baseResolver;
public TitleValueResolver()
{
_baseResolver = new BaseValueResolver();
}
protected override string ResolveCore(Usersource)
{
return _baseResolver.Parser.GetTitle(source.TITLE);
}
}
public class NameValueResolver : ValueResolver<User, string>
{
private readonly BaseValueResolver _baseResolver;
public NameValueResolver()
{
_baseResolver = new BaseValueResolver();
}
protected override string ResolveCore(Usersource)
{
return _baseResolver.Parser.GetName(source.TITLE);
}
}
public class ShortNameValueResolver : ValueResolver<User, string>
{
private readonly BaseValueResolver _baseResolver;
public ShortNameValueResolver()
{
_baseResolver = new BaseValueResolver();
}
protected override string ResolveCore(Usersource)
{
return _baseResolver.Parser.GetShortName(source.TITLE);
}
}
I am using the above code to add logic to the destination property using the separate custom value resolvers. Not sure is this the right approach.
i) Is there a better way to achieve this?
ii) And how to use unity to resolve in case i want to inject some dependency to custom resolver constructor?
Thanks
As I understand your question, you want to utilize a ValueResolver, that resolves multiple source properties into an intermediate data object, which is used to resolve multiple target properties. As an example, I assume the following source, target, intermediate and resolver types:
// source
class User
{
public string UserTitle { get; set; }
}
// target
class UserViewModel
{
public string VM_Title { get; set; }
public string VM_OtherValue { get; set; }
}
// intermediate from ValueResolver
class UserTitleParserResult
{
public string TransferTitle { get; set; }
}
class TypeValueResolver : ValueResolver<User, UserTitleParserResult>
{
protected override UserTitleParserResult ResolveCore(User source)
{
return new UserTitleParserResult { TransferTitle = source.UserTitle };
}
}
You need a target property in order to utilize opt.ResolveUsing<TypeValueResolver>(). This means, you can establish a mapping, where an appropriate target property is available.
So, for the moment, lets wrap the result into an appropriate container type:
class Container<TType>
{
public TType Value { get; set; }
}
And create a mapping
Mapper.CreateMap<User, Container<UserViewModel>>()
.ForMember(d => d.Value, c => c.ResolveUsing<TypeValueResolver>());
And another mapping
Mapper.CreateMap<UserTitleParserResult, UserViewModel>()
.ForMember(d => d.VM_Title, c => c.MapFrom(s => s.TransferTitle))
.ForMember(d => d.VM_OtherValue, c => c.Ignore());
And another mapping
Mapper.CreateMap<User, UserViewModel>()
.BeforeMap((s, d) =>
{
Mapper.Map<User, Container<UserViewModel>>(s, new Container<UserViewModel> { Value = d });
})
.ForAllMembers(c => c.Ignore());
// establish more rules for properties...
The last mapping is a bit special, since it relies on a nested mapping in order to update the destination with values from source via separately configured mapping rules. You can have multiple different transfer mappings for different properties by adding appropriate intermediate mappings and calls in BeforeMap of the actual mapped type. The properties that are handled in other mappings need to be ignored, since AutoMapper doesn't know about the mapping in BeforeMap
Small usage example:
var user = new User() { UserTitle = "User 1" };
// create by mapping
UserViewModel vm1 = Mapper.Map<UserViewModel>(user);
UserViewModel vm2 = new UserViewModel() { VM_Title = "Title 2", VM_OtherValue = "Value 2" };
// map source properties into existing target
Mapper.Map(user, vm2);
Dunno if this helps you. There might be better ways if you rephrase your question to describe your initial problem instead of what you suspect to be a solution.
Related
I have this model with following attributes. (Simplified)
public class Blog {
private string Code { get; set; }
private string Name { get; set; }
private byte[] Image { get; set; }
}
When I make a request to the OData URL for ex: http://localhost/api/odata/Blog, I want only Code and Name properties to be returned, ignoring the Image. And if I make
a request something like http://localhost/api/odata/Blog?$select=(Code,Name,Image) then I want the Image to be returned. How can I make this work?
Using attributes like [IgnoreDataMember] makes it unavailable for OData query to be accessed, therefore it is not a suitable solution.
First, probably properties of the Blog class are public, not private.
I had a similar scenario and resolve it by implementing a custom serializer:
Serializer provider class:
public class MyODataSerializerProvider : DefaultODataSerializerProvider
{
MyResourceSerializer myResourceSerializer;
public MyODataSerializerProvider(IServiceProvider serviceProvider) : base(serviceProvider)
{
myResourceSerializer = new MyResourceSerializer(this);
}
public override ODataEdmTypeSerializer GetEdmTypeSerializer(IEdmTypeReference edmType)
{
if (edmType.IsEntity())
{
return myResourceSerializer;
}
return base.GetEdmTypeSerializer(edmType);
}
}
Serializer class:
public class MyResourceSerializer : ODataResourceSerializer
{
public MyResourceSerializer(ODataSerializerProvider serializerProvider) : base(serializerProvider) { }
public override ODataResource CreateResource(SelectExpandNode selectExpandNode, ResourceContext resourceContext)
{
var resource = base.CreateResource(selectExpandNode, resourceContext);
if (selectExpandNode.SelectAllDynamicProperties)
{
resource.Properties = resource.Properties.Where(p => p.Name != "Image");
}
return resource;
}
}
And configuration of course:
routeBuilder.MapODataServiceRoute("OData", "odata", b =>
{
b.AddService(Microsoft.OData.ServiceLifetime.Singleton, sp => edmModel);
var conventions = ODataRoutingConventions.CreateDefault();
//Workaround for https://github.com/OData/WebApi/issues/1622
conventions.Insert(0, new AttributeRoutingConvention("OData", app.ApplicationServices, new DefaultODataPathHandler()));
//Custom Convention
b.AddService<IEnumerable<IODataRoutingConvention>>(Microsoft.OData.ServiceLifetime.Singleton, a => conventions);
b.AddService(Microsoft.OData.ServiceLifetime.Singleton, typeof(ODataSerializerProvider), sp => new MyODataSerializerProvider(sp));
});
I have requirement to map two objects. The requirement is train reservation details which will have towardsjourney and return journey details.
public class ReservationSource
{
public string ReservationNumber{get;set;}
public TravelS towardsTravelS{get;set;}
public TravelS returnTravelS{get;set;}
}
This is the class which is in the ReservationSource class which is to capture towards and return journey details.
public class TravelS
{
public string travelId{get;set;}
public ICollection<JourneyS> Journeys{get;set;}
}
Above is the reservation source object. This source needs a mapping to the destination object. Destination object is given below.
public class ReservationDestination
{
public string ReservationNumber{get;set;}
public TravelD towardsTravelD{get;set;}
public TravelD returnTravelD{get;set;}
}
public class TravelD
{
public string travelId{get;set;}
public ICollection<JourneyD> Journeys{get;set;}
}
public class JourneyD
{
public string JourneyId{get;set;}
}
public class JourneyS
{
public string JourneyId{get;set;}
}
This is my destination object . Here i want to map my source to destination. How do i define mapping config and map .
var config = new mappingConfiguration(cfg=>
{
cfg.CreateMap<ReservationSource,ReservationDestination>()
});
Imapper map = config.CreateMapper();
This part of code maps only the reservationNumber to the destination object. Can someone help me to map all objects. That is towardsTravelS to towardsTravelD and returnTravelS to returnTravelD.
.net core version : 3.1
First of all you forgot to mention this but I assume there also is a class TravelS that looks like this:
public class TravelS
{
public string TravelId { get; set; }
}
There are a few things missing in your configuration. At the moment AutoMapper doesn't know it has to map properties with different names (TowardsTravelS => TowardsTravelD etc) so we have to define those aswell:
cfg.CreateMap<ReservationSource, ReservationDestination>()
.ForMember(dest => dest.ReturnTravelD, opt => opt.MapFrom(src => src.ReturnTravelS))
.ForMember(dest => dest.TowardsTravelD, opt => opt.MapFrom(src => src.TowardsTravelS));
Here we tell AutoMapper that these properties that have different names need to be mapped.
Secondly TravelS and TravelD are different classes so we need to configure them for mapping as well:
cfg.CreateMap<TravelS, TravelD>();
So we now have something like this:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ReservationSource, ReservationDestination>()
.ForMember(dest => dest.ReturnTravelD, opt => opt.MapFrom(src => src.ReturnTravelS))
.ForMember(dest => dest.TowardsTravelD, opt => opt.MapFrom(src => src.TowardsTravelS));
cfg.CreateMap<TravelS, TravelD>();
});
var mapper = config.CreateMapper();
var source = new ReservationSource
{
ReservationNumber = "9821",
ReturnTravelS = new TravelS
{
TravelId = "1"
},
TowardsTravelS = new TravelS
{
TravelId = "2"
}
};
var destination = mapper.Map<ReservationDestination>(source);
Console.WriteLine(JsonSerializer.Serialize(destination));
Output:
{"ReservationNumber":"9821","TowardsTravelD":{"TravelId":"2"},"ReturnTravelD":{"TravelId":"1"}}
Try it for yourself here: https://dotnetfiddle.net/FfccVR
Add this in your services in startup :
it's reusable and cleaner
public void ConfigureServices(IServiceCollection services)
{
services.AddAutoMapper(Assembly.GetExecutingAssembly());
}
add these interface and class in your project
public interface IMapFrom<T>
{
void Mapping(Profile profile) => profile.CreateMap(typeof(T), GetType());
}
using AutoMapper;
using System;
using System.Linq;
using System.Reflection;
public class MappingProfile : Profile
{
public MappingProfile()
{
ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly());
}
private void ApplyMappingsFromAssembly(Assembly assembly)
{
var types = assembly.GetExportedTypes()
.Where(t => t.GetInterfaces()
.Any(i =>i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
.ToList();
foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod("Mapping")
?? type.GetInterface("IMapFrom`1").GetMethod("Mapping");
methodInfo?.Invoke(instance, new object[] { this });
}
}
}
and your source model be like this (map ReservationSource to ReservationSource):
public class ReservationSource : IMapFrom<ReservationSource>
{
public string Name { get; set; }
public string City { get; set; }
public void Mapping(Profile profile)
{
profile.CreateMap<ReservationSource,ReservationDestination>()
.ForMember(dest => dest.ReturnTravelD, opt => opt.MapFrom(src => src.ReturnTravelS))
.ForMember(dest => dest.TowardsTravelD, opt => opt.MapFrom(src => src.TowardsTravelS));
}
}
With the following example (LinqPad):
void Main()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, DestinationNested>()
.ConstructUsing((source, context) => new DestinationNested(source.InnerValue));
cfg.CreateMap<Source, DestinationOuter>()
.ForMember(x => x.OuterValue, y => y.MapFrom(z => z.OuterValue))
.ConstructUsing((source, context) =>
{
return new DestinationOuter(source.OuterValue, context.Mapper.Map<DestinationNested>(source));
});
});
var src = new Source { OuterValue = 999, InnerValue = 111 };
var mapper = config.CreateMapper();
var mapped = mapper.Map<DestinationOuter>(src);
mapped.Dump();
mapper.ConfigurationProvider.AssertConfigurationIsValid();
}
public class Source
{
public int OuterValue { get; set; }
public int InnerValue { get; set; }
}
public class DestinationOuter
{
public int OuterValue { get; private set; }
public DestinationNested destinationNested { get; private set; }
public DestinationOuter(int outerValue, DestinationNested destinationNested)
{
this.OuterValue = outerValue;
this.destinationNested = destinationNested;
}
}
public class DestinationNested
{
public int NestedValue { get; private set; }
public DestinationNested(int nestedValue)
{
this.NestedValue = nestedValue;
}
}
AssertConfigurationIsValid() currently throws an exception regarding the properties as I'm using ContructUsing.
In practice it does map correctly, but I'd like AssertConfigurationIsValid as part of my test suite to look for regressions (without needing to do manual tests of the mapper).
I'd like reassurance that all my properties are mapped from the source to the destination via the contructor. I wish to use a contructor as it's my Domain tier and the contructor enforces the mandatory items.
I don't wish to ignore all private setters via the IgnoreAllPropertiesWithAnInaccessibleSetter() feature, as I might be ignoring something which I haven't actually set.
Ideally I also don't want to need to do manual Ignore() on each of the properties which appear in the constructor as that leaves a gap for code drift.
I've tried various combinations in Automapper but no luck so far.
I suppose it's a static analysis challenge; I wish to know that my contructor covers all properties in the Destination. And I wish to know that the contructor is being passed everything from the source.
I realise that Automapper isn't doing very much automatic at this point, is there a nice way to lean on automapper for this testing or is this instead a static analysis problem?
Here's my take.
static void Main(string[] args)
{
try{
var mapperCfg = new AutoMapper.MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, DestinationOuter>().ForCtorParam("destinationNested", o => o.MapFrom(s => new DestinationNested(s.InnerValue)));
});
mapperCfg.AssertConfigurationIsValid();
var mapper = mapperCfg.CreateMapper();
var src = new Source { OuterValue = 999, InnerValue = 111 };
mapper.Map<DestinationOuter>(src).Dump();
}catch(Exception ex){
ex.ToString().Dump();
}
}
public class Source
{
public int OuterValue { get; set; }
public int InnerValue { get; set; }
}
public class DestinationOuter
{
public int OuterValue { get; }
public DestinationNested DestinationNested { get; }
public DestinationOuter(int outerValue, DestinationNested destinationNested)
{
this.OuterValue = outerValue;
this.DestinationNested = destinationNested;
}
}
public class DestinationNested
{
public int NestedValue { get; private set; }
public DestinationNested(int nestedValue)
{
this.NestedValue = nestedValue;
}
}
After reading lots of docs, stepping through the integration tests with the debugger and a few days of good'ol experimentation, this is the best I have:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Source, DestinationNested>()
.ForCtorParam("nestedValue", x => x.MapFrom(y => y.InnerValue))
.ForMember(x => x.NestedValue, x => x.MapFrom(y => y.InnerValue));
cfg.CreateMap<Source, DestinationOuter>()
.ForPath(x => x.destinationNested.NestedValue, x => x.MapFrom(y => y.InnerValue))
.ForCtorParam("destinationNested", x => x.MapFrom(y => y));
});
I'm pretty happy with it; it's getting rid of the ContructUsing() smell which in my broader codebase was constructing the nested objects. And it's warning me if my destination object is not populated. Ideally, the constructor param string would be type safe, but I understand why it cannot (perhaps this is something for a fun Roslyn code analyser project for another day :-) )
The secret sauce (hot of the press) was the x => x.MapFrom(y => y) coupled with the .ForPath(x => x.destinationNested.NestedValue, x => x.MapFrom(y => y.InnerValue)) it seemed to give AutoMapper enough hints that the destinationNested was related to InnerValue and that the contructor param was renamed to "destinationNested". The magic came in that rather than using the context to contruct the nested object the innocent looking x.MapFrom(y => y) seemed to let it use the property mapping instead*.
*That's my layman's explanation, I haven't yet followed enough of the AutoMapper source code to understand the relationship between property mapping and constructor mapping truely. Reading through some GitHub tickets, I thought they were separate concepts.
I also haven't seen x.MapFrom(y => y) mentioned in the docs, so I'd be interested in learning more about it.
This is what I am currently using (simplified and run in console app):
public class SomeValueResolver : ValueResolver<DateTime, long>
{
private readonly ISomeDependency _someDependency;
public SomeValueResolver(ISomeDependency _someDependency)
{
// ...
}
protected override long ResolveCore(DateTime source)
{
// ...
}
}
public class MyRegistry : Registry
{
public MyRegistry()
{
For<ISomeDependency >()
.Singleton()
.Use<SomeDependency>();
}
}
public static class AutoMapperConfiguration
{
public static void Configure(IContainer container)
{
Mapper.Initialize(cfg =>
{
cfg.ConstructServicesUsing(t => container.GetInstance(t));
cfg.AddProfile(new AutomapperProfile1());
});
}
}
public class AutomapperProfile1 : Profile
{
protected override void Configure()
{
CreateMap<Source, Target>()
.ForMember(dest => dest.Y, opt => opt.ResolveUsing<SomeValueResolver>().FromMember(e => e.X))
.IgnoreAllSourcePropertiesWithAnInaccessibleSetter();
}
}
public class Source
{
public DateTime X { get; set; }
}
public class Target
{
public DateTime Y { get; set; }
}
// main method
var container1 = new Container(new MyRegistry());
AutoMapperConfiguration.Configure(container1);
var source = new Source { X = DateTime.UtcNow };
var target = Mapper.Map<Target>(source);
Unfortunately, I get an exception along those lines:
Unable to create a build plan for concrete type SomeValueResolver
new SomeValueResolver(ISomeDependency)
? ISomeDependency= **Default**
1.) Attempting to create a BuildPlan for Instance of SomeValueResolver -- SomeValueResolver
2.) Container.GetInstance(SomeValueResolver)
Can this be resolved (pardon the pun).
I've tried your code with StructureMap 4.0.1.318 and Automapper 4.2.0.0.
I did get an exception which is different because related to bad conversion of DateTime to Int64.
I think you intended to write this :
public class Target
{
public long Y { get; set; }
}
By changing the type, the mapping works like a charm.
It's maybe related to the SomeDependency class which should possess a parameterless constructor to be resolved that way.
I have a viewmodel set up that contains several physical path strings that point to files on a server.
I have virtual directories pointing to the root folders on the server.
What I need to do is change this physical paths into paths that reference the virtual folders, so I can create hyper links in my page views.
What I am hoping to do is have a common resolver that I can pass a key to, then have it return the path that I need.
Here is how I did it. I just want to see if there is a more simple "cleaner" way of doing it.
I have only been working with mvc and c# for a couple of months, so I am still learning.
Thank in advance.
Here is the mapper info
Mapper.CreateMap<FAG_DETAIL, OrderFagDetail>()
.ForMember(dest => dest.TXT01, opt => opt.ResolveUsing<DrawingPathCorrect>())
.ForMember(dest => dest.TXT02, opt => opt.ResolveUsing<Prog1PathCorrect>())
.ForMember(dest => dest.TXT03, opt => opt.ResolveUsing<Prog2PathCorrect>())
.ForMember(dest => dest.TXT04, opt => opt.ResolveUsing<Prog3PathCorrect>())
.ForMember(dest => dest.TXT05, opt => opt.ResolveUsing<Prog4PathCorrect>())
.ForMember(dest => dest.TXT07, opt => opt.ResolveUsing<Procs1PathCorrect>())
.ForMember(dest => dest.TXT08, opt => opt.ResolveUsing<Procs2PathCorrect>())
.ForMember(dest => dest.TXT09, opt => opt.ResolveUsing<Procs3PathCorrect>())
.ForMember(dest => dest.TXT10, opt => opt.ResolveUsing<Procs4PathCorrect>())
.ForMember(dest => dest.TXT11, opt => opt.ResolveUsing<FASPathCorrect>())
.ForMember(dest => dest.TXT06, opt => opt.ResolveUsing<SecondDrawingPathCorrect>());
Here is ModelView along with the resolver that I am currently using. The model FAG_DETAIL that is the soruce for OrderfagDetail is very large so I will not included it here. It is safe to assume that in the source there are properties that match the ModelView properties exactly.
public class OrderFagDetail
{
public decimal NO { get; set; }
public decimal FKNO { get; set; }
public decimal TYP { get; set; }
public string TXT01 { get; set; } //drawing link
public string TXT02 { get; set; } //First Op program or L20 Program
public string TXT03 { get; set; } //Second op program or K16 Program
public string TXT04 { get; set; } //Third op Program
public string TXT05 { get; set; } //Fourth op Program
public string TXT06 { get; set; } //Second drawing
public string TXT07 { get; set; } //First Op process sheet
public string TXT08 { get; set; } //Second Op process sheet
public string TXT09 { get; set; } //Third Op process sheet
public string TXT10 { get; set; } //Fourth Op process sheet
public string TXT11 { get; set; } //First Article link
}
public interface IValueResolver
{
ResolutionResult Resolve(ResolutionResult source);
}
public class SecondDrawingPathCorrect : ValueResolver<FAG_DETAIL, string>
{
protected override string ResolveCore(FAG_DETAIL detail)
{
PathResolver pr = new PathResolver();
return (pr.ResolvePath(detail.TXT06,"PDFs\\"));
}
}
public class PathResolver
{
public string ResolvePath(string strSrc, string strKey)
{
string corrected = "";
if (strSrc.Length > 0)
{
string inputdetail = strSrc;
corrected = inputdetail.Substring(inputdetail.IndexOf(strKey)+strKey.Length, inputdetail.Length - inputdetail.IndexOf(strKey)-strKey.Length);
}
return (corrected);
}
}
maybe for improvement you can make the PathResolver class static or singleton
the difference is
Static Class:
You cannot create the instance of static class.
Loaded automatically by the .NET Framework common language runtime
(CLR) when the program or namespace containing the class is loaded.
Static Class cannot have constructor.
We cannot pass the static class to method.
We cannot inherit Static class to another Static class in C#.
Singleton:
You can create one instance of the object and reuse it.
Singleton instance is created for the first time when the user
requested.
Singleton class can have constructor.
You can create the object of singleton class and pass it to method.
Singleton class does not say any restriction of Inheritance.
We can dispose the objects of a singleton class but not of static
class.
Assuming that you have multiple CreateMaps like this:
Mapper.CreateMap<PdfFileObject, MyViewModel>()
.ForMember( d => d.Uri, opt => opt.ResolveUsing(something with "pdfs")
Mapper.CreateMap<ImageFileObject, MyViewModel>()
.ForMember( d => d.Uri, opt => opt.ResolveUsing(something with "images")
..etc
Create your resolver like this:
public class SecondDrawingPathCorrect<TFILE> : ValueResolver<FAG_DETAIL, string>
where TFILE : FAG_DETAIL
{
private readonly string _replaceKey;
public SecondDrawingPathCorrect(string replaceKey)
{
_replaceKey = replaceKey;
}
protected override string ResolveCore(FAG_DETAIL detail)
{
string corrected = "";
if (detail.TXT06.Length > 0)
{
string inputdetail = detail.TXT06;
corrected = inputdetail.Substring(inputdetail.IndexOf(_replaceKey) + 5, inputdetail.Length - inputdetail.IndexOf("_replaceKey) - 5);
}
return corrected;
}
}
Register your maps like this:
Mapper.CreateMap<PdfFileObject, MyViewModel>()
.ForMember( d => d.Uri, opt => opt.ResolveUsing(new SecondDrawingPathCorrect<PdfFileObject>("PDFs");
For the above to work, your PfdFileObject, ImageFileObject, etc, should implement/inherit FAG_DETAIL.
There is another option too, if you can control FAG_DETAIL, you can expose on it a property string FilePathPrefix (which for PdfFileObject will return "PDFs"). Then you can use it in the resolver itself (i.e. in the "replace" part use detail.FilePrefix instead of _replaceKey).
With the latest one, you can drop the generic part of the resolver , and do registration like this:
Mapper.CreateMap<FAG_DETAIL, MyViewModel>()
.ForMember( d => d.Uri, opt => opt.ResolveUsing<SecondDrawingPathCorrect>()
.Include<PdfFileObject, MyViewModel>()
.Include<ImageFileObject, MyViewModel>()
...more includes;