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 theValue
property. - When you change the
DxSpinEdit
value in UI, theValue
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:
- Customize a Built-in Property Editor (Blazor)
- How to: Access the Grid Component in a List View
- Ways to Access UI Elements and Their Controls
- ActionBase.CustomizeControl
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");
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:
- Manually Build a Render Tree (RenderTreeBuilder)
- Implement a Property Editor Based on a Custom Component (Blazor)
- How to: Use a Custom Component to Implement List Editor (Blazor)
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:
- Component Model properties must have the same type and name as related component’s parameters.
- 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: