2

UPDATE: this stuff has evolved into a nice project, see it at http://valueinjecter.codeplex.com


check this out, I just wrote a simple automapper, it takes the value from the property with the same name and type of one object and puts it into another, and you can add exceptions (ifs, switch) for each type you may need

so tell me what do you think about it ?

I did it so I could do something like this:

Product –> ProductDTO

ProductDTO –> Product

that's how it begun:

I use the "object" type in my Inputs/Dto/ViewModels for DropDowns because I send to the html a IEnumerable<SelectListItem> and I receive a string array of selected keys back

 public void Map(object a, object b)
    {
        var pp = a.GetType().GetProperties();
        foreach (var pa in pp)
        {
            var value = pa.GetValue(a, null);

            // property with the same name in b
            var pb = b.GetType().GetProperty(pa.Name);
            if (pb == null)
            {
                //no such property in b
                continue;
            }

            if (pa.PropertyType == pb.PropertyType)
            {
                pb.SetValue(b, value, null);
            }

        }
    }

UPDATE: the real usage:
the Build methods (Input = Dto):

        public static TI BuildInput<TI, T>(this T entity) where TI: class, new()
        {
            var input = new TI();
            input = Map(entity, input) as TI;
            return input;
        }

        public static T BuildEntity<T, TI, TR>(this TI input)
            where T : class, new()
            where TR : IBaseAdvanceService<T>
        {               
            var id = (long)input.GetType().GetProperty("Id").GetValue(input, null);
            var entity = LocatorConfigurator.Resolve<TR>().Get(id) ?? new T();
            entity = Map(input, entity) as T;
            return entity;
        }

        public static TI RebuildInput<T, TI, TR>(this TI input)
            where T: class, new()
            where TR : IBaseAdvanceService<T>
            where TI : class, new()
        {

                return input.BuildEntity<T, TI, TR>().BuildInput<TI, T>();
            }

in the controller:

    public ActionResult Create()
    { 
        return View(new Organisation().BuildInput<OrganisationInput, Organisation>());
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create(OrganisationInput o)
    {
        if (!ModelState.IsValid)
        {
            return View(o.RebuildInput<Organisation,OrganisationInput, IOrganisationService>());                
        }
        organisationService.SaveOrUpdate(o.BuildEntity<Organisation, OrganisationInput, IOrganisationService>());
        return RedirectToAction("Index");
    }

The real Map method

public static object Map(object a, object b)
        {
            var lookups = GetLookups();

            var propertyInfos = a.GetType().GetProperties();
            foreach (var pa in propertyInfos)
            {
                var value = pa.GetValue(a, null);

                // property with the same name in b
                var pb = b.GetType().GetProperty(pa.Name);
                if (pb == null)
                {
                    continue;
                }

                if (pa.PropertyType == pb.PropertyType)
                {
                    pb.SetValue(b, value, null);
                }
                else if (lookups.Contains(pa.Name) && pa.PropertyType == typeof(LookupItem))
                {
                    pb.SetValue(b, (pa.GetValue(a, null) as LookupItem).GetSelectList(pa.Name), null);
                }
                else if (lookups.Contains(pa.Name) && pa.PropertyType == typeof(object))
                {
                    pb.SetValue(b, pa.GetValue(a, null).ReadSelectItemValue(), null);
                }
                else if (pa.PropertyType == typeof(long) && pb.PropertyType == typeof(Organisation))
                {
                    pb.SetValue(b, pa.GetValue<long>(a).ReadOrganisationId(), null);
                }
                else if (pa.PropertyType == typeof(Organisation) && pb.PropertyType == typeof(long))
                {
                    pb.SetValue(b, pa.GetValue<Organisation>(a).Id, null);
                }
            }

            return b;
        }
Omu
  • 69,856
  • 92
  • 277
  • 407
  • 1
    off topic - i love your opening line to the 'question': "check this out..."! haha. i've thought about doing this but not had the balls – Matt Kocaj Nov 23 '09 at 08:31
  • @cottsak yep it's really good opening :D, never seen one like that – Omu Jun 11 '10 at 11:15
  • @ChuckNorris - Can you please point out why Automapper (initially) was not recommended for ViewModel<->Model mapping? I realize this is a very late comment in this discussion and many things might have moved ahead in both Automapper and ValueInjector, but nonetheless, if you have some noteworthy point about why to avoid Automapper, please let us know. – Faredoon Dec 05 '12 at 00:33
  • @FairDune I did this ^ cuz my classes were quite simple and I thought that there's no need to write configuration for each pair, a simple convention will do – Omu Dec 05 '12 at 09:53
  • @ChuckNorris Thanks. I agree. After scouring SO for ValueInjecter posts, I got a lot of answers. I am using ValueInjecter now. – Faredoon Dec 05 '12 at 23:03

4 Answers4

6

Just use AutoMapper. This is fine, but it'll grow into a mini project.

Just some things AM (the real one) does is:

  • reporting if you have properties that can't be mapped to
  • flattening objects
  • providing hooks for you to customise some aspects, not in a big switch statement
  • using Expression.Compile for perf reasons rather than reflection directly

But it's certainly an interesting space to mess about in, and the idea of auto mapping is certainly useful.

A bit like DI in 15 or 33 lines vs NInject or its friends - cool, but why?.

I take it you've read the article and comments regarding 2 way mapping on Jimmy's blog ?

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
  • I intend to use it for editing entities (to map from my models into my viewmodels and viceversa), i wanted to use automapper for that, but Jimmy told me not to :) – Omu Nov 23 '09 at 08:23
  • 1
    @Omu, fair enough. The point for me though is that your superficially simple 'solution' is hiding an iceberg of special cases, cool and all as it is, and in completing it, you'll end up with a non-trivial amount of work. cottsak's point asking what are you doing this mapping for is worth considering. But you didnt give us much to go on regarding what your *real* scenario is. For all we know you might the ideal .NET RIA Services user :P Did Jimmy say not to add 2-way mapping to AM or that he wasnt going to because its not a use case he sees making sense? Forking could be better than reinventing – Ruben Bartelink Nov 23 '09 at 08:37
  • I have updated the question text, you can see now how I use it – Omu Nov 23 '09 at 13:20
  • Thanks for the link, it (lack of 2-way mapping) makes sense now. – JTew Feb 12 '10 at 00:02
5

One thing you might want to add is to cache the reflection bits. If you map an object twice, you probably don't want to look up all the reflection stuff again. Also, things like GetValue and SetValue are quite slow, I switched to late-bound delegates + Reflection.Emit to speed things up.

Jimmy Bogard
  • 26,045
  • 5
  • 74
  • 69
  • :) well, I don't map a specific type to another one, it's more like per property mapping for all Types – Omu Nov 23 '09 at 13:41
  • I'm going to investigate how to speed the things up though, thanx for suggesting Reflection.Emit and late-bound delegates, not sure about caching – Omu Nov 23 '09 at 13:45
  • 2
    Caching definitely helped AutoMapper out - the GetFields and GetProperties calls can be a bottleneck - run this in something like dotTrace and you can see where the time is spent. This type of coding is pretty cool, have fun with it! – Jimmy Bogard Nov 25 '09 at 13:59
  • thnx, good idea with caching the GetProperties, I can store the Properties for each type in cache so I won't need to get them each time – Omu Nov 26 '09 at 08:55
2

Just a thought:

One might wonder what the point of an abstraction is if the abstraction is so easily mapped to that being abstracted.

Matt Kocaj
  • 11,278
  • 6
  • 51
  • 79
2

you can add exceptions (ifs, switch) for each type you may need

You're not going to do this. Seriously. Case statements acting on object types are bad OOP style. I mean, really bad. Like driving with a drink of Vodka inside your stomach. It may work for some time, but eventually you get into trouble.

OK Jimmy told you not to use AutoMapper... but I bet he meant something else. Now, have you invented something different - something that make Jimmy happy? ;-) No, you just made your own half-rolled AutoMapper. And Jimmy told you not to use it! ;-)

So here's my suggestion: ignore what Jimmy says, just think yourself.. and use AutoMapper ;-)

Omu
  • 69,856
  • 92
  • 277
  • 407
queen3
  • 15,333
  • 8
  • 64
  • 119
  • well yes, I could, but with this stuff I can do my own conventions very easily; I use it at the moment in 2 methods of a "Builder" class: BuildEntity BuildInput, so i'm mapping automatically all my entities to all my Inputs (ViewModels), without the need to specify a mapping (CreateMap) for each pair of types – Omu Nov 23 '09 at 10:40
  • You can have your BuildEntity<> methods to invoke AutoMapper CreateMap/Map internally. Moreover, you can have it to check if CreateMap is already registered, so you have possibility to override default mapping - instead of hard-coding into switches which is the BAD BAD style. Still I don't see a reason to re-invent AutoMapper. – queen3 Nov 23 '09 at 12:55
  • I have updated the question text, you can see now how I use it – Omu Nov 23 '09 at 13:20