Automapper: Want to Reuse Code via Interface Mappings - c#

I'm building CMS like application.
For example my BlogPost page contains several widget areas. Each widget hosts a serie of "related" blog posts.
All my views are pure presentational, i build urls, convert datetime and ints into strings in my service layer. I find this approach easier to maintain, since views have zer0 logic. All logic is consolidated into AutoMapper's resolvers, converters and custom transformation logic.
So lets come closer to the problem at hand.
To create Url i need 2 parameters: BlogId and BlogSlug, my urls look like b/{id}/{slug}.html
Im quite happy with that.
In my CSM i use so called "source models", a model that is not view model, but an intermediate representation of it. Why do i have to resort to such wicked solutions ?
Well, lets take a look how a typical data retrieval code can look like in my project:
.Select(x =>
{
Id = x.Id,
BlogId = x.Blog.Id,
BlogSlug = x.Blog.Slug,
// Here is the trap, LINQ provider will throw an exception, since he doesn't know how to translate function into expression
BlogUrl = Url.Action("RenderPost", "BlogController", new { Id = x.Blog.Id, slug = x.Blog.Slug })
}
So thats not an option.
Luckily, we can do this
.Select(x => new
{
Id = x.Id,
BlogId = x.Blog.Id,
BlogSlug = x.Blog.Slug
}
.ToList()
.Select(x => new
{
// This works
BlogUrl = Url.Action("RenderPost", "BlogController", new { Id = x.BlogId, slug = x.BlogSlug })
}
Copy paste this stuff into each and every action method that renders different "intresting blog" parts (they have different visual representation as well cant use same view model) ? Not a good way, so i came up with a solution.
I created "source model", so the code will be
.Select(x => new BlogPostSourceViewModel
{
Id = x.Id,
BlogId = x.Blog.Id,
BlogSlug = x.Blog.Slug
}
.ToList()
.Select(x => x.ToBlogPostViewModel()) // Extension method { return Mapper.Map<>() }
.ToList();
This surely looks better, but i have many different models like BlogPostSourceViewModel, BlogAuthorSourceViewModel, BlogCommentSourceViewModel. They all need this link building logic.
Ok, i extract the needed source data (BlogId, BlogSlug) into an interface
BlogPostSourceViewModel : IBlogPostUrl
BlogAuthorSourceViewModel: IBlogPostUrl
BlogCommentSourceViewModel : IBlogPostUrl
Then i define the mappings
Mapper.CreateMap<BlogPostSourceViewModel, BlogPostViewModel>
.ForMember(dest => dest.BlogUrl, opt => opt.ResolveUsing<BlogPostUrlResolver>())
Mapper.CreateMap<BlogAuthorSourceViewModel, BlogAuthorViewModel>
.ForMember(dest => dest.BlogUrl, opt => opt.ResolveUsing<BlogPostUrlResolver>())
Mapper.CreateMap<BlogCommentSourceViewModel, BlogCommentViewModel>
.ForMember(dest => dest.BlogUrl, opt => opt.ResolveUsing<BlogPostUrlResolver>())
Resolver:
BlogPostUrlResolver : ValueResolver<IBlogPostUrl, String>
// Here goes the url building logic
As you see the more models i have that need blog url the more identical mappings i have to add. This is ok for now, but as project grows it will be painful.
Ideally i would want to have it like this:
Mapper.CreateMap<IBlogPostUrl, SomeOtherInterfaceWithBlogUrlAsString>
.ForMember(dest => dest.BlogUrl, opt => opt.ResolveUsing<BlogPostUrlResolver>())
but Automapper doesn't understand it. And i dont know how if there is other way to do it.
Any ideas ?

If I understand the question correctly...
In order to use AutoMapper mapping capabilities across all of your classes you can provide base class that will use Generics:
public abstract class Base<Entity, ViewModel>
where Entity : EntityObject
where ViewModel : BaseViewModel
{
// you will call this method from your operations class using base
public SomeViewModel GetData()
{
public Entity entityObject = db.Entity.SingleOrDefault();
public ViewModel yourViewModelName = base.Map(entityObject);
return yourViewModelName;
}
....
// this will be defined only once for each mapping direction
// ie. there will be multiple of these.
public static Entity Map(ViewModel typeViewModel)
{
try
{
Mapper.CreateMap<ViewModel, Entity>();
Entity t = Mapper.Map<ViewModel, Entity>(typeViewModel);
return t;
}
catch (Exception exc)
{
throw exc;
}
}
}
// this will be your class for database operations on specific Entity
// that inherits generic base, with its AutoMapping setup.
public class DataBaseOperationsClass : Base<SomeEntity, SomeViewModel>
{
public SomeViewModel Get()
{
return base.GetData();
}
}
Hope this helps !

Related

EF Core with GraphQL

I'm currently exploring the GraphQL development and I'm currently exploring what kind of SQL queries are Generated via EF Core and I observed that no matter that my GraphQL query includes only a few fields the EF Core sends SQL Select for all fields of the Entity.
This is the code I'm using now:
public class DoctorType : ObjectGraphType<Doctors>
{
public DoctorType()
{
Field(d => d.PrefixTitle);
Field(d => d.FName);
Field(d => d.MName);
Field(d => d.LName);
Field(d => d.SufixTitle);
Field(d => d.Image);
Field(d => d.EGN);
Field(d => d.Description);
Field(d => d.UID_Code);
}
}
public class Doctors : ApplicationUser
{
public string Image { get; set; }
[StringLength(50)]
public string UID_Code { get; set; }
}
the query I'm using is
{
doctors{
fName
lName
}
}
The SQL generated selects all fields of the Doctor entity.
Is there any way to further optimize that the generated SQL query from EF Core?
I'm guessing this happens because the DoctorType inherits from ObjectGraphType<Doctors> and not from some Projection of the Doctor, but I can't think of a clever workaround of this?
Any suggestions?
EDIT:
I'm using GraphQL.NET (graphql-dotnet) by Joe McBride version 2.4.0
EDIT 2:
Either I'm doing it wrong or I don't know.
As one of the comments suggested i downloaded GraphQL.EntityFramework Nuget package by SimonCropp
I did all the configuration needed for it:
services.AddDbContext<ScheduleDbContext>(options =>
{
options.UseMySql(Configuration.GetConnectionString("DefaultConnection"));
});
using (var myDataContext = new ScheduleDbContext())
{
EfGraphQLConventions.RegisterInContainer(services, myDataContext);
}
My Object graph Type is looking as follows
public class SpecializationType : EfObjectGraphType<Specializations>
{
public SpecializationType(IEfGraphQLService graphQlService)
:base(graphQlService)
{
Field(p => p.SpecializationId);
Field(p => p.Code);
Field(p => p.SpecializationName);
}
}
My query looks is:
public class RootQuery : EfObjectGraphType
{
public RootQuery(IEfGraphQLService efGraphQlService,
ScheduleDbContext dbContext) : base(efGraphQlService)
{
Name = "Query";
AddQueryField<SpecializationType, Specializations>("specializationsQueryable", resolve: ctx => dbContext.Specializations);
}
}
and I'm using this graphQL query
{
specializationsQueryable
{
specializationName
}
}
The debug log show that the generated SQL query is
SELECT `s`.`SpecializationId`, `s`.`Code`, `s`.`SpecializationName`
FROM `Specializations` AS `s`
even though I want only specializationName field and I'm expecting it to be:
SELECT `s`.`SpecializationName`
FROM `Specializations` AS `s`
UPDATE
I guess so far I didn't understand how graphQL really worked. I thought that there is some behind the scene fetch of data but there isn't.
The primary fetch is done in the query's field resolver :
FieldAsync<ListGraphType<DoctorType>>("doctors", resolve: async ctx => await doctorServices.ListAsync());
and as long the result to the resolver is the full object in my case the resolver return List of Doctors entity, it will query the Database for the whole entity (all fields). No optimisations are done out of the box from GraphQL doesn't matter if you return IQueryable or else of the entity you are querying.
Every conclusion here is thought of mine it is not 100% guaranteed right
So what I've did is create a group of Helper methods which are creating an selection Expression to use in the LINQ query. The helpers are using resolver's context.SubFields property to get the fields needed.
The problem is that you need for every level of the query only the leaves, say some query "specializations" with "SpecializationName" and "Code" and the "Doctors" with their "Name" and else. In this case in the RootQuery specializations field's resolver you need only the Specializations entity projection so: SpecializationName and Code , then when it goes to fetch all Doctors from the "doctors" Field in SpecializationType the resolver's context has different SubFields which should be used for the projection of the Doctor.
The problem with the above is, when you use query batches i guess even if you dont the thing is that the Doctors Field in SpecializationType needs the SpecializationId fetched in the RootQuery specializations Field.
I guess i didn't explain good what i went through.
Base line is as far as I understand we have to dynamically create selectors which the linq should use to project the entity.
I'm posting my approach here:
public class RootQuery : EfObjectGraphType
{
public RootQuery(IEfGraphQLService efGraphQlService, ISpecializationGraphQlServices specializationServices,
IDoctorGraphQlServices doctorServices, ScheduleDbContext dbContext) : base(efGraphQlService)
{
Name = "Query";
FieldAsync<ListGraphType<SpecializationType>>("specializations"
, resolve: async ctx => {
var selectedFields = GraphQLResolverContextHelpers.GetFirstLevelLeavesNamesPascalCase(ctx.SubFields);
var expression = BuildLinqSelectorObject.DynamicSelectGenerator<Specializations>(selectedFields.ToArray());
return await specializationServices.ListAsync(selector: expression);
});
}
}
SpecializationType
public class SpecializationType : EfObjectGraphType<Specializations>
{
public SpecializationType(IEfGraphQLService graphQlService
, IDataLoaderContextAccessor accessor, IDoctorGraphQlServices doctorServices)
: base(graphQlService)
{
Field(p => p.SpecializationId);
Field(p => p.Code);
Field(p => p.SpecializationName);
Field<ListGraphType<DoctorType>, IEnumerable<Doctors>>()
.Name("doctors")
.ResolveAsync(ctx =>
{
var selectedFields = GraphQLResolverContextHelpers.GetFirstLevelLeavesNamesPascalCase(ctx.SubFields);
selectedFields = GraphQLResolverContextHelpers.AppendParrentNodeToEachItem(selectedFields, parentNode: "Doctor");
selectedFields = selectedFields.Union(new[] { "Specializations_SpecializationId" });
var expression = BuildLinqSelectorObject.BuildSelector<SpecializationsDoctors, SpecializationsDoctors>(selectedFields);
var doctorsLoader = accessor.Context
.GetOrAddCollectionBatchLoader<int, Doctors>(
"GetDoctorsBySpecializationId"
, (collection, token) =>
{
return doctorServices.GetDoctorsBySpecializationIdAsync(collection, token, expression);
});
return doctorsLoader.LoadAsync(ctx.Source.SpecializationId);
});
}
}
DoctorsServices:
public class DoctorGraphQlServices : IDoctorGraphQlServices
{
public ScheduleDbContext _dbContext { get; set; }
public DoctorGraphQlServices(ScheduleDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<List<Doctors>> ListAsync(int? specializationId = null)
{
var doctors = _dbContext.Doctors.AsQueryable();
if(specializationId != null)
{
doctors = doctors.Where(d => d.Specializations.Any(s => s.Specializations_SpecializationId == specializationId));
}
return await doctors.ToListAsync();
}
public async Task<ILookup<int, Doctors>> GetDoctorsBySpecializationIdAsync(IEnumerable<int> specializationIds, CancellationToken token, Expression<Func<SpecializationsDoctors, SpecializationsDoctors>> selector = null)
{
var doctors = await _dbContext.SpecializationsDoctors
.Include(s => s.Doctor)
.Where(spDocs => specializationIds.Any(sp => sp == spDocs.Specializations_SpecializationId))
.Select(selector: selector)
.ToListAsync();
return doctors.ToLookup(i => i.Specializations_SpecializationId, i => i.Doctor);
}
}
SpecializationServices
public class SpeciaizationGraphQlServices : ISpecializationGraphQlServices
{
public ScheduleDbContext _dbContext { get; set; }
public SpeciaizationGraphQlServices(ScheduleDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<dynamic> ListAsync(string doctorId = null, Expression<Func<Specializations, Specializations>> selector = null)
{
var specializations = _dbContext.Specializations.AsQueryable();
if (!string.IsNullOrEmpty(doctorId))
{
specializations = specializations.Where(s => s.Doctors.Any(d => d.Doctors_Id == doctorId));
}
return await specializations.Select(selector).ToListAsync();
}
public async Task<ILookup<string, Specializations>> GetSpecializationsByDoctorIdAsync(IEnumerable<string> doctorIds, CancellationToken token)
{
var specializations = await _dbContext.SpecializationsDoctors
.Include(s => s.Specialization)
.Where(spDocs => doctorIds.Any(sp => sp == spDocs.Doctors_Id))
.ToListAsync();
return specializations.ToLookup(i => i.Doctors_Id, i => i.Specialization);
}
public IQueryable<Specializations> List(string doctorId = null)
{
var specializations = _dbContext.Specializations.AsQueryable();
if (!string.IsNullOrEmpty(doctorId))
{
specializations = specializations.Where(s => s.Doctors.Any(d => d.Doctors_Id == doctorId));
}
return specializations;
}
}
This post has become pretty large, sorry for the span..
For DoctorType, check the defined ObjectGraphType which is used to return Doctors.
For example, I have PlayerType like below:
public class PlayerType : ObjectGraphType<Player>
{
public PlayerType(ISkaterStatisticRepository skaterStatisticRepository)
{
Field(x => x.Id);
Field(x => x.Name, true);
Field(x => x.BirthPlace);
Field(x => x.Height);
Field(x => x.WeightLbs);
Field<StringGraphType>("birthDate", resolve: context => context.Source.BirthDate.ToShortDateString());
Field<ListGraphType<SkaterStatisticType>>("skaterSeasonStats",
arguments: new QueryArguments(new QueryArgument<IntGraphType> { Name = "id" }),
resolve: context => skaterStatisticRepository.Get(context.Source.Id), description: "Player's skater stats");
}
}
And I return Field<ListGraphType<PlayerType>> by
public class NHLStatsQuery : ObjectGraphType
{
public NHLStatsQuery(IPlayerRepository playerRepository, NHLStatsContext dbContext)
{
Field<ListGraphType<PlayerType>>(
"players",
resolve: context => {
return dbContext.Players.Select(p =>new Player { Id = p.Id, Name = p.Name });
//return playerRepository.All();
});
}
}
For the query and its columns, it is controlled by resolve in Field.
No matter what fields you want to return, make sure the columns defined in PlayerType are returned in resolve.
I suggest you:
1-use dto models and map them with database models
This means that you need to convert input dto model in database model to save in db; and also convert database models got from entity framework database select into dto model.
This is the classic approach used when you made a generic api, that for example get dto model data in input request, convert dto to save data in database, and viceversa.
2-map dto model to graphqltypes (objectgraphtype and inputobjectgraphtype)
This means that for each dto model could be necessary write 1 objectgraphtype and 1 inputobjectgraphtype.
TO DO THIS I'VE CREATE AN AUTOMATIC DTO TO GRAPHTYPE CONVERTER, so you don't need to write K and K of codes!! (see link at the end)
3-DON'T USE ADDDBCONTEXT! Graphql middleware use a singleton pattern; everything used via Dependecy injection in graphql is singleton externally, even if it is register as scoped (AddDbContext means "scoped").
This means that you have 1 connection opened to startup. You can't do 2 db operation in the same time!
In the real life you can't use AddDbContext with Graphql!
You can use factory pattern to do this. So, don't pass dbcontext in Dependency injection, but a Func and instantiate dbcontext explicitally.
Here a complete implementation example:
https://github.com/graphql-dotnet/graphql-dotnet/issues/576#issuecomment-626661695
There was a talk about GraphQL with EF Core 6 by #jeremylikness on .NET Conf 2021. I would recommend using .NET 6 and check his talk out:
https://devblogs.microsoft.com/dotnet/get-to-know-ef-core-6/#graphql
https://aka.ms/graphql-efcore
https://www.youtube.com/watch?v=GBvTRcV4PVA
https://www.youtube.com/watch?v=4nqjB_z5CU0
Here is an example implementation using Hot Chocolate GraphQL server:
https://chillicream.com/docs/hotchocolate/integrations/entity-framework
This is what Microsoft wrote about GraphQL for EF Core 6.0 in their High-level plan:
GraphQL has been gaining traction over the last few years across
a variety of platforms. We plan to investigate the space and find ways
to improve the experience with .NET. This will involve working with
the community on understanding and supporting the existing ecosystem.
It may also involve specific investment from Microsoft, either in the
form of contributions to existing work or in developing complimentary
pieces in the Microsoft stack.
https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-6.0/plan#graphql
To enable automatic fields projection, having your DB context set up as usual for .NET 6, add hotchocolate server:
dotnet add package HotChocolate.Data.EntityFramework
Expose some data to it:
public class MyQueries
{
[UseProjection] // Enables field projection
public IQueryable<Book> Books([Service] MyContext db) => db.Books;
}
Enable it in Program.cs:
builder.Services.AddGraphQLServer().AddQueryType<MyQueries>().AddProjections();
...
app.MapGraphQL("/graphql");
That should be enough to ensure automatic db fields projection. Now you can run GraphQL queries via builder generated at /graphql/, while monitoring SQL via MyContext.Database.Log = Console.Write;
I'm using GraphQL.NET (graphql-dotnet) by Joe McBride version 2.4.0
First of all, I'd recommend updating at least to v4.6 - there are lots of fixes and useful updates.
Secondly, if you don't have data mutation (means - update/delete/insert data), I'd say that better to not use EF for fetching data. Based on the same GraphQL.Net lib you can take a look, for example, NReco.GraphQL is using lightweight-ORM to fetch and map data (you just need to define a schema in json-file).

AutoMapper.Mapper.CreateMap<TSource,TDestination>()' is obsolete

I have to classes Like
class A
{
public int id {get; set;}
}
class B
{
public C c {get; set;}
}
class C
{
public int id {get; set;}
public string Name {get; set;}
}
My requirement is to map id of class A to id of class C.
Now what I was doing till now was:
Mapper.CreateMap().ForMember(des => des.C.Id, src => src.MapFrom(x => x.id));
and it was working fine.
Now seems like Auto mapper has changed their implementation. and I am getting warning as below:
AutoMapper.Mapper.CreateMap()' is obsolete: 'Dynamically creating maps will be removed in version 5.0. Use a MapperConfiguration instance and store statically as needed, or Mapper.Initialize. Use CreateMapper to create a mapper instance.
I need to map some properties of classes which has different name and structure. Any help on this.
Previously
Mapper.CreateMap<Src, Dest>()
.ForMember(d => d.UserName, opt => opt.MapFrom(/* ????? */));
The problem here is mapping definitions are static, defined once and reused throughout the lifetime of the application. Before 3.3, you would need to re-define the mapping on every request, with the hard-coded value. And since the mapping configuration is created in a separate location than our mapping execution, we need some way to introduce a runtime parameter in our configuration, then supply it during execution.
This is accomplished in two parts: the mapping definition where we create a runtime parameter, then at execution time when we supply it. To create the mapping definition with a runtime parameter, we “fake” a closure that includes a named local variable:
Mapper.Initialize(cfg => {
string userName = null;
cfg.CreateMap<Source, Dest>()
.ForMember(d => d.UserName,
opt => opt.MapFrom(src => userName)
);
});
For more information see this
For one or more classes
cfg.CreateMissingTypeMaps = true;
cfg.CreateMap<Source, Dest>()
.ForMember(d => d.UserName,
opt => opt.MapFrom(src => userName)
);
cfg.CreateMap<AbcEditViewModel, Abc>();
cfg.CreateMap<Abc, AbcEditViewModel>();
});
In mapping class
IMapper mapper = config.CreateMapper();
var source = new AbcEditViewModel();
var dest = mapper.Map<AbcEditViewModel, Abct>(source);
Another way that seems a bit cleaner is to make a MappingProfile class which inherits from the Profile class of AutoMapper
public class MappingProfile:Profile
{
public MappingProfile()
{
CreateMap<Source1, Destination1>();
CreateMap<Source2, Destination2>();
...
}
}
Then you initialize the mapping with Mapper.Initialize(c => c.AddProfile<MappingProfile>()); in your startup code
That will allow you to use the mapping anywhere by calling
destination1Collection = source1Collection.Select(Mapper.Map<Source1, Destination1>);
Finally I found the resolution. I was doing: Mapper.Initialize{ Mapping field from source to destination }
in the App_start and adding this file to the global.asax--> Application_Start() --> GlobalConfiguration.
I need to add one more line inside my Mapper.Initialize which is cfg.CreateMissingTypeMaps = true;
Now this code will work for explicit mapping where two classes don't have the same structure and names of properties.
Apart from this, if we need to map properties of two class with the same structure the code Mapper.map(source, destination) will also work, which was not working earlier.
Let me know if someone is having difficulty with the solution. Thanks all for the above reply.

Automapper-Map to a string

I tried creating a mapping to a string using the following CreateMap():
Mapper.CreateMap<MyComplexType, string>()
.ConvertUsing(c => c.Name);
But when I try to use this mapping, I get the following error:
Type 'System.String' does not have a default constructor
That makes sense, but I've been reading around and supposedly this should work. Is there something else I have to do?
In my case I was using
.ProjectTo<>()
To project directly from a DBContext collection (EF 6) to my DTO e.g.
db.Configuration.LazyLoadingEnabled = false;
prospects = db.Prospects.Where([my where lambda]).ProjectTo<ProspectDTO>().ToList();
With a destination with an IEnumerable<string> property coming from a M-M related table i.e.
public class ProspectDTO
{
public IEnumerable<string> Brands { get; set; }
}
and my solution was mapping as follows
AutoMapper.Mapper.CreateMap<Prospect, ProspectDTO>().ForMember(dest => dest.Brands, opts => opts.MapFrom(src => src.Brands.Select(b => b.Name)));
N.B. I am using the ProjectTo<> like this to avoid the common lazy loading select n+1 problem and ensure decent (quick) sql runs against the DB, and I have all the related table data I need. Excellent.
Thanks Jimmy Bogard you rockstar !!!

Code a SQL projection and mapping at the same time?

This works to carve out a DDL object from an Address object from our database:
public class DDL {
public int? id { get; set; }
public string name { get; set; }
}
List<DDL> mylist = Addresses
.Select( q => new DDL { id = q.id, name = q.name })
.ToList();
However, we'd like to keep our POCO to ViewModel mappings in a single place outside of our MVC controller code. We'd like to do something like this:
List<DDL> mylist = Addresses
.Select( q => new DDL(q)) // <-- constructor maps POCO to VM
.ToList();
But SQL cannot use the constructor function. The object initializer above doesn't use functions to map fields. Of course you could do .AsEnumerable().Select( q => new DDL(q)), but this selects all the fields in SQL (including the data), sends it to C#, then C# carves out the fields we need (terribly inefficient to transfer data we don't need.)
Any suggestions? We happen to be using Entity Framework 6 to pull data.
All you need is to define the expression somewhere and use it. For example, in your ViewModel as a static read-only field.
public class SomeViewModel
{
public static readonly Expression<Func<SomeEntity, SomeViewModel>> Map = (o) => new SomeViewModel
{
id = o.id,
name = o.name
}
public int id { get; set; }
public string name { get; set; }
}
// Somewhere in your controller
var mappedAddresses = Addresses.Select(SomeViewModel.Map);
I personally made myself a little static Mapper that keeps all the maps and use them for me. The maps are declared in a static initializer in all my ViewModels. The result gives me something that feels like AutoMapper, yet doesn't require the lib or the complicated mapping code (but also won't do any magic for you).
I can write something like this:
MyCustomMapper.Map<Entity, ViewModel>(entity);
and it's overloaded to accept IEnumerables, IQueryables and a single ViewModel. I also added overloads with only 1 generic type (the entity) that accept a type parameter. This was a requirement for me.
You can use anonymous types to restrict what to select from the DB and then use those fields to construct your object :
List<DDL> mylist = Addresses
.Select( q => new { id, name })
.AsEnumerable()
.Select(i => new DDL(i.id, i.name) // <- On the Enumerable and not on the Queryable
.ToList();
Are you against using 3rd party libraries? Automapper's QueryableExtensions does exactly what you want.
List<DDL> mylist = Addresses
.Project().To<DDL>()
.ToList();
It even has nice features like being able to filter on the transformed object and that filter being performed server side.
List<DDL> mylist = Addresses
.Project().To<DDL>()
.Where(d => d.name = "Smith") //This gets translated to SQL even though it was performed on DDL.
.ToList();

Automapper conditional language mapping

I have a domain model that contains member variables for two languages, something like this:
public class Resource
{
public string SwedishName;
public string EnglishName;
}
For presentation I have a simplified model, that is delivered to a json serializer:
[JsonObject]
public class JsonResource
{
[JsonProperty]
public string Name;
}
These are mapped with automapper like so:
Mapper.CreateMap<Resource, JsonResource>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.SwedishName));
My question is, if this is possible to do in a more conditional way, depending on which language is asked for? My initial thought, was something along these lines:
string lang = "en";
json = Mapper.Map<Resource, JsonResource>(resource, lang)
Though, it does not seem possible to have several mappings for the same types?
Currently Im leaning towards, just defining another identical presentation model for the other language:
if (lang == "en")
json = Mapper.Map<Resource, EnglishJsonResource>(resource)
else
json = Mapper.Map<Resource, JsonResource>(resource)
Is this a feasible solution, or is there a better way?
I would not create separate classes. Use AfterMap:
Mapper.CreateMap<Resource, JsonResource>()
.AfterMap((r,b) => r.Name = isEnglish ? b.EnglishName : b.SwedishName);
isEnglish is the condition in your app, however you need to use it.
You could create two different classes EngMapper and SimpleMapper which will implement the IMapper interface with a method initMapping for example after that you can create a factory in order to get the right mapper according to your language. So in the end your mapping will be separated for the different languages( that is better in my opinion ).

Categories