Search code examples
c#dynamicobject

C# Custom DynamicObject cast to derived object


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

Solution

  • 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