Skip to main content
All docs
V24.1

Underlying Controls and Components Behind UI Elements (ASP.NET Core Blazor)

  • 5 minutes to read

Overview

In XAF Blazor applications, UI is built using Razor components. Component classes are usually Razor markup pages with .razor file extension. The code snippet below demonstrates a custom Razor component:

<DxSpinEdit @bind-Value="@Value" ShowSpinButtons="false" CssClass="my-custom-class"></DxSpinEdit>

@code {
    int Value { get; set; } = 10;
}

In this code snippet, you add a DxSpinEdit<T> control to the markup, and specify the following parameters:

  • ShowSpinButtons - disables increment/decrement buttons.
  • CssClass - adds a custom class to HTML elements rendered by the DxSpinEdit component.

@bind-Value="@Value" is responsible for the following functionality:

  • When XAF renders a DxSpinEdit component, its value is set to the Value property.
  • When you change the DxSpinEdit value in UI, the Value property is updated.

See also:

You will rarely need to create custom components., but may need to customize built-in components. To do that, you don’t need to modify markup code. Use XAF component models - proxy objects that allow you to change the parameters of their respective components from anywhere in code.

For example, DxSpinEditModel is an object representation of the DxSpinEdit component. Each DxSpinEditModel property has a corresponding DxSpinEdit property:

public class DxSpinEditModel : ComponentModelBase {
    //
    public bool ShowSpinButtons {
        get => GetPropertyValue<bool>(true);
        set => SetPropertyValue(value);
    }
    public string CssClass {
        get => GetPropertyValue<string>();
        set => SetPropertyValue(value);
    }
}

To customize a property editor’s component in an auto-generated XAF View, you have to work with Component Model at runtime or in code:

using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Blazor.Editors;

public class AddCustomCssClassController : ViewController<DetailView> {
    protected override void OnActivated() {
        base.OnActivated();
        View.CustomizeViewItemControl<NumericPropertyEditor>(this, editor => {
            editor.ComponentModel.ShowSpinButtons = false;
            editor.ComponentModel.CssClass += " my-custom-class";
        });
    }
}

You can use Component Models to customize visual appearance and behavior of Property Editors (such as DxTextBox, DxDateEdit, DxSpinEdit), List Editors (such as DxGrid, DxScheduler), navigation (such as DxAccordion, DxTreeView), DxToolbar, and other components in XAF ASP.NET Core Blazor applications.

See also:

A Base Class for Underlying XAF Blazor Components

DevExpress.ExpressApp.Blazor.Components.Models.ComponentModelBase is an abstract base class for all Component Models. Built-in Component Models replicate all parameters of the corresponding component by default. However, when you create a custom Component Model, you can include only necessary parameters.

In most cases, you can use the SetPropertyValue and GetPropertyValue methods to add the required component parameters to your Component Model class:

using System.Linq.Expressions;
using DevExpress.ExpressApp.Blazor.Components.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;

namespace YourSolutionName.Blazor.Server.Editors;

public class InputTextModel : ComponentModelBase {
    public string Value {
        get => GetPropertyValue<string>();
        set => SetPropertyValue(value);
    }
    public EventCallback<string> ValueChanged {
        get => GetPropertyValue<EventCallback<string>>();
        set => SetPropertyValue(value);
    }
    public Expression<Func<string>> ValueExpression {
        get => GetPropertyValue<Expression<Func<string>>>();
        set => SetPropertyValue(value);
    }
    public override Type ComponentType => typeof(InputText);
}

The ComponentType property is described in the next section.

The following methods and properties can also be useful:

ComponentModelBase.SetAttribute

This method sets additional attributes for the component (an equivalent of the ParameterAttribute.CaptureUnmatchedValues property). For more information, refer to the following topic: Arbitrary attributes.

public void SetAttribute(string name, object value)

Example of usage:

InputModel model = new InputModel();
model.SetAttribute("title", "A tooltip text");

XAF ASP.NET Core Blazor ComponentModel SetAttribute Method, DevExpress

ComponentModelBase.Attributes

This property contains a list of additional attributes specified by the ComponentModelBase.SetAttribute method.

public IReadOnlyDictionary<string, object> Attributes { get; }

Example of usage:

<InputText Value=@ComponentModel.Value
        @* --- *@
        @attributes=ComponentModel.Attributes />

@code {
    [Parameter] public InputModel ComponentModel { get; set; }
}

See also:

Component State Changes and Content Render

ComponentModelBase implements the IComponentModel and IComponentModelRenderable interfaces.

namespace DevExpress.ExpressApp.Blazor.Components.Models;

public interface IComponentModel {
    event EventHandler Changed;
}
public interface IComponentModelRenderable : IComponentModel {
    Type ComponentType { get; }
}

The IComponentModel.Changed event is triggered every time a property of the Component Model changes. For example, when you call the SetPropertyValue method with a new value.

The ComponentType property is required to create a RenderFragment and render the underlying component.

Use the following extension method to create a RenderFragment of a component defined by the corresponding Component Model:

namespace DevExpress.ExpressApp.Blazor.Components.Models;

public static class ComponentModelRenderer {
    public static RenderFragment GetComponentContent(this IComponentModelRenderable renderable, Action<object> addComponentReferenceCapture = default);
}

The following conditions must be met:

  1. Component Model properties must have the same type and name as related component’s parameters.
  2. The ComponentType property must return the component type that will be rendered.

An example of extension method use:

using DevExpress.ExpressApp.Blazor.Components.Models;
// ...
var model = new InputTextModel();
// ...
RenderFragment inputTextContent = model.GetComponentContent();

DevExpress.ExpressApp.Blazor.Components.ComponentModelObserver is a component that wraps the main component and subscribes to the ComponentModel.Changed event. When the event is triggered, ComponentModelObserver calls the StateHasChanged method and re-renders itself and the child component.

In XAF:

public class CustomViewItem : ViewItem, IComponentContentHolder {
    private RenderFragment componentContent;
    public InputTextModel ComponentModel { get; private set; }
    protected override object CreateControlCore() {
        ComponentModel = new InputTextModel();
        // ...
        return ComponentModel;
    }

    public RenderFragment ComponentContent {
        get {
            componentContent ??= ComponentModelObserver.Create(ComponentModel, ComponentModel.GetComponentContent());
            return componentContent;
        }
    }
    // ...
}

The following example shows a possible ComponentContent representation in Razor markup:

<ComponentModelObserver ComponentModel="@ComponentModel">
    @ComponentModel.GetComponentContent()
</ComponentModelObserver>

@code {
    public InputModel ComponentModel { get; set; }
}

Handle Component Events

Razor components usually have events implemented as properties of the EventCallback<T> type. Examples include DxTextBox.TextChanged or InputBase.ValueChanged.

XAF can handle some of these events. To safely add your own event handler without overriding the default XAF implementation, we recommend that you use the following pattern:

var defaultEventHandler = ComponentModel.SomeEvent;
ComponentModel.SomeEvent = EventCallback.Factory.Create<TArgument>(this, async eventArgument => {
    // Comment out the line below if you do not want XAF to handle this event.
    await defaultEventHandler.InvokeAsync(eventArgument);

    // Implement your custom logic here.
    // ...
});

See also: