0

So, I have a page that contains a grid

SearchPage.razor

<FiGrid @ref="Grid"
    <Columns>
        @Columns
    </Columns>
    <EditFormTemplate Context="contextEntity">
        @EditFormTemplate(contextEntity)
    </EditFormTemplate>
</FiGrid>

@code{
protected RenderFragment Columns { get; set; }

protected RenderFragment<object> EditFormTemplate { get; set; }
}

this page has a RenderFragment to render the columns and one to render an editForm whenever is necessary

i inherit from this SearchPage to customize this 2 props:

CustomSearchPage.razor

@inherits SearchPage

@{
    base.BuildRenderTree(__builder);
}

@code {

    private static RenderFragment _customColumns = __builder =>
    {
        <CustomColumn FieldName="@nameof(Factory.Id)" Width="75px"/>
        <CustomColumn FieldName="@nameof(Factory.BusinessName)"/>
        <CustomColumn FieldName="@nameof(Factory.Address)"/>
    };

    private static RenderFragment<object> _customEditFormTemplate = editModel => __builder =>
    {
        <CustomEditForm Entity="editModel"/>;
    };

    protected override void OnInitialized()
    {
        Columns = _customColumns;
        EditFormTemplate = _customEditFormTemplate;
        base.OnInitialized();
    }

}

but it gives me this error on the console:

Operation is not valid due to the current state of the object.

maybe I'm using the wrong approach, in that case, how should I accomplish the same thing?

I need to be able to inherit from a page and pass the page a custom edit form that takes the context of the template

basically I want to pass the context of the template down to a RenderFragment created via code

Ivan Ambla
  • 763
  • 5
  • 24
  • The way the `CustomSearchPage.razor` is coded is not standard Blazor from my experience. By inherit, do you want to change `SearchPage.razor`, or use `SearchPage.razor`? – Marius Feb 09 '22 at 13:05
  • i want to change it and use the CustomSearchPage, the SearchPage is only used as a base component to specialize – Ivan Ambla Feb 09 '22 at 13:33
  • is there a better way to specialize a base components changing his structure for example changing the columns or the edit form? – Ivan Ambla Feb 09 '22 at 13:35
  • i want to be able to override some methods too, so I can't just call SearchPage with templates in CustomSearchPage because like that I wouldn't be able to override some SearchPage methods – Ivan Ambla Feb 09 '22 at 13:37
  • OK I understand. My approach to this would be different. I would've created common components used in both components and then modify each component to do their respective jobs. You can also reference classes to use their methods. So maybe look at using an interface or a base class to cater for the method changes. – Marius Feb 09 '22 at 13:59
  • Hard to say for sure, but I would try changing `__builder` to simply `builder` on your two private RenderFragment definitions - as `__builder` will already exist on the page - you actually want a new parameter/name, not the existing field – Mister Magoo Feb 09 '22 at 14:14

1 Answers1

0

You are getting the error because you can't build component inheritance in the way you envisage. You can't call BuildRenderTree manually like this:

base.BuildRenderTree(__builder);

because you don't have a reference to the Renderer's RenderTreeBuilder.

A RenderFragment is a delegate defined as:

public delegate void RenderFragment(RenderTreeBuilder builder);

It's run by the Renderer which invokes it and passes in the Renderer's instance of RenderTreeBuilder.

All Razor pages are pre-compiled into C# classes. So this simple component DisplayDiv.razor:

<h3>@Header</h3>

@code {
    [Parameter] public string Header { get; set; } = "Hello Blazor";
}

gets pre-compiled by the Razor processor to something like this:

public class DisplayDiv : ComponentBase
{
    [Parameter] public string Header { get; set; } = "Hello Blazor";

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        builder.OpenElement(0, "h3");
        builder.AddContent(1, this.Header);
        builder.CloseElement();
    }
}

BuildRenderTree gets added to ComponentBase's base RenderFragment which StateHasChanged passes to the Renderer when it's called to render the component.

private readonly RenderFragment _renderFragment;

public ComponentBase()
{
    _renderFragment = builder =>
    {
        _hasPendingQueuedRender = false;
        _hasNeverRendered = false;
        BuildRenderTree(builder);
    };
}

You are trying to define multiple copies of BuildRenderTree, which you can't do within the razor page setting. The child BuildRenderTree method built by the Razor pre-compiler overrides that of the parent.

Your SearchPage is a component which you then use in a page

<SearchPage>
    <Columns>
        <CustomColumn FieldName="@nameof(Factory.Id)" Width="75px"/>
        <CustomColumn FieldName="@nameof(Factory.BusinessName)"/>
        <CustomColumn FieldName="@nameof(Factory.Address)"/>
    </Columns>
    <EditFormTemplate>
        <CustomEditForm Entity="editModel"/>
    </EditFormTemplate>
</SearchPage>

You can do inheritance in components, but your parent components need to be classes, not Razor components, and build out any markup directly in code.

MrC aka Shaun Curtis
  • 19,075
  • 3
  • 13
  • 31
  • ty for the clarification, but the error is not thrown until i add the _customEditFormTemplate part, with only the columns it works fine, i understand the concept of having the parent components be only classes, but it worked fine until i tried to get another value on a render fragment: RenderFragment _customEditFormTemplate = editModel =>__builder – Ivan Ambla Feb 11 '22 at 08:16
  • Your code design leaves a lot up to interpretation by the Razor pre-compiler. My first impression of your code was "That won't work!": your never too old to learn new tricks! My guess is your Razor code is pushing the boundary conditions of the Razor interpreter. Personally, that's somewhere I wouldn't want to be with production code, so would redesign. – MrC aka Shaun Curtis Feb 11 '22 at 11:15
  • Your best course of action if you want to continue is to turn on `EmitCompilerGeneratedFiles` in your project and take a look at what is being built. See this answer if you don't know how to - https://stackoverflow.com/questions/70958616/how-to-see-c-sharp-code-compiled-from-razor-component-with-vs2022-in-project-tar – MrC aka Shaun Curtis Feb 11 '22 at 11:16
  • ty so much for the advice! ill look into that and update this question as i find an answer – Ivan Ambla Feb 11 '22 at 14:37
  • i ended up throwing this approach and completely redesign the way i handle edits, now i use a state manager as a scoped service and whenever i need to edit an entity i just call the service passing the entity, the service then checks the type of the entity with reflection and displays a popup containing the editForm component of the same "type" with – Ivan Ambla Feb 11 '22 at 15:31
  • i might have actually made it even more complicated but now its a lot easier to edit an entity when and where i want just by calling a service – Ivan Ambla Feb 11 '22 at 15:33
  • NP, sometimes you can't see the wood for the trees and need another set of eyes. – MrC aka Shaun Curtis Feb 11 '22 at 15:34