3

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?

9
  • Does this answer your question? How to render a component on button click in Blazor? Commented Aug 3, 2021 at 8:15
  • @PeterB see my edit. Commented Aug 3, 2021 at 11:52
  • In all your first attempts, without an @if , how would you control where the new HTML shows up in your page? Commented Aug 3, 2021 at 11:53
  • @HenkHolterman hmm I never really thought of that, I was just assuming that it would go at the end. Or that I can put the C# code where I want the element to be at, e.g I place a @functions block in a div and it would add it there (example 3). Commented Aug 3, 2021 at 11:56
  • @functions is just a very old version of @code . Commented Aug 3, 2021 at 12:11

4 Answers 4

3

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();
        };
    }
Sign up to request clarification or add additional context in comments.

8 Comments

Very nice answer, and I upvoted it. But my question would be-- in what context would doing this actually be better than just having a component with the same markup? I mean, technically, you could build a complete renderer of your own using a collection of C# string manipulations and serve it from a Windows machine.
@Bennyboy1973 - as you've probably already guessed, none! I wouldn't do it this way. I was originally going to answer this question by proposing to build a BinaryLogicControl but desisted as It might be considered over the top. See Habib Hassan's PrettyBlazor for how to do more complex if+. I personally hate seeing lots of markup and logic code mixed up, so I'm a component for everything man. As is almost always the case with questions, we only see the top of the iceberg, so I added the suggestion to show how to create the placeholder. BTW thanks.
@MrCakaShaunCurtis thanks for the answer. This looks like it could do the work. I still have one question though: If I would send the RenderSomeContent method some parameters and would add them into the string, would that still be passed over? Doesn't seem like it would to me but maybe it does somehow.. Also, that PrettyBlazor class is really nice. If I go with any of the other solutions in the thread, I'll definitely use that. Could you maybe also elaborate on the BinaryLogicControl thing you talked about?
@speyck. See updated answer. Can you give us a specific example of "send method some parameters"?
@MrCakaShaunCurtis I mean with that, that the RenderSomeContent method would have some parameters. The signature could look like this: void RenderSomeContent(string value1, int value2). I'm passing the method two values and the method would then add the new markup with these values, e.g: <SomeComponent Parameter1="@value1" Number="@value2" />. Also thanks for adding the other stuff to the answer.
|
1

This is only a partial answer I think. But inline markup in a function is possible, just a little awkward because of the extra {}. Which you can merge with an @if() or other control structure:

@{ void F1() { <p>Inline Markup (local fn)</p> } }

@{ F1(); }

@if (2 > 1)
{
    F1();
    F2(__builder);
}

@code {

    void F2(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
    {
        <p>Inline Markup </p>
    }
}

Consider that the markup section of a page is compiled to an override void BuildRenderTree(builder) {...}.

You can add a local function (F1) or call another method from your class (F2).

This does not solve your OnFinish(editContext) problem though because that is called outside the rendering code path.

So you will still need an @if() .

7 Comments

If you take a look at my 4th example, you can see that I have already tried having the __builder parameter in my method. The first method you mention would actually work, but I can't call it from the C# code. So yes, in the end a @if is still needed...
Yes. You might as well use direct markup inside an @if. But also consider that apparently you are using OnFinish() in a very different way than the author(s) intended.
What do you mean by that?
I don't know where it comes form. Maybe look up some examples of how others are using it.
Okay, really take my advice. Forget about "adding markup" and create components with whatever markup you'd like, then show the components based on logic conditions. Use [Parameter] in your components to allow you to pass data to them (for example, your editform model).
|
0

Razor code uses control structures like if, foreach and so on. You can build an HTML string however you like, and use a conditional to let Blazor know when to display it.

@if (CustomHtml is not null) {
    <div>@((MarkupString)CustomHtml)</div>
}

@code
{
    string CustomHtml; // starts as null
    private void OnFinish(EditContext editContext)
    {
        CustomHtml = "<b>You are now registered!</b>";
    }
}

Generally, for adding controls, you'd include a .razor Component in your conditional logic. Components can be nested, so there's really no limit to what you can build.

Comments

0

In the function you have to manipulate the editContext or return something.

Pseudo-code example with return:

@functions
{
   private string OnFinish(EditContext editContext)
   {
      return "<a>Placeholder</a>";
   }
}

Pseudo-code example with manipulate:

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

1 Comment

I can't make the method a string because the razor component only wants a delegate that is a void. Also, editContext does not have a definition for any change() method so that does not work either.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.