0

I have a class DocumentObject that extends DynamicObject to allow dynamic membership attributes.

public class DocumentObject : DynamicObject
    {
        /// <summary>
        /// Inner dictionary that holds the dynamic members of the object
        /// </summary>
        Dictionary<string, object> dictionary = new Dictionary<string, object>();

        /// <summary>
        /// Try to get the member that is not defined in the class (additional dynamic members) from inner dictionary
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            // Converting the property name to lowercase
            // so that property names become case-insensitive.
            string name = binder.Name.ToLower();

            // If the property name is found in a dictionary,
            // set the result parameter to the property value and return true.
            // Otherwise, return false.
            return dictionary.TryGetValue(name, out result);
        }

        /// <summary>
        /// Try to set the member that is not defined in the class (additional dynamic members) to inner dictionary
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            // Converting the property name to lowercase
            // so that property names become case-insensitive.
            dictionary[binder.Name.ToLower()] = value;

            // You can always add a value to a dictionary,
            // so this method always returns true.
            return true;
        }

        /// <summary>
        /// Get the names of all the dynamic members
        /// </summary>
        /// <returns></returns>
        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return dictionary.Keys;
        }

    }

I have a base Person class that inherits DocumentObject

public class PersonDto : DocumentObject
    {
        [JsonProperty("id")]
        public string Id { get; set; }
    }

Another child OfficePersonDto class that inherits PersonDto

public class OfficePersonDto : PersonDto 
    {
        [JsonProperty("name")]
        public string Name { get; set; }
    }

In my function, I am receiving JSON object that has to be at least PersonDto object, but if its an OfficePersonDto type, I wish to be able to cast PersonDto into OfficePersonDto. I.e. JSON = {"Id":1, "Name": "Orchard"}, in PersonDto, name attribute will be saved using DocumentObject's dictionary, while casting to OfficePersonDto, both Id and name are attributes of the class.

How can I cast from PersonDto to a child class e.g. OfficePersonDto?

PersonDto personDto = ...
OfficePersonDto off = personDto as OfficePersonDto  // results in null or Name is null

1 Answer 1

0

Automapper is very helpful when the issue is about these kind of conversions. I'll show you a quick working example. But it will be still open for more refactoring of course.

PersonDto personDto = ...
// Do not use the following conversion, instead get help from automapper
// OfficePersonDto off = personDto as OfficePersonDto
    
var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<PersonDto, OfficePersonDto>()
        .ForMember(d => d.Name, opt => opt.MapFrom(new OfficePersonNameResolver()));
});
    
var mapper = config.CreateMapper();
var off = mapper.Map<OfficePersonDto>(personDto); // off is created with correct values

OfficePersonNameResolver is like this:

public class OfficePersonNameResolver : IValueResolver<PersonDto, OfficePersonDto, string>
{
    public string Resolve(PersonDto source, OfficePersonDto destination, string destMember, ResolutionContext context)
    {
        return (source as dynamic).Name;
    }
}

You are welcome to ask if you have questions about this.

Edit: Generalizing the resolver to IValueResolver<TSource, TDestination, TDestinationMember>

Change the creation of configuration like this:

var config = new MapperConfiguration(cfg =>
{
      cfg.CreateMap<PersonDto, OfficePersonDto>()
          .ForMember(d => d.Name, opt => opt.MapFrom(new DynamicObjectValueResolver<PersonDto, OfficePersonDto, string>("name")));
});

And The value resolver should be like this now:

public class DynamicObjectValueResolver<TSource, TDestination, TDestinationMember> : IValueResolver<TSource, TDestination, TDestinationMember>
   where TDestinationMember : class
{
    private readonly string _propertyName;

    public DynamicObjectValueResolver(string propertyName)
    {
       _propertyName = propertyName;
    }

    public TDestinationMember Resolve(TSource source, TDestination destination, TDestinationMember destMember, ResolutionContext context)
    {
        dynamic eo = JsonConvert.DeserializeObject<ExpandoObject>(JsonConvert.SerializeObject(source));
        IDictionary<string, object> dictionary = eo;
        return dictionary[_propertyName] as TDestinationMember;
     }
}

Working example: https://dotnetfiddle.net/KS91To

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks @Koray Elbek, may I ask if I can generialise the resolver to IValueResolver<S, D>, where S and D are of DocumentObject type? So that I do not have to create a resolver to every base class of DocumentObject.
Saw your update, is it possible if I can generalise the mapper config as well? cfg.CreateMap<typeof(DocumentObject), typeof(DocumentObject)>()? I tried. But get invalidCastException from DocumentObject to OfficePersonDto.
I don't think generalization of the config is also possible, to be honest. Even if it's possible somehow, it might require a lot of scaffolding. Which will result in a bad overengineering.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.