Reuse and Customize Components

  • 5 minutes to read

Blazor framework allows you to build and reuse composite components. You can also render parts or entire components dynamically in C#.

Composite Components

This section describes how to create a composite Blazor component.

  1. In Solution Explorer, right-click a folder with components and select Add -> New Item. In the Add New Item dialog, select Razor Component, enter the component's name, and click Add.

    Add New Item

  2. Add DevExpress Blazor components to the newly created *.razor file:

    <h3>My Component</h3>
    
    <DxCalendar VisibleDate="@DateTime.Today" />
    
    <DxTextBox Text="@Text"
               ClearButtonDisplayMode="@DataEditorClearButtonDisplayMode.Auto"
               NullText="Type text..." />
    
    <DxButton Text="My Button" />
    
    @code {
        [Parameter]
        public string Text { get; set; }
    }
    
  3. Add MyComponent code to a page (for example, Index.razor):

    <MyComponent Text="My Text" />
    

    My Component

Refer to Microsoft documentation for more information on how to create ASP.NET Core Razor components.

Build Components Dynamically

You can use the following methods to render Blazor components dynamically:

Render Fragments

A render fragment is a custom UI element that you can reuse on pages or in component markup. To define a fragment, use either Razor template syntax or the RenderFragment delegate. To insert the fragment into markup, use the @ symbol.

Razor Template Syntax

The code below demonstrates the renderTextBox fragment that renders a Text Box component.

@{
    RenderFragment<string> renderTextBox = (string model) =>
    @<DxTextBox Text="@model"></DxTextBox>;
}

@renderTextBox("MyText")

The code below demonstrates the renderGrid fragment that renders a Data Grid component.

@{
    RenderFragment<DataItem> renderGrid = (model) =>
    @<DxDataGrid @key="@model" Data="@model.Data">
        @foreach (var column in model.ColumnNames) {
            <DxDataGridColumn Field="@column" @key="@column" />
        }
    </DxDataGrid>; 
}

@renderGrid(dataObject)

RenderFragment Delegate

You can use the RenderFragment delegate to define render fragments for simple, nested components, and components that include <Template> elements.

The code above demonstrates how to use template syntax to render a Text Box component. You can render the same component dynamically using the RenderFragment delegate. To define a render fragment, call the RenderTreeBuilder's methods: OpenComponent, AddAttribute, and CloseComponent.

@renderTextBox()

@code {
    private RenderFragment renderTextBox() {
        RenderFragment item = b => {
            b.OpenComponent<DxTextBox>(0);
            b.AddAttribute(1, "Text", "My text");
            b.CloseComponent();
        };
        return item;
    }
}
NOTE

Refer to Manual RenderTreeBuilder logic for more information on how to use RenderTreeBuilder.

You can create render fragments for nested components. For example, you need to build Form Layout component that contains a tabbed group that contains an item.

<DxFormLayout>
    <DxFormLayoutTabPage Caption="My Tab">
        <DxFormLayoutItem Caption="DynLayoutItem" ColSpanMd="6">
        </DxFormLayoutItem>
    </DxFormLayoutTabPage>
</DxFormLayout>

To build this component, define a render fragment that contains <DxFormLayoutTabPage>. Then add the ChildContent attribute that specifies a nested render fragment for <DxFormLayoutItem>. To add the attribute, call the AddAttribute(sequence, "ChildContent", RenderFragment) method.

<DxFormLayout>
    @renderLayoutTabPage()
</DxFormLayout>

@code {
    private RenderFragment renderLayoutTabPage() {
        RenderFragment item = b => {
            b.OpenComponent<DxFormLayoutTabPage>(0);
            b.AddAttribute(1, "Caption", "My tab");
            b.AddAttribute(2, "ChildContent", (RenderFragment)((tabPageBuilder) => {
                tabPageBuilder.OpenComponent<DxFormLayoutItem>(0);
                tabPageBuilder.AddAttribute(1, "Caption", "DynLayoutItem");
                tabPageBuilder.AddAttribute(2, "ColSpanMd", 6); 
                tabPageBuilder.CloseComponent();
            }));
            b.CloseComponent();
        };
        return item;
    }
}

A component can contain a template with another component, for example:

<DxFormLayout>
    <DxFormLayoutTabPage Caption="My Tab">
        <DxFormLayoutItem Caption="DynLayoutItem" ColSpanMd="6">
            <Template>
                <DxTextBox @bind-Text="@text"></DxTextBox>
            </Template>
        </DxFormLayoutItem>
    </DxFormLayoutTabPage>
</DxFormLayout>

To build this component, use the code below. To add the <Template> attribute, use the AddAttribute(sequence, "Template", RenderFragment<Object>delegate) method.

<DxFormLayout>
    @renderLayoutTabPage()
</DxFormLayout>

@code {
    private RenderFragment renderLayoutTabPage() {
        RenderFragment item = b => {
            b.OpenComponent<DxFormLayoutTabPage>(0);
            b.AddAttribute(1, "Caption", "My tab");
            b.AddAttribute(2, "ChildContent", (RenderFragment)((tabPageBuilder) => {
                tabPageBuilder.OpenComponent<DxFormLayoutItem>(0);
                tabPageBuilder.AddAttribute(1, "Caption", "DynLayoutItem");
                tabPageBuilder.AddAttribute(2, "ColSpanMd", 6);
                tabPageBuilder.AddAttribute(5, "Template", (RenderFragment<Object>)((context) => ((itemTemplate) => {
                    itemTemplate.OpenComponent<DxTextBox>(0);
                    itemTemplate.AddAttribute(1, "Text", text);
                    itemTemplate.CloseComponent();
                })));
                tabPageBuilder.CloseComponent();
            }));
            b.CloseComponent();
        };
        return item;
    }
}

BuildRenderTree Method

To create and reuse a modified version of an existing DevExpress Blazor component, create a ComponentBase descendant and override the BuildRenderTree method to generate the component's content.

For example, you need to add multiple Data Grid components to an application, and some of them (Group 1) should have the same settings. You can end up with repeated code like demonstrated in the following example:

Group 1
<DxDataGrid PageSize="5" ShowFilterRow="false" ShowPager="false" ShowGroupPanel="true" ...>
<DxDataGrid PageSize="5" ShowFilterRow="false" ShowPager="false" ShowGroupPanel="true" ...>
<DxDataGrid PageSize="5" ShowFilterRow="false" ShowPager="false" ShowGroupPanel="true" ...>
...
Group 2
<DxDataGrid PageSize="15" ShowFilterRow="false"  AutoCollapseDetailRow="true" ShowDetailRow="true" ShowFilterRow="false" SelectionMode="DataGridSelectionMode.None"  ...>
...

You can turn repeated code into a single composite parameter (for example, Settings). This parameter can accept a dictionary with grid settings.

  1. Create a ComponentBase descendant (for example, MyGrid<T>).
  2. Add the Data, ChildContent (for columns), and Settings parameters to the class.
  3. Override the BuildRenderTree method to define the MyGrid<T> component's markup. This method accepts a RenderTreeBuilder object. Use its methods to add <DxDataGrid<T>> to the markup and specify the component's attributes.

    public class MyGrid<T> : ComponentBase {
        [Parameter]
        public IEnumerable<T> Data { get; set; }
        [Parameter]
        public RenderFragment ChildContent { get; set; }
        [Parameter]
        public Dictionary<string, object> Settings { get; set; }
        protected override void BuildRenderTree(RenderTreeBuilder builder) {
            builder.OpenComponent<DxDataGrid<T>>(0);
            builder.AddAttribute(1, "Data", (object)Data);
            builder.AddAttribute(2, "Columns", ChildContent);
            if (Settings != null) {
                builder.AddMultipleAttributes(3, Settings);
                //OR
                //int seq = 3;
                //foreach (var item in Settings) {
                //    builder.AddAttribute(seq++, item.Key, item.Value);
                //}
            }
            builder.CloseComponent();
        }
    }
    
  4. Add the MyGrid component to a page.

    <MyGrid Data="Forecasts" Settings="InputAttributes" >
        <DxDataGridCommandColumn Width="150px" />
        <DxDataGridColumn Field="@nameof(WeatherForecast.Date)"></DxDataGridColumn>
        <DxDataGridColumn Field="@nameof(WeatherForecast.TemperatureC)"></DxDataGridColumn>
        <DxDataGridColumn Field="@nameof(WeatherForecast.TemperatureF)"></DxDataGridColumn>
        <DxDataGridComboBoxColumn Data="@WeatherForecastService.Summaries" Field="@nameof(WeatherForecast.Summary)"></DxDataGridComboBoxColumn>
    </MyGrid>
    
    @code {
        public List<WeatherForecast> Forecasts { get; set; }
        public Dictionary<string, object> InputAttributes { get; set; } =
            new Dictionary<string, object>() {
                { "PageSize", 5 },
                { "ShowFilterRow", false },
                { "ShowPager" , false },
                { "ShowGroupPanel", true }
            };
        protected override async Task OnInitializedAsync() {
            base.OnInitialized();
            WeatherForecast[] data = await ForecastService.GetForecastAsync(DateTime.Now);
            Forecasts = data.ToList();
        }
    }
    
NOTE

Example: A complete sample project is available at How to reuse a component and configure its state via a dictionary.