In my ASP.NET MVC application I need to implemenet mapping from one object to another with some kind of UI for mapping configuration in runtime, so the user can define mapping "on the go". Is there any libraries that supports such functionality?
Description
This is objects in my application. I need to somehow allow user to configure mapping of this objects via UI during application runs. For exmaple some kind of page in my application where user will be able to define mapping in simple way like so map Amout of OrderDTO to Order Qty and later without application recompile change this mapping for exmaple for ExactAmmount
//Object in DAL
public class Order
{
public int Id {get; set;}
public string Name {get; set;}
public decimal Qty {get; set;}
//Lots of other fields
}
//Object from XSD generation (for example)
public class OrderDTO
{
public int Id {get; set;}
public string Description {get; set;}
public decimal Ammout {get; set;}
public decimal VAT {get; set;}
public decimal ExactAmmount {get; set;}
//Lots of other fields
}
Note: for legacy reasons I based this answer on AutoMapper 4.2.1 instead of the current 5.x version. The overall approach should be similar with the new version.
It is possible to create different mapping configurations and different mappers within a program. Also, it is possible to create member mappings by member names (string) instead of lambda expressions. However, some static type information is still necessary (as far as my example goes).
See the following example of a profile, that prepares a custom mapping based on property names:
class MemberProfile : Profile
{
private string from;
private string to;
public MemberProfile(string from, string to)
{
this.from = from;
this.to = to;
}
protected override void Configure()
{
this.CreateMap<Order, OrderDTO>()
.ForMember(to, c => c.MapFrom<decimal>(from));
}
}
This could be extended to support different source property types and a collection of custom mappings instead of a single one.
Usage example:
var order = new Order() { Id = 1, Name = "Test", Qty = 0.5m };
var conf1 = new MapperConfiguration(c => c.AddProfile(new MemberProfile("Qty", "Ammout")));
var conf2 = new MapperConfiguration(c => c.AddProfile(new MemberProfile("Qty", "ExactAmmount")));
var res1 = conf1.CreateMapper().Map<OrderDTO>(order);
var res2 = conf2.CreateMapper().Map<OrderDTO>(order);
For res1, Qty is mapped to Ammout and for res2, Qty is mapped to ExactAmmount. Since the difference is described as string property names, it should be possible to let the user influence this configuration.
Related
I'm using CSVHelper (thanks Josh Close) to read a CSV file which works great. I'm now trying to use it to map that file to some internal classes; however, the CSV's I'm mapping vary by customer but all need to map to my internal classes. I need to allow the customer to define how the CSV maps to my POCO objects.
I'm storing the customer defined mappings as Dictionary<string,int> -- ["Firstname", 20],["Lastname",21],["Address.Line1",30], ["Address.Line2",31], etc.
I have a dynamic map class that works for dynamically mapping based on a given mapping at runtime. My problem lies in dealing with reference type properties. Here is what I have so far.
POCO Classes
public class Client
{
public int Id {get; set;}
public string Firstname {get; set;}
public string Lastname {get; set;}
public Address Address {get; set;}
...
}
public class Address
{
public string Line1 {get; set;}
public string Line2 {get; set;}
public string City {get; set;}
...
}
Based on a few posts that I've run across here and here, I came up with the following that uses a defined mapping to map a CSV dynamically.
Dynamic Map
public class BaseCSVMap<T> : ClassMap<T> where T : class
{
public void CreateMap(Dictionary<string,int> mappings)
{
foreach(var mapping in mappings)
{
var propname = mapping.Key;
var csvIndex = mapping.Value;
var member = typeof(T).GetProperty(propname);
Map(typeof(T), member).Index(csvIndex);
}
}
}
Using Dynamic Map
var id = 2; //Customer 2
var mappings = dataContext.Mappings.Where(m => m.id = id); //Get customer 2's map
using(var reader = File.OpenText(#"c:\temp\testfile.csv"))
{
var csv = new CsvReader(reader);
csv.Configuration.HasHeaderRecord = true; //hardcoded for now
var map = new BaseCSVMap<Client>();
map.CreateMap(mappings);
csv.Configuration.RegisterClassMap(map);
var records = csv.GetRecords<Client>();
}
I added the following in my BaseCSVMap<T> class, which works great if all my reference properties are strings, but doesn't work so well when a property is something else.
var member = typeof(T).GetProperty(propname);
//New code
//Mapping would look like ["Address.Line1",78]
if(member.GetType().IsClass)
{
string exp = $"c.{propname}";
var p = Expression.Parameter(typeof(T), "c");
var e = System.Linq.Dynamic.Core.DynamicExpressionParser.ParseLambda(new [] {p}, null, exp);
Map((Expression<Func<T,string>>)e).Index(csvIndex);
}
I also looked for a way to take advantage of the Reference mapping that CSVHelper makes available, but was unable to figure out how to do that in a dynamic fashion.
Looking for some guidance in how to accomplish defining a dynamic map for a reference type with CSVHelper.
Scenario:
An Intern can learn multiple technologies
db design
ef view
Result
controller code:
private InternEntities db = new InternEntities();
// GET: api/Interns
public IQueryable<Intern> GetInterns()
{
return db.Interns;
}
What am i doing wrong here?
This is an expected error, and the reason is because your types reference each other like an Infinity Mirror. In order to solve the problem, you have several options.
1- You can develop a ViewModel and then serialize that one:
public class InternViewModel{
public int Id {get; set;}
public String Name {get; set;}
public List<String> Tehcnologies {get; set;}
}
2- You can select the properties that you need when returning the entity in your actions:
public async Task<List<Technology>> Get() {
var data = dbContext.Set<Technology>().Select(x=> new Technology{
Id = x.Id,
Name = x.Name,
Intern= new Intern {
Id = x.Technology.Id,
Name = x.Technology.Name,
Technologies = null
}
});
return await data.ToListAsync();
}
3- Load only the what you need which is known as Explicit Loading.
I want to add OData syntax for querying my application's data. I don't want to fully implement ODataController, but have ApiController and implement one GET action that will support OData queries, as described here: Supporting OData Query Options in ASP.NET Web API 2
Example of what I want to have:
public class LetterEntity
{
public int Id {get; set;}
public string Title {get; set;}
public string Content {get; set;}
public string Source {get; set;}
public DateTime SendingTime {get; set;}
public string AnotherWierdString {get; set;
...
}
public class LetterDTO
{
public int Id {get; set;}
public string Title {get; set;}
public string LetterContent {get; set;}
public string Source {get; set;}
public DateTime SendingTime {get; set;}
}
public class LetterInsideFolderDTO
{
public string Title {get; set;}
public string Source {get; set;}
}
public class LettersController : ApiController
{
// Is there a way to do something like the following?:
[HttpGet]
[Route("api/letters")]
[EnableQuery]
public IQueryable<LetterInsideFolderDTO> Get(ODataQueryOptions<LetterDTO> query)
{
IQueryable<Letter> letters = db.Letters;
var queryOnEntites = // Convert the query to work on the entities somehow? - This is where I need help!!
var afterQuery = query.ApplyTo(letters)
IQueryable<LetterInsideFolderDTO> dtos = afterQuery.ProjectTo<LetterInsideFolderDTO>(afterQuery)
return dtos;
}
}
Because of the fact that at the moment I take Entity model directly in the clients query, there is a strong coupling between clients and server.
For example if i want to query and get all the letters that has "abc" inside the Content field, I need to route to the following:
api/letters/?$filter=contains(Content,'abc')
If tomorrow I decide to change that property from "Content" to "LetterContent" all clients code will be broken.
How can I surpass it?
I have not tried it. This and this can help you to map the query url to actual controller by using router map and controller selector where you can map LetterDto to LetterEntity
This will work for Entity Framework (not sure for Nhibrenate but probably will) and will do a real SQL query not in memory filtering.
var queryOnEntites = db.Letters.Select(l=>new LetterDTO{Id =l.Id ... });
var afterQuery = query.ApplyTo(queryOnEntites);
But you shouldn't use DTOs with odata if you want to make some properties private to the API use builder.EntitySet<T1>("TEndpoint").EntityType.Ignore(o => o.SomeProp);
Now if you don't want the whole LetterEntity sent to client you have the $select=Id,... option for this purpose.
If you don't setup
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: builder.GetEdmModel());
you don't have an Odata endpoint and $metadata won't be available.
I haven't tested it but this way I don't think the client libraries(.net,java,js...) would work and you will have to make raw ajax requests to get the data.
ODataQueryOptions<> and translate it to
ODataQueryOptions<>
You can't do this. ProjectTo(AutoMapper) function dose the same thing as .Select(l=>new LetterDTO{Id =l.Id ... }); but you will have problems with this. This all is dependent on your back end since IQueryable is not the same as IEnumerable.
http://blog.ploeh.dk/2012/03/26/IQueryableTisTightCoupling/
This all depends on what level of LINQ your back end provides (NHibrenate tends to be worse then EF, and if you are using Mongo, Elastic, Cassandra... who knows what might go wrong when you use AutoMapper)
If tomorrow I decide to change that property from "Content" to
"LetterContent" all clients code will be broken.
You can setup Name property on an Entity set with oData. Remember Odata is data access level not BL. You should treat it the same way as changing SQL column name in the database.
Instead of exposing your entity models directly, create ViewModels corresponding to each entity model. They are nothing but simple classes having same or only required properties which we want to be exposed to the outer world. Having implemented this way, your entity model properties may change but ViewModels can remain unchanged. You are also addressing the security aspect of not exposing every property of your entity to end clients.
Mapping between entity model and ViewModel has to be done by yourself or by an object-object mapper like AutoMapper.
I got two objects with some properties as follows:
public class Person
{
public string DoctorId { get; set ;}
public string DoctorName { get; set;}
public string PersonId { get; set;}
public string PersonName { get; set;}
}
public class SaveRequest
{
public string DoctorName { get; set;}
public string PersonName { get; set;}
public string PersonId { get ; set;}
}
Now as you can see, I want to map the SaveRequest to Person Object I have. We do this by PersonId which is the key and also my requirement as you see is, not all properties are in SaveRequest, its just name of person and doctor and PersonId just for primary key...
Please help me as to how to map these in C# with a small code.
You could try using a mapper like AutoMapper
With automapper, you'll have to
Add a reference using nuGet (PM> Install-Package AutoMapper)
Then create a map using Mapper.Initialize(cfg => cfg.CreateMap<Person, SaveRequest>());
Then, simply use the mapper like so: SaveRequest sr = Mapper.Map<SaveRequest>(person); where person is an instance of Person
More info here (https://github.com/AutoMapper/AutoMapper/wiki/Flattening)
Or you could build your own custom Mapping method which takes in a Person and returns/maps the data to a SaveRequest
Or you could simply use Person and ignore the DoctorId property wherever you intended to use SaveRequest
Automapper is a library that aims to make this type of thing much easier, however it's more suited for use in multiple areas within a project rather than just one isolated case (otherwise it'll take longer to set it up than it's worth). http://automapper.org/
The old school way is to just do it by hand, property by property.
One could write a routine using reflection that would examine property names and match the values, but if you go that far, you might as well just use Automapper, is that's exactly what it does (and a lot more).
Suppose I have two objects:
class Order
{
string Name {get; set;}
Customer Customer {get; set;}
Item[] Items {get; set;}
}
and
class OrderDTO
{
string Name {get; set;}
CustomerDTO Customer {get; set;}
ItemDTO[] Items {get; set;}
}
If I receive an object orderDTO that is fully populated and do orderDTO.TranslateTo<Order>() the result will only have Name populated, not Customer or Items. Is there a way to do a recursive translation or the only option is to translate Customer and each of the Items manually?
I would wrap this in a re-usable extension method, e.g:
public static OrderDTO ToDto(this Order from)
{
return new OrderDTO {
Name = from.Name,
Customer = from.ConvertTo<CustomerDTO>(),
Items = from.Items.Map(x => x.ConvertTo<ItemDTO>()).ToArray(),
}
}
Which can then be called whenever you need to map to an Order DTO, e.g:
return order.ToDto();
Some more examples of ServiceStack's Auto Mapping is available on the wiki.
ServiceStack's party-line is if your mapping requires more than the default conventional behavior that's inferable from an automated mapper then you should wrap it behind a DRY extension method so the extensions and customizations required by your mapping are cleanly expressed and easily maintained in code. This is recommended for many things in ServiceStack, e.g. maintain un-conventional and complex IOC binding in code rather than relying on an obscure heavy-weight IOC feature.
If you prefer it, you can of course adopt a 3rd party tool like AutoMapper.
You are going to have to handle complex mapping explicitly yourself. Here are some unit tests from ServiceStack src that show how complex types are currently handled.
You can see that the the Car object is serialized into JSON.
var user = new User() {
FirstName = "Demis",
LastName = "Bellot",
Car = new Car() { Name = "BMW X6", Age = 3 }
};
var userDto = user.TranslateTo<UserDto>();
Assert.That(userDto.FirstName, Is.EqualTo(user.FirstName));
Assert.That(userDto.LastName, Is.EqualTo(user.LastName));
Assert.That(userDto.Car, Is.EqualTo("{Name:BMW X6,Age:3}"));
I agree with Trust Me - I'm a Doctor that Automapper is worth using. The built in Translating was designed to reduce dependencies.