Visible expression for RenderObjectAttribute

Visible expression for RenderObjectAttribute

I made a quick change to my CG.Blazor.Forms library. It’s a change I need for a project I’m building that makes good use of the dynamic form generation capabilities of my library.


The source code for the CG.Blazor.Forms library can be found HERE.

The NUGET package for the CG.Blazor.Forms library can be found HERE.

An introduction to the CG.Blazor.Forms library can be found HERE.


So, what did I change? I added a new property to the RenderObjectAttribute class, called VisibleExp. That new property allows a developer to show or hide the object associated with the attribute, at render time, based on the value of another property, on the same view-model.

Let’s look at what I’m saying. We’ll start with an example view-model:

public class ExampleVM
{
    [RenderMudSelect(Options = "Item A, Item B")]
    public string Selected { get; set; }

    [RenderObject(VisibleExp = "x => x.Selected=\"Item A\"")]
    public ModelA A { get; set; }

    [RenderObject(VisibleExp = "x => x.Selected=\"Item B\"")]
    public ModelB B { get; set; }

    public ExampleVM()
    {
        Selected = nameof(A);
        A = new SqlServerModel();
        B = new MongoModel();
    }
}

public class ModelA 
{
   [RenderMudTextField]
   public string A {get; set; }
}

public class ModelB 
{
   [RenderMudTextField]
   public string B {get; set; }
}

ExampleVM is a typical view-model for a dynamic form. The properties on the class are decorated with RenderObjectAttribute attributes that provide the form generator with hints about how to render the form. The ModelA and ModelB classes are just a quick way to get some content that we can flip between, at runtime.

Next, we’ll generate a dynamic Blazor UI using some markup, like this:

<DynamicComponent Model="@Model" />

@code {
	ExampleVM Model { get; set; }
}

That markup creates a simple UI, using the DynamicComponent component, that looks something like this:

The “Selected” field is a dropdown that contains two options: “Item A”, and “Item B”. When the second option is chosen, the lower part of the UI automatically renders itself like this:

That, in itself, may not seem like much, but, what’s happening is, every time Blazor renders our dynamic form, the RenderObjectAttribute attribute, on the ExampleVM view-model, is checking the VisibleExp property for a LINQ expression that it can use to determine whether to render the associated object (or not). When it finds a valid LINQ expression, it invokes it to determine whether to render the associated object, or not.

Recall, that the VisibleExp property for the first object (the A property on ExampleVM), looks like this:

VisibleExp = "x => x.Selected=\"Item A\""

That means, whenever the Selected property, on ExampleVM contains the string “Item A”, the A property will be rendered.

The VisibleExp property for the second object (the B property, on ExampleVM) looks like this:

VisibleExp = "x => x.Selected=\"Item B\""

That means, whenever the Selected property, on ExampleVM contains the string “Item B”, the B property will be rendered.

So how did I do that? Let’s look at that now:

In our example, above, the RenderObjectAttribute class was used to decorate object properties that should be rendered. That’s needed because, by default, the form generator, that drives the operation of the DynamicComponent component, simply ignores all undecorated properties on the view-model. It’s a bit more complicated than that, actually. It turns out, it’s not enough to be told to render a property, the form generator also needs to know how to render the property. That’s really the biggest reason properties on the view-model have to be decorated, in order to be seen by the form generator and rendered on the resulting Blazor form.

So how does RenderObjectAttribute work? Let’s start by looking at the source:

[AttributeUsage(
    AttributeTargets.Property | AttributeTargets.Class, 
    AllowMultiple = false)]     
public class RenderObjectAttribute : FormGeneratorAttribute
{
    public string VisibleExp { get; set; }

    public override int Generate(
        RenderTreeBuilder builder,
        int index,
        IHandleEvent eventTarget,
        Stack<object> path,
        PropertyInfo prop,
        ILogger<IFormGenerator> logger
        )
    {
        // Validate the parameters before attempting to use them.
        Guard.Instance().ThrowIfNull(builder, nameof(builder))
            .ThrowIfLessThanZero(index, nameof(index))
            .ThrowIfNull(path, nameof(path))
            .ThrowIfNull(logger, nameof(logger));

        try
        {
            // If we get here then we are trying to render an entire object,
            //   one child property at a time.

            // Should never happen, but, pffft, check it anyway.
            if (false == path.Any())
            {
                // Let the world know what we're doing.
                logger.LogDebug(
                    "RenderObjectAttribute::Generate called with an empty path!"
                    );

                // Return the index.
                return index;
            }

            // Get the model reference.
            var model = path.Peek();

            // Get the model's type.
            var modelType = model.GetType();

            // Is the IsVisible expression itself invalid?
            if (false == TryVisibleExp(
                path, 
                out var isVisible
                ))
            {
                // Let the world know what we're doing.
                logger.LogDebug(
                    "Not rendering a '{PropType}' object. [idx: '{Index}'] object " +
                    "since the 'VisibleExp' property contains a malformed LINQ expression. " +
                    "The expression should be like: x => x.Property = \"value\"",
                    modelType.Name,
                    index
                    );

                // Return the index.
                return index;
            }

            // Is the IsVisible expression false?
            if (false == isVisible)
			{
                // Let the world know what we're doing.
                logger.LogDebug(
                    "Not rendering a '{PropType}' object. [idx: '{Index}'] object " +
                    "since the 'VisibleExp' property contains a LINQ expression that " +
                    "resolves to false. ",
                    modelType.Name,
                    index
                    );

                // Return the index.
                return index;
            }

            // Let the world know what we're doing.
            logger.LogDebug(
                "Rendering child properties for a '{PropType}' object. [idx: '{Index}']",
                modelType.Name,
                index
                );

            // Get the child properties.
            var childProps = modelType.GetProperties()
                .Where(x => x.CanWrite && x.CanRead);

            // Loop through the child properties.
            foreach (var childProp in childProps)
            {
                // Create a complete property path, for logging.
                var propPath = $"{string.Join('.', path.Reverse().Select(x => x.GetType().Name))}.{childProp.Name}";

                // Get the value of the child property.
                var childValue = childProp.GetValue(model);

                // Is the value missing?
                if (null == childValue)
                {
                    // If we get here then we've encountered a NULL reference
                    //   in the specified property. That may not be an issue,
                    //   if the property is a string, or a nullable type, because
                    //   we can continue to render.
                    // On the other hand, if the property isn't a string or 
                    //   nullable type then we really do need to ignore the property.

                    // Is the property type a string?
                    if (typeof(string) == childProp.PropertyType)
                    {
                        // Assign a default value.
                        childValue = string.Empty;
                    }

                    else if (typeof(Nullable<>) == childProp.PropertyType)
                    {
                        // Nothing to do here, really.
                    }

                    // Otherwise, is this a NULL object ref?
                    else if (childProp.PropertyType.IsClass)
                    {
                        // Let the world know what we're doing.
                        logger.LogDebug(
                            "Not rendering property: '{PropPath}' [idx: '{Index}'] " +
                            "since it's value is null!",
                            propPath,
                            index
                            );

                        // Ignore this property.
                        continue;
                    }
                }

                // Push the property onto path.
                path.Push(childValue);

                // Look for any form generation attributes on the view-model.
                var attrs = childProp.GetCustomAttributes<FormGeneratorAttribute>();

                // Loop through the attributes.
                foreach (var attr in attrs)
                {
                    // Render the property.
                    index = attr.Generate(
                        builder,
                        index,
                        eventTarget,
                        path,
                        childProp,
                        logger
                        );
                }

                // Did we ignore this property?
                if (false == attrs.Any())
                {
                    // Let the world know what we're doing.
                    logger.LogDebug(
                        "Not rendering property: '{PropPath}' [idx: '{Index}'] " +
                        "since it's not decorated with a FormGenerator attribute!",
                        propPath,
                        index
                        );
                }

                // Pop property off the path.
                path.Pop();
            }

            // Return the index.
            return index;
        }
        catch (Exception ex)
        {
            // Provide better context for the error.
            throw new FormGenerationException(
                message: "Failed to render an object! " +
                    "See inner exception(s) for more detail.",
                innerException: ex
                );
        }
    }

    private bool TryVisibleExp(
        Stack<object> path,
        out bool result
        )
	{
        // Make the compiler happy.
        result = true;

        // Is the expression missing, or empty?
        if (string.IsNullOrEmpty(VisibleExp))
		{
            // No expression is ok.
            return true;
		}

        // If we get here then we've determined there is a LINQ
        //   expression in the VisibleExp property, so we need to
        //   parse it now, and invoke the resulting Func to get
        //   the results.

        // Get the view-model reference.
        var viewModel = path.Skip(1).First();

        // Get the view-model's type.
        var viewModelType = viewModel.GetType();

        // Look for the expression parts.
        var parts = VisibleExp.Split("=>")
            .Select(x => x.Trim())
            .ToArray();

        // There should be 2 parts to a LINQ expression.
        if (2 == parts.Length)
        {
            // Create the parameter expression.
            var x = Expression.Parameter(
                viewModelType, 
                parts[0]
                );

            // Parse the labmda expression.
            var exp = DynamicExpressionParser.ParseLambda(
                new[] { x },
                null,
                parts[1]
                );

            // Compile and invoke the expression.
            result = (bool)exp.Compile().DynamicInvoke(
                viewModel
                );

            // We have a valid result.
            return true;
        }

        // The expression was invalid.
        return false;
    }
}

Let’s start by walking through the Generate method. Generate is common to all form generation attributes and is inherited as part of the FormGeneratorAttribute base class. The form generator itself calls the Generate method as part of the form generation process. We’ve overridden Generate, in RenderObjectAttribute, to step into the associated object property, and then, walk through all the properties on that type, rendering as we go.

We start by validating any incoming parameters. Then we do some deeper sanity checking of the path variable. That variable contains a path to the thing we’re actively trying to render.

After assuring ourselves that our incoming parameters are all valid, we then grab a reference to the parent object (called model). After that, we get the type for the model. We’ll use those two values, model and modelType, to walk through the properties for the object we’re trying to render.

This is where our new property, VisibleExp comes into play …

The first thing we need to determine is whether we should render the current object, or not. If VisibleExp is empty, then the answer is yes, we should. On the other hand, if VisibleExp contains a LINQ expression, and if that expression evaluates to FALSE, then the answer is no, we shouldn’t. We make that determination by calling a method called TryVisibleExp. We’ll cover TryVisibleExp shortly, for now though, just know that it makes the determination, for us, about whether we should continue to render, or stop.

Assuming we should stop, we do that, logging the decision, and returning the current index value.

Assuming we should continue, we log that we’re about to do some rendering, then we grab all the properties on the modelType that have a getter and a setter (readable and writeable). Once we’ve done that, we iterate through that collection of properties.

For each property, we create a path (of sorts) to the property. We’ll need that for logging purposes, later. Next we get the value of the property. If the property is null we need to do some checking to ensure that we can still render something. It’s difficult to render NULL, so sometimes we have to stop at that point. If we can continue rendering the property, or if the value isn’t NULL. we push the current value into the path variable, then we go look for any FormGeneratorAttributes on the property.

For each attribute we find, we then call Generate on that attribute. In this way, we use a bit of recursion to make everything that much easier to code, and that much more efficient. Of course, efficient doesn’t necessarily equate to “easier to understand”, but, I suppose there are always tradeoffs, right?

After we’ve finished rendering the property, we pop the current property value off our path variable and move on to the next property.

But wait, where is the code that actually renders the property?? Well, if the property we’re rendering is another object, and that property is also decorated with a RenderObjectAttribute attribute, then we’ll recursively step into that child property and keep going – until we either stack fault or run out of descendant properties to render. My guess is, developers will get tired of adding child properties, and grandchild properties, and great-grandchild properties, and so on, long before the stack itself faults. At least I hope that’s the case. :o)

Eventually, one of those descendant properties will contain a property that returns a primitive type. When that happens, assuming the property was decorated with a kind of FormGeneratorAttribute attribute, then that attribute will render the property as a UI component, of one sort or another. For instance, our dropdown list is rendered with a RenderMudSelectAttribute attribute, and our a text box fields are both rendered with RenderMudTextFieldAttribute attributes.

And that’s how the rendering actually takes place.

The only thing I haven’t talked about, so far, is the implementation of the TryVisibleExp method. Looking at that code now, we see that the method is laid out like any other .NET TryXXX method, where it returns TRUE if the outgoing property is valid, and FALSE if it isn’t. In our case, we return TRUE if the VisibleExp property contains a LINQ expression, and that expression is valid, and FALSE if those two things are not true.

In order to evaluate the LINQ expression, we need a reference to the parent of this model. We get that from the path variable. After that, we get the type of the view-model. We’ll need those type bits of information when we create the LINQ expression.

Next, we split the value of the VisibleExp property up using the => symbol. That gives us the name of the property on the left hand side, and the expression itself on the right hand side.

We then use the variable (left hand side of VisibleExp) to create an actual LINQ parameter expression.

After that, we use the call to DynamicExpressionParser.ParseLambda to turn the right hand side of the VisibleExp property into an actual LINQ expression. Having written this sort of code, by hand, in the past, I am very grateful to Microsoft for the DynamicExpressionParser class. It’s much easier than writing expression code, trust me.

After we have the LINQ lambda, expression, we just have to compile it, then call it, to get the results of the operation, which we store in the outgoing result variable.


The result of this new parameter, VisibleExp, is that we can now create dynamic forms that can decide, at render time, which properties to render, and which ones not to. That because important as the complexity of dynamic forms increases, which they seem to do whether we like it or not.

The alternative to this approach is quite complicated since dynamic forms are, by their nature, somewhat self contained, and therefore, not easy to extend with Blazor code or java script.


Photo by Joran Quinten on Unsplash