Skip to main content
All docs
V23.2

How to: Show a Custom Data-Bound Control in an XAF View (Blazor) - External Data

  • 4 minutes to read

This topic demonstrates how to add a custom data-bound (data-aware) control to a View in a Blazor application. It also explains how to extend app navigation and add a shortcut to the new View.

DevExpress XAF - A Custom Data-Bound Control

This topic is based on the MainDemo Blazor Server demo application that ships with XAF. You can find this demo in the following folder: %PUBLIC%\Documents\DevExpress Demos 23.2\Components\XAF\MainDemo.NET.EFCore\CS\MainDemo.Blazor.Server.

Create a Razor Component

Blazor apps are built using Razor components. In this example, we implement a custom Razor component based on a DxChart control that displays a graph of incomplete tasks.

To add this component to your project, follow the steps below:

  1. In the Solution Explorer, right-click your Blazor project’s name and select Add -> New Item from the ensuing context menu.
  2. Specify a component name (TaskChartComponent.razor).
  3. Add the following code to the created file.

    @using MainDemo.Module.BusinessObjects;
    @using System.Drawing;
    @using DevExpress.Blazor;
    
    <DxChart T="DemoTask"
             Data="@ComponentModel.DataSource"
             Width="100%">
        <DxChartTitle Text="Tasks">
            <DxChartSubTitle Text="Breakdown by department"></DxChartSubTitle>
        </DxChartTitle>
        @RenderSeries(Priority.Low)
        @RenderSeries(Priority.Normal)
        @RenderSeries(Priority.High)
        <DxChartLegend Position="RelativePosition.Outside"
                       HorizontalAlignment="HorizontalAlignment.Center"
                       VerticalAlignment="VerticalEdge.Bottom" />
        <DxChartTooltip Enabled="true"
                        Position="RelativePosition.Outside">
            <div style="margin: 0.75rem">
                <div class="fw-bold">@context.Point.Argument</div>
                <div>Priority: @context.Point.SeriesName</div>
                <div>Tasks in progress: @($"{context.Point.Value:N0}")</div>
            </div>
        </DxChartTooltip>
    </DxChart>
    
    @code {
        [Parameter] public TaskChartComponentModel ComponentModel { get; set; }
        public static RenderFragment Create(TaskChartComponentModel componentModel) => @<TaskChartComponent ComponentModel="@componentModel" />;
        private RenderFragment RenderSeries(Priority priority) {
            var color = priority switch {
                Priority.Low => Color.FromArgb(0x3c, 0x7e, 0x38),
                Priority.Normal => Color.FromArgb(0xd6, 0x82, 0x29),
                Priority.High => Color.FromArgb(0xd3, 0x30, 0x3d),
                _ => throw new ArgumentOutOfRangeException()
            };
            string name = $"{priority} priority";
            return @<DxChartStackedBarSeries T="DemoTask"
                                             Name="@name"
                                             Color="@color"
                                             TArgument="string"
                                             ArgumentField="@(task => GetDepartment(task))"
                                             TValue="int"
                                             ValueField="@(task => task.Status == Module.BusinessObjects.TaskStatus.Completed ? 0 : 1)"
                                             SummaryMethod="Enumerable.Sum"
                                             Filter="@(task => task.Priority == priority)" />;
        }
        private string GetDepartment(DemoTask task) {
            return (task.AssignedTo as Employee)?.Department?.Title ?? "None";
        }
    }
    
  4. In the Properties window, set this file’s Build Action to Content.

Create a Control and Bind It to Data Using Object Space

Follow the steps below to create a custom control:

  1. Add a new file (TaskChartControl.cs) to your Blazor project.
  2. Add the following code to the created file.

    using DevExpress.ExpressApp;
    using DevExpress.ExpressApp.Blazor;
    using DevExpress.ExpressApp.Blazor.Components;
    using DevExpress.ExpressApp.Blazor.Components.Models;
    using DevExpress.ExpressApp.Editors;
    using MainDemo.Module.BusinessObjects;
    using Microsoft.AspNetCore.Components;
    
    namespace MainDemo.Blazor.Server;
    
    public class TaskChartComponentModel : ComponentModelBase {
        public IEnumerable<DemoTask> DataSource {
            get => GetPropertyValue<IEnumerable<DemoTask>>();
            set => SetPropertyValue(value);
        }
    }
    
    public class TaskChartControl : IComplexControl, IComponentContentHolder {
        private RenderFragment componentContent;
        private TaskChartComponentModel componentModel = new();
        private IObjectSpace objectSpace;
    
        public RenderFragment ComponentContent {
            get {
                componentContent ??= ComponentModelObserver.Create(componentModel, TaskChartComponent.Create(componentModel));
                return componentContent;
            }
        }
    
        public void Refresh() {
            componentModel.DataSource = objectSpace.GetObjects<DemoTask>().AsEnumerable();
        }
    
        public void Setup(IObjectSpace objectSpace, XafApplication application) {
            this.objectSpace = objectSpace;
            componentModel.DataSource = objectSpace.GetObjects<DemoTask>().AsEnumerable();
        }
    }
    

The control in this example implements the IComplexControl interface. This allows the control to access an ObjectSpace instance and use the Object Space API to read the required data and then initialize the data source or refresh the data if necessary.

To display a Razor component in the UI, a View Item’s control must implement the IComponentContentHolder interface. The ComponentContent member returns a RenderFragment. RenderFragment is a delegate type that specifies a segment of UI content. In this tutorial, you need to deal with the following two RenderFragment delegates:

  • A RenderFragment returned by the TaskChartComponent.Create(componentModel) method. This delegate defines a Chart control that receives data from a Component Model instance.
  • A RenderFragment returned by the ComponentModelObserver.Create() method. In this method, you wrap the Chart’s component model in a built-in XAF component (ComponentModelObserver) to ensure that the TaskChartComponent re-renders whenever its component model (TaskChartComponentModel) changes. For more information on rendering and other Razor component lifecycle events, refer to the following Microsoft article: ASP.NET Core Razor component lifecycle.

Add the Control to a View

  1. In the Blazor application project, double-click the Model.xafml file to start the Model Editor. Right-click the Views node and choose Add | DashboardView.

    DevExpress XAF - Add a View

  2. Set the Id property to TaskChartView.

    DevExpress XAF - View ID

  3. Right-click the Views | TaskChartView | Items node and choose Add… | ControlDetailItem.

    DevExpress XAF - Add a ControlDetailItem

  4. Set the Id property to TaskChartView, and the IModelControlDetailItem.ControlTypeName property - to the type of the custom User Control you created (e.g., MainDemo.Blazor.Server.TaskChartControl).

    DevExpress XAF - Specify a Control Type

Note

You can add the ControlViewItem View Item to any existing Detail View or Dashboard View instead of creating a new Dashboard View.

Create a Navigation Item to Show the View

  1. Navigate to the NavigationItems | Items | Default | Items node. Right-click the Items node and select Add… | NavigationItem from the invoked context menu.

    DevExpress XAF - Add a Navigation Item

  2. For the newly added node, in the IModelNavigationItem.View dropdown list, select the View you created earlier (TaskChartView).

    DevExpress XAF - Specify a View for the Navigation Item

  3. Run your Blazor application and click TaskChartView in the navigation tree. The Chart View bound to the DemoTask collection is displayed.

    DevExpress XAF - A Custom Data-Bound Control