Search code examples
c#asp.netrazorhttp-cachingexpandoobject

Dynamic property assigned to object


Is it possible to attach dynamic property to an object of user-defined class?

public class Room
{
    public int NumberOfDoors { get; set; }

    public int NumberOfWindows { get; set; }
}

then from other context:

Room room = new Room();

dynamic contact = new ExpandoObject();
contact.NumberOfWalls = 4;

and then somehow associate NumberOfWalls with room, as its property?

Update (Larger Picture):

as per @nawfal's suggestion

I have a cached List<Room> being iterated in a razor view (outside the themes folder), calling a

particular partial view (from the current theme) for each element. One of the theme needs an extra

property in Room. I only have access to modify code in that particular theme folder, the partial

views cshtml files (don't ask why).

So its basically:

(psuedocode)

Room.NoOfWalls = SomeHeavyLiftingProcess(Room.NoOfWindows, Room.NoOfDoors)


I am looking for a way o update the List<Room> rooms object with NoOfWalls in

HttpRuntime.Cache["rooms"] to avoid calling SomeHeavyLiftingProcess() with each request. The

goal is to inject a property in cached object. Unfortuntely HttpRuntime.Cache["rooms"] is object

type and doesn't allow me to do this:

HttpRuntime.Cache["rooms"][3]["NoOfWalls"] = SomeHeavyLiftingProcess(..)

So I am thinking, for the first request (when cache is empty or invalid):

  • Unpackig: Retrieve (List<Room>)HttpRuntime.Cache["room"], inject NoOfWalls in the current room object.

  • Repacking: Update List<Room> room with the new object and assign it back to HttpRuntime.Cache.


For the subsequent requests, the value of NoOfWalls will come from cached object @Model.NoOfWalls.


Solution

  • You cannot add properties not defined in a class to an existing instance, without using a dynamic object like ExpandoObject.

    If you need to add members to an existing class, you can create a child class with a special constructor:

    public class SpecialRoom : Room
    {
        public SpecialRoom() { }
    
        public SpecialRoom(Room copy)
        {
            this.NumberOfDoors = copy.NumberOfDoors;
            this.NumberOfWindows = copy.NumberOfWindows;
        }
    
        public int NumberOfJacuzzis { get; set; }
    }
    

    Usage:

    var room = new Room();
    room.NumberOfDoors = 3;
    
    var specialRoom = new SpecialRoom(room)
    {
        NumberOfJacuzzis = 7
    };
    

    Or:

    var listOfRooms = new List<Room>();
    // ...
    var listOfSpecialRooms = listOfRooms.Select(x => new SpecialRoom(x));
    listOfSpecialRooms.ForEach(x => x.NumberOfJacuzzis = ComplexCalculation(x));
    

    If you have an existing concrete object (like an instance of the Room class), you can convert it to a dynamic object with a method like this:

    public static dynamic ConvertObjectToDynamic(object value)
    {
        if (value == null)
        {
            return null;
        }
    
        IDictionary<string, object> dynamicObject = new ExpandoObject();
        var properties = value.GetType().GetProperties(
            BindingFlags.Public | BindingFlags.Instance);
    
        foreach (var propertyInfo in properties)
        {
            if (propertyInfo.GetIndexParameters().Length == 0)
            {
                var propertyValue = propertyInfo.GetValue(value);
                dynamicObject[propertyInfo.Name] = propertyValue;
            }
        }
    
        return dynamicObject;
    }
    

    Usage:

    var room = new Room();
    room.NumberOfDoors = 3;
    
    dynamic dynamicObject = ConvertToDynamic(room);
    dynamicObject.WhateverYouWant = 7;
    

    Now dynamicObject.NumberOfDoors will be 3, and dynamicObject.WhateverYouWant will be 7.