-1

Building a proof of concept, I've gotten most of it solved, this is just the final step of my proof of concept and I'm hitting a brick wall. I'm building an automatic constructor for a left nav on a ASP.net with Razor Pages based upon the specific definitions contained within a page. The idea is a universal navbar at the top, and a page specific navbar on the left.

    List<object> navElements = new List<object> {
        new {text = "Test1", location = "test1", subElements = new List<object> {
            new {text = "Test1-1", location = "test1-1", subElements = new List<object> {
                new {text = "Test1-1-1", location = "test1-1-1"},
                new {text = "Test1-1-2", location = "test1-1-2"},
                new {text = "Test1-1-3", location = "test1-1-3"}
            } },
            new {text = "Test1-2", location = "test1-2", subElements = new List<object> {
                new {text = "Test1-2-1", location = "test1-2-1"},
                new {text = "Test1-2-2", location = "test1-2-2"},
                new {text = "Test1-2-3", location = "test1-2-3"}
            } },
            new {text = "Test1-3", location = "test1-3", subElements = new List<object> {
                new {text = "Test1-3-1", location = "test1-3-1"},
                new {text = "Test1-3-2", location = "test1-3-2"},
                new {text = "Test1-3-3", location = "test1-3-3"}
            } }

        } }
    };

Notice this test definition is attempting to delve three levels in. I've built a recursive function to catch every layer of subElements contained within the list. The problem arises that because subElement doesn't exist on the final layer, it throws a 'object' does not contain a definition for 'subElements' and no accessible extension method 'subElements' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)

It can find both universal properties (text and location).

Found this Stackflow question that suggested to use OBJECT.GetType().GetProperty("PROPERTYNAME") != null, and that seems to be working just fine, however I still am unable to compile when calling that property after this if block.

How am I able to check for, and call, a variably existing property inside of a generic object?

This check needs to catch for two actions:

  1. Assigns necessary stylesheet code to allow for the drop box in each layer. Since the existence of the drop box is dependent on the existence of a sub layer.
  2. To trigger the recursion of lower layers. This would enable infinite layers of drop boxes since it's functionized.

Here's the general appearance of the functionized builder.

@{
    void navLeftBuilder(List<object> elements)
    {
        foreach (object element in elements)
        {
            @if (element.subElements) { navLeftBuilder(element.subElement); }
        }
    }
}

I've even tried changing the object call to

@{
    void navLeftBuilder(List<object> elements)
    {
        object obj;
        foreach (int element in elements)
        {
            obj = elements[element];
            @if (obj.subElements) { navLeftBuilder(obj.subElement); }
        }
    }
}

1 Answers1

0

Try with something like the following

class NavElement { 
        public string Text { get; set; }
        public string Location { get; set; }

        public List<NavElement> SubElements { get; set; }
    }

    private static void Main()
    {
        List<NavElement> navElements = new List<NavElement> {
        new NavElement{Text = "1", Location = "1", SubElements = new List<NavElement> {
            new NavElement{Text = "1-1", Location = "1-1", SubElements = new List<NavElement> {
                new NavElement{Text = "1-1-1", Location = "1-1-1"},
                new NavElement {Text = "1-1-2", Location = "1-1-2"},
                new NavElement {Text = "1-1-3", Location = "1-1-3"}
            } },
            new NavElement {Text = "1-2", Location = "1-2", SubElements = new List<NavElement> {
                new NavElement {Text = "1-2-1", Location = "1-2-1"},
                new NavElement {Text = "1-2-2", Location = "1-2-2"},
                new NavElement {Text = "1-2-3", Location = "1-2-3"}
            } },
            new NavElement {Text = "1-3", Location = "1-3", SubElements = new List<NavElement> {
                new NavElement {Text = "1-3-1", Location = "1-3-1"},
                new NavElement {Text = "1-3-2", Location = "1-3-2"},
                new NavElement {Text = "1-3-3", Location = "1-3-3"}
            } }
        } }
        };

With this schema, you can access the properties and the children without reflection.

Console.WriteLine(navElements[0].SubElements[2].SubElements[2].Text); 

will print

1-3-3

EDIT

For the record, this is what would work, but what you should NOT do.

void navLeftBuilder(List<dynamic> elements)
{
    foreach (dynamic element in elements)
    {
        Console.WriteLine($"I'm '{element.text}'");
        try
        {
            navLeftBuilder(element.subElements);
        }
        catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException)
        {
            //  property doesn't exist, ignore
        }
    }
}
Community
  • 1
  • 1
tymtam
  • 31,798
  • 8
  • 86
  • 126
  • Well that seems to work for that purpose, but it also seems to have broken my passing data from my page to my partial view <_< Let me play around with it and I will get back! – Taylor Haze Oct 19 '19 at 04:25
  • Capitalisation maybe? `Text` vs `text`? – tymtam Oct 19 '19 at 04:30
  • No, I'm working with ASP.net and Razor pages. I'm building a partial view as a template for this. So I'm passing the `navElements` variable inside of a `@Html.Partials("PartialsDefinitionLocation", navElements)` and retrieving it with `@{List navElements = Model}` It was working fine passing the data this way but not it's throwing an error there. Additionally I'll have to figure out how to make this available to a foreach loop. I suppose I can use a traditional for loop but I was appreciating the foreach loop for this purpose as it read nicely. Reading up how 2 enumerate my obj – Taylor Haze Oct 19 '19 at 04:36
  • Idk. Working with `object` is digging a hole for yourself. Type safety is one of a founding features of `c#`. – tymtam Oct 19 '19 at 04:44
  • I suppose the description that this is a proof of concept to build an infinitely deep drop menu, and being able to foreach through the definitions to actualize them in the laziest way possible, is rather important and I was very brief on that. I've built a single layer verison of this that looked exactly how the code looked in the first bit, just without the `subElements` property. – Taylor Haze Oct 19 '19 at 04:45
  • Out of curiosity, why not use the try-catch blocks? – Taylor Haze Oct 19 '19 at 05:17
  • I meant "NO `dynamic`". – tymtam Oct 19 '19 at 05:27