Search code examples
c#razorblazor.net-5

How can I add Razor Markup from a C# Method?


Objective

I have a form on my page that will call the method OnFinish when the user clicks the button to send the form out. In that method, I want to add new markup to the page. (The code for the form is not relevant, so I won't add it on here)

Problem

There does not seem to be a simple way (if any) to add razor markup from C# code.

What I've tried

The first thing I tried was straight markup in the C# Code. That looks like this:

private void OnFinish(EditContext editContext)
{
    <a>Placeholder</a>
}

This did - of course - not work, because I was writing it in a .cs file.


In the .razor file I tried doing the same:

@code
{
    private void OnFinish(EditContext editContext)
    {
        <a>Placeholder</a>
    }
}

The compiler didn't like this either and raised the following error:

The name '__builder' does not exist in the current context

I quickly learned from this question, that markup cannot be written in a @code block.


According to this official ASP.NET blog post, writing markup in a @functions block should work:

@functions
{
    private void OnFinish(EditContext editContext)
    {
        <a>Placeholder</a>
    }
}

This, unfortunately, had the same outcome as using a @code block.

EDIT: After reading through the blog post again I realised, that the feature only works in .cshtml files. I tested this and, indeed, it works in .cshtml files. But these files cannot be used as components, so it doesn't work for me after all.


I tried using a RenderTreeBuilder __builder as a parameter in the method, which was suggested in this answer.

The problem with this solution is, that the __builder parameter is only available from within the markup code like this:

<a>@__builder</a>

and not from the C# code (that's why the parameter is used).


The next thing I found was the @helper block, which is mentioned in this answer. I applied this to my code like this:

@helper OnFinish(EditContext editContext) 
{
    <a>Placeholder</a>
}

This does not work either, as the compiler raises the following two errors:

The helper directive is not supported.

The name 'helper' does not exist in the current context

Conclusion

Now, this is where the search comes to an end. I spent so much time researching this, that I want to believe that there might not be any solution to this at all. What have I missed?


EDIT

Some have mentioned the idea of putting the markup in an if, while the if gets then activate when some condition comes true (e.g. I have a bool that I set to true or I change a variable and the if recognizes that).

That solution can be seen in either this other question or this answer in the current thread.

The code for that solution would look like this:

if (renderNewMarkup)
{
    <a>Placeholder</a>
}
@functions
{
    private void OnFinish(EditContext editContext)
    {
        renderNewMarkup = true;
    }
}

This does work but it's very unpleasant in my opinion. The problem I have with this is the use of variables.

In my program, I retrieve some data from the UI and would pass it further to the then created markup. I can give you an example:

private void OnFinish(EditContext editContext)
{
    Dictionary<string, string> dict = new()
    {
        ["Key1"] = boundVariable,
        ["Key2"] = otherBoundVariable,
        ["Key3"] = anotherBoundVariable
    }
    
    // Create markup with dict variable
}

The markup I want to add to the HTML is a custom razor component that has a Dictionary<string, string> parameter.

You can see that I now can't just simply create the markup there. What I have to achieve what I want would look like this:

@if (renderNewCustomComponent)
{
    <Custom DictParameter="@dict" />
}

@code
{
    private Dictionary<string, string> dict;

    private void OnFinish(EditContext editContext)
    {
        dict = new()
        {
            ["Key1"] = boundVariable,
            ["Key2"] = otherBoundVariable,
            ["Key3"] = anotherBoundVariable
        }
    
        renderNewCustomComponent = true;
    }
}

I hope you see what I mean by this.


Then there's an even bigger problem with this solution. When I want to call a method, e.g. AddElementToHTML(), that will always add some element to the HTML, that would not be possible. Yes, with some complex way, with a @foreach or something, that would be possible, but why wouldn't the language just allow that in the first place?


Solution

  • Original Answer

    This shows you how to build and display a simple render fragment from markup. It may help.

    @page "/Test"
    <h3>Test8</h3>
    <input @bind-value="content" class="form-control" />
    <button class="btn btn-dark" @onclick="RenderSomeContent">Render Content</button>
    <button class="btn btn-danger ml-2" @onclick="ClearContent">Clear Content</button>
    
    @Placeholder
    
    @code {
        RenderFragment Placeholder { get; set; }
    
        string content = "<div class='m-3 bg-success'>Hello</div>";
    
        void RenderSomeContent()
        {
            Placeholder = (builder) =>
            {
                builder.AddMarkupContent(0, content);
            };
        }
    
        void ClearContent()
        {
            Placeholder = null;
        }
    }
    

    Update on comment

    BinaryLogicControl
    @if (this.Show)
    {
        if (this.ChildContent != null)
        {
            @this.ChildContent
        }
        else
        {
            @((MarkupString)this.Body)
        }
    }
    
    @code {
        [Parameter] public bool Show { get; set; }
        [Parameter] public RenderFragment ChildContent { get; set; }
        [Parameter] public string Body { get; set; }
    }
    
    TestPage
    @page "/Test"
    
    <button class="btn btn-secondary" @onclick="DoIt">Toggle content</button>
    
    <BinaryLogicControl Show="show">
        <div class='m-3 bg-success'>Hello</div>
    </BinaryLogicControl>
    <BinaryLogicControl Show="show" Body="@Display" />
    
    @code {
    
        string Display = string.Empty;
        bool show;
    
        void DoIt()
        {
            show = !show;
            Display = "<div class='m-5 p-3 bg-danger'>Hello</div>";
        }
    }
    

    Update Based on Rendering a component

    Based on the comments, what you want is the DynamicComponent coming in Net 6. At present you have to build your own (simpler) version. The code below shows an updated version of the original answer that builds out the BinaryLogicControl for rendering rather than a simple Markupstring.

    @page "/Test"
    
    <button class="btn btn-primary" @onclick="RenderMe">Render Control</button>
    
    @Placeholder
    
    @code {
       RenderFragment Placeholder { get; set; }
    
        void RenderMe()
        {
            RenderSomeContent(typeof(BinaryLogicControl), new Dictionary<string, object> { 
               { "Show", true }, 
               { "Body", "<div class='m-3 bg-warning'>Hello</div>" }
            });
        }
    
        void RenderSomeContent(Type componentType, Dictionary<string, object> parameters)
        {
            Placeholder = (builder) =>
            {
                builder.OpenComponent(0, componentType);
                foreach (var parameter in parameters)
                {
                    builder.AddAttribute(1, parameter.Key, parameter.Value);
                }
                builder.CloseComponent();
            };
        }