Is it possible to transform a value during injection?

Jun 30, 2010 at 1:01 PM

I have two scenarios where the simple injection doesn't work very well.

First, one of my business objects has a property of type TimeZoneInfo, which is serialized to a string when it's written to the database.  As a result, my DTO (which is essentially a Linq-to-Sql object) has the same property but of type string.  Is it possible to specify during the injection call to transform that string property using the TimeZoneInfo.FromSerializedString() method?  Right now I do the injection then manually set that property, but it would be nice to wrap it up in the injection process.

That same object also has an enum property that is stored in the database as an integer.  The injection fails to set this on its own, and again I have to set the enum manually.  This second scenario is far more common in my application.  Unfortunately I can't simply change how the enum is stored in the database since in many cases they are Flags that require integer storage for bitwise comparison.

Otherwise the tool is great -- I started with AutoMapper and quickly ran into problems unflattening my DTOs into my business objects.  Besides the issues above, this works well.

Josh

Jun 30, 2010 at 2:33 PM
Edited Jun 30, 2010 at 4:14 PM

yes, and quite easy

        public class TimeZoneInfoToString : LoopValueInjection<TimeZoneInfo, string>
        {
            protected override string SetValue(TimeZoneInfo sourcePropertyValue)
            {
                return sourcePropertyValue.ToSerializedString();
            }
        }

        public class StringToTimeZoneInfo : LoopValueInjection<string, TimeZoneInfo>
        {
            protected override TimeZoneInfo SetValue(string sourcePropertyValue)
            {
                return TimeZoneInfo.FromSerializedString(sourcePropertyValue);
            }
        }

        public enum Colors { Red, Green, Blue }

        public class ColorsToInt : LoopValueInjection<Colors, int>
        {
            protected override int SetValue(Colors sourcePropertyValue)
            {
                return (int)sourcePropertyValue;
            }
        }


usage:

a.InjectFrom<TimeZoneInfoToString>(b)
.InjectFrom<ColorsToInt>(b);

 

Jun 30, 2010 at 3:01 PM
Edited Jun 30, 2010 at 3:05 PM

When I try to override the SetValue method as you suggested in the TimeZoneInfoToString class, the compiler complains that there is no suitable method found to override.  It doesn't seem to like that I changed the argument from object to TimeZoneInfo.

Assuming I can overcome that, my next question would be how the injection code would look for an object that contains a TimeZoneInfo property and a few enum properties.  Would I repeat the InjectFrom method calls as below?

 

a.InjectFrom(b);  // To cover the regularly-typed properties

a.InjectFrom<StringToTimeZoneInfo>(b);  // To catch the time zone property

a.InjectFrom<IntToMyFirstEnum>(b);  // To catch the first enum property

a.InjectFrom<IntToMySecondEnum>(b);  // To catch the second enum

 

Is there a way to string those or to allow the standard InjectFrom method to know that when it sees a TimeZoneInfo object it uses TimeZoneInfoToString?

Jun 30, 2010 at 3:14 PM
Edited Jun 30, 2010 at 3:25 PM
joshcdsi wrote:

When I try to override the SetValue method as you suggested in the TimeZoneInfoToString class, the compiler complains that there is no suitable method found to override.  It doesn't seem to like that I changed the argument from object to TimeZoneInfo.

Sorry for that, codeplex editor removed my generics when pasting, I've edit the post, now it should work 

about the InjectFrom,

all he is doing is calling

target.InjectFrom<LoopValueInjection>(source);
and yes you have to specify each time when you inject which injections to use, but of course you can create a method of your own
that will be something like this:
public static void MapFrom(this object target, object source)
{	
	target.InjectFrom<V1>(source)	 	
 	.InjectFrom<V2>(source)		
	.InjectFrom<V3>(source);
}
Jun 30, 2010 at 3:14 PM

Follow-up to my last post...

My last question on that post was asking the wrong way.  Right now, I'm working on going from DTO to business object, so a better question is how would the InjectFrom method know which property to use for the string -> TimeZoneInfo transformation?

I'm sure once I start working on the insert/update operations I'll need to know the reverse.

Jun 30, 2010 at 3:23 PM
Edited Jul 1, 2010 at 6:49 AM

ok, I've edited a little bit more my answers so I think now it should answer your question, also I've created 2 injections for any type of enums here is the whole unit test (NUnit):

[TestFixture]
    public class EnumTests
    {
        [Test]
        public void Test()
        {
            var e = new Entity
            {
                Color = Colors.Blue,
                Color2 = Colors.Green,
                Mood = Moods.VeryHappy,
                Mood2 = Moods.Great,
            };
            var dto = new Dto();
            dto.InjectFrom<EnumToInt>(e);

            Assert.AreEqual(2, dto.Color);
            Assert.AreEqual(1, dto.Color2);
            Assert.AreEqual(2, dto.Mood);
            Assert.AreEqual(3, dto.Mood2);


            var e2 = new Entity();
            e2.InjectFrom<IntToEnum>(dto);
            Assert.AreEqual(dto.Color, 2);
            Assert.AreEqual(dto.Color2, 1);
            Assert.AreEqual(dto.Mood, 2);
            Assert.AreEqual(dto.Mood2, 3);
        }

        public enum Colors
        {
            Red, Green, Blue
        }

        public enum Moods
        {
            Happy, Awesome, VeryHappy, Great
        }

        public class Entity
        {
            public Colors Color { get; set; }
            public Colors Color2 { get; set; }
            public Moods Mood { get; set; }
            public Moods Mood2 { get; set; }
        }

        public class Dto
        {
            public int Color { get; set; }
            public int Color2 { get; set; }
            public int Mood { get; set; }
            public int Mood2 { get; set; }
        }

        public class EnumToInt : ValueInjection
        {
            protected override void Inject(object source, object target, PropertyDescriptorCollection sourceProps, PropertyDescriptorCollection targetProps)
            {
                for (var i = 0; i < sourceProps.Count; i++)
                {
                    var s = sourceProps[i];
                    if (!s.PropertyType.IsSubclassOf(typeof(Enum))) continue;
                    var t = targetProps.GetByNameType<int>(s.Name);
                    if (t == null) continue;

                    var value = s.GetValue(source);
                    t.SetValue(target, value);
                }
            }
        }

        public class IntToEnum : ValueInjection
        {
            protected override void Inject(object source, object target, PropertyDescriptorCollection sourceProps, PropertyDescriptorCollection targetProps)
            {
                for (var i = 0; i < sourceProps.Count; i++)
                {
                    var s = sourceProps[i];
                    if (s.PropertyType != typeof(int)) continue;
                    var t = targetProps.GetByName(s.Name);
                    if (t == null) continue;
                    if (!t.PropertyType.IsSubclassOf(typeof(Enum))) continue;

                    var value = s.GetValue(source);
                    t.SetValue(target, value);
                }
            }
        }
    }
Jun 30, 2010 at 3:27 PM

Gotcha -- after doing some playing I see how it works.  I'll probably go the route of using a Map() method as you suggested.

For the suggestion box:  It would be great to be able to determine special transformation classes and string them together or, better yet, assign them to a specific class type, so that the ValueInjecter would know on a certain class type, if there is a TimeZoneInfo property that matches a string property on the other side, it automatically uses the special transformation class.

In that way, I could call an Initialize() method (or pull from an XML config file) to set all those transformations, then every time I need to map something I just use the standard a.InjectFrom(b) method.

Thanks for the quick responses.  Everything's working great.

Jun 30, 2010 at 3:30 PM
Edited Jun 30, 2010 at 3:36 PM
joshcdsi wrote:

how would the InjectFrom method know which property to use for the string -> TimeZoneInfo transformation?

 it is going to look for the properties with the same name from the source and target object where the source props should also be of type string and the target props should be of type TimeZoneInfo

you can also specify target/source props prefix when injecting or inside the Injection

Jun 30, 2010 at 6:27 PM

That works great, thanks for taking the time to put that together.  FYI, CodePlex removed your generics again -- once in the EnumToInt class (missing the int specification) and a few times in the test class (missing the EnumToInt and IntToEnum specifications).

Thanks again!

Jul 1, 2010 at 6:55 AM
Edited Jul 1, 2010 at 6:56 AM
Ok, I've fixed that, thnx for telling me

Cheers,

Omu
Jul 5, 2010 at 3:05 PM

Hi

update: with the new version 1.9.3

the injections for enums can be written like this:

        public class EnumToInt : LoopValueInjection
        {
            protected override bool TypesMatch(Type sourceType, Type targetType)
            {
                return sourceType.IsSubclassOf(typeof (Enum)) && targetType == typeof (int);
            }
        }      
     
        public class IntToEnum : LoopValueInjection
        {
            protected override bool TypesMatch(Type sourceType, Type targetType)
            {
                return sourceType == typeof (int) && targetType.IsSubclassOf(typeof(Enum));
            }
        }

cya

Omu

 

Jul 4, 2011 at 11:36 AM

For anyone who uses the string value on enums, you can convert them generically with:

 

public class EnumToString : ValueInjection {

            protected override void Inject(object source, object target) {
                var sourceProps = TypeDescriptor.GetProperties(source);
                var targetProps = TypeDescriptor.GetProperties(target);

                for (var i = 0; i < sourceProps.Count; i++) {
                    var s = sourceProps[i];
                    if (!s.PropertyType.IsSubclassOf(typeof(Enum))) continue;
                    var t = targetProps.GetByNameType<string>(s.Name);
                    if (t == null) continue;

                    var value = s.GetValue(source);
                    t.SetValue(target, value);
                }
            }
        }

        public class StringToEnum : ValueInjection {

            protected override void Inject(object source, object target) {
                var sourceProps = TypeDescriptor.GetProperties(source);
                var targetProps = TypeDescriptor.GetProperties(target);

                for (var i = 0; i < sourceProps.Count; i++) {
                    var s = sourceProps[i];
                    if (s.PropertyType != typeof(string)) continue;
                    var t = targetProps.GetByName(s.Name);
                    if (t == null) continue;
                    if (!t.PropertyType.IsSubclassOf(typeof(Enum))) continue;

                    var value = s.GetValue(source);

                    if(value == null) {
                        continue;
                    }

                    var enumVal = Enum.Parse(t.PropertyType, value.ToString(), true);

                    t.SetValue(target, enumVal);
                }
            }
        }