Ticket T943982
Visible to All Users

Blazor - How to integrate a custom DevExtreme component and bind it to a data source

created 4 years ago

Dear XAF team,

I am trying to implement Devextreme Gantt, after reading extensive help topics I manager to create a small functional demo (attached), need help with below:

  1. how to pass list of objects to Gantt (in attached demo, i have hard coded data)
  2. How to implement callback from javascript for the value changed in Gantt

Many Thx,
Bunty

Answers approved by DevExpress Support

created 4 years ago (modified 3 years ago)

Solution architecture

To integrate a DevExtreme component in XAF Blazor, we will create a custom Blazor component that will render a DevExtreme component and bind XAF data to it.

Display a Blazor page inside an View

1. In the SolutionName.Module.Blazor project, create a custom View Item as described in How to: Implement a View Item.
2. In the custom View Item class (SolutionName.Module.Blazor\Editors\IComponentViewItem.cs), override its CreateControlCore method to create a RenderFragment delegate.
3. Create the new Blazor component named "Grid.razor", override the OnAfterRenderAsync method and use the JSRuntime.InvokeVoidAsync method to render and initialize the DevExtreme Data Grid widget.

SolutionName.Module.Blazor\Editors\IComponentViewItem.cs

C#
using System; using DevExpress.ExpressApp; using DevExpress.ExpressApp.Blazor; using DevExpress.ExpressApp.Editors; using DevExpress.ExpressApp.Model; using Microsoft.AspNetCore.Components; namespace BlazorTest.Module.Blazor.Editors { public interface IModelComponentViewItem : IModelViewItem { } [ViewItem(typeof(IModelComponentViewItem))] public class IComponentViewItem : ViewItem, IComplexViewItem { class ComponentContentHolder : IComponentContentHolder { private IComponentViewItem viewItem; public ComponentContentHolder(IComponentViewItem viewItem) { this.viewItem = viewItem; } RenderFragment IComponentContentHolder.ComponentContent => Grid.Create(viewItem.Model.Caption); } public IModelComponentViewItem Model { get; set; } public IComponentViewItem(IModelComponentViewItem model, Type classType) : base(classType, model.Id) { Model = model; } XafApplication application; protected override object CreateControlCore() { return new ComponentContentHolder(this); } void IComplexViewItem.Setup(IObjectSpace objectSpace, XafApplication application) { this.application = application; } } }

4. In the Model Editor for the SolutionName.Module.Blazor project, add the custom View Item to a Dashboard View.

Clipboard-File-2.png

Bind a DevExteme component to data

DevExtreme components are purely client-side components. To pass data to the Blazor component and get it back on the server, use standard JavaScript Interop methodologies:

To manipulate XAF data in your Blazor component, obtain an IObjectSpace instance:

  1. inject our DevExpress.ExpressApp.Blazor.Services.IXafApplicationProvider service to your Blazor component.
  2. Call IXafApplicationProvider.GetApplication to obtain an XafApplication instance. Please note that our DevExpress.ExpressApp.Blazor.Services.IXafApplicationProvider service is an internal/undocumented API and may change in the future.
  3. Call XafApplication.CreateObjectSpace to get an IObjectSpace instance.

DevExtreme components allow you to pass a CustomStore instance as their data source. To populate a DevExteme component with data:

  1. Get an array of your objects from an IObjectSpace in the OnAfterRenderAsync method of your Blazor component.
  2. Pass this array as a parameter to your JavaScript function that creates the DevExtreme component.
  3. Use this array in the load function of the CustomStore of your DevExtreme component.

Implement CRUD operations in a DevExtreme component

To save changes made in a DevExtreme component:

  1. Implement the insertupdate and remove functions of the CustomStore of your DevExtreme component.
  2. Call your Blazor component methods in the insertupdate and remove functions to pass appropriate parameters to your Blazor component methods (for instance, using JSRuntime.InvokeVoidAsync).

For example, your Blazor component implementation and JavaScript code (code used to create a DevExtreme DataGrid component) may be as follows:

SolutionName.Blazor.Server\Pages\Grid.razor

Razor
@using Microsoft.JSInterop @implements IDisposable @inject IJSRuntime JSRuntime @inject DevExpress.ExpressApp.Blazor.Services.IXafApplicationProvider ApplicationProvider <h2>@Caption</h2> <div id="grid" style="width:100%; height: 800px;"></div> @code { [Parameter] public string Caption { get; set; } private DevExpress.ExpressApp.IObjectSpace objectSpace; private IEnumerable<Item> GetData() { return objectSpace.GetObjects<BlazorTest.Module.BusinessObjects.DomainObject1>().Select(item => new Item { Oid = item.Oid, Name = item.Name, Address = item.Address }); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) { return; } var app = ApplicationProvider.GetApplication(); objectSpace = app.CreateObjectSpace(typeof(BlazorTest.Module.BusinessObjects.DomainObject1)); await JSRuntime.InvokeVoidAsync("JsFunctions.InitDevExtremeDataGrid", GetData(), DotNetObjectReference.Create(gridUpdateInvokeHelper)); } private GridUpdateInvokeHelper gridUpdateInvokeHelper; protected override void OnInitialized() { gridUpdateInvokeHelper = new GridUpdateInvokeHelper(UpdateRecord); } private async void UpdateRecord(UpdateItem param) { var obj = objectSpace.GetObjectByKey<BlazorTest.Module.BusinessObjects.DomainObject1>(param.Key); if (obj == null) { return; } obj.Name = param.Values.Name; obj.Address = param.Values.Address; objectSpace.CommitChanges(); await JSRuntime.InvokeVoidAsync("JsFunctions.RefreshDevExtremeDataGrid", GetData()); } void IDisposable.Dispose() { objectSpace?.Dispose(); } public static RenderFragment Create(string caption) =>@<Grid Caption=@caption />; }

SolutionName.Blazor.Server\Pages\GridUpdateInvokeHelper.cs

C#
using Microsoft.JSInterop; using System; namespace SolutionName.Blazor.Server.Pages { public class GridUpdateInvokeHelper { private Action<UpdateItem> action; public GridUpdateInvokeHelper(Action<UpdateItem> action) { this.action = action; } [JSInvokable] public void UpdateRecordCaller(UpdateItem param) { this.action.Invoke(param); } } public class Item { public Guid Oid { get; set; } public string Name { get; set; } public string Address { get; set; } } public class UpdateItem { public Guid Key { get; set; } public Item Values { get; set; } } }

SolutionName.Blazor.Server\wwwroot\js\index.js

JavaScript
window.JsFunctions = { gridData: [], setData: function (data) { window.JsFunctions.gridData = data; }, RefreshDevExtremeDataGrid: function (data) { window.JsFunctions.setData(data); var element = document.getElementById('grid'); DevExpress.ui.dxDataGrid.getInstance(element).refresh(); }, InitDevExtremeDataGrid: function (data, dotnetHelper) { window.JsFunctions.setData(data); var element = document.getElementById('grid'); new DevExpress.ui.dxDataGrid(element, { dataSource: { key: 'oid', loadMode: "raw", load: function (loadOptions) { return window.JsFunctions.gridData; }, update: function (key, values) { return window.JsFunctions.UpdateRecord(key, values, dotnetHelper); }, }, editing: { allowUpdating: true }, onRowUpdating: function (options) { options.newData = Object.assign({}, options.oldData, options.newData); }, onDisposing: function () { dotnetHelper.dispose(); } }); }, UpdateRecord: function (key, values, dotnetHelper) { return dotnetHelper.invokeMethodAsync('UpdateRecordCaller', { key, values }); }, };

See Also

  • A sample project in the attachment to illustrate this solution;
  • FAQ: XAF ASP.NET Core Blazor Server UI (the section How to use components that are not integrated in the Blazor UI by default?)
    Show previous comments (8)
    DevExpress Support Team 4 years ago

      Hi Bunty,

      I updated our answer to use a component instance method helper class from the following Microsoft article to call a .NET method from JavaScript: Call .NET methods from JavaScript functions in ASP.NET Core Blazor -> Component instance method helper class. A call of a component static method is not suitable for multi-user environments. I also briefly described your approach to render a Blazor component on an XAF View.

        What if I wanted to pass the View (or, at least, the Oid of the object) to the Blazor component, if the ViewItem is added to a DetailView and not to a ListView?

        DevExpress Support Team 4 years ago

          Hello,

          I created a separate ticket on your behalf: (T991322: Blazor - How to pass the View (or, at least, the Oid of the object) to the Blazor component, if the ViewItem is added to a DetailView). We placed it in our processing queue and will process it shortly.

          Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

          Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.