From b8936adfb8be24cf3e8f381afc1d7c3cb7223f5a Mon Sep 17 00:00:00 2001 From: Efim Bryliakov Date: Mon, 8 Dec 2025 19:35:14 +0400 Subject: [PATCH 1/4] Update custom view item example to follow modern best practices Simplified the ButtonDetailViewItemBlazor implementation: - ViewItem now implements IComponentContentHolder directly (no wrapper class needed) - Component model is created with all parameters in CreateControlCore This approach reduces boilerplate code and better demonstrates the recommended pattern for creating custom view items in XAF Blazor applications. --- .../Editors/ButtonViewItem/Button.razor | 10 +++ .../ButtonDetailViewItemBlazor.cs | 66 +++++++++---------- .../Editors/ButtonViewItem/ButtonModel.cs | 26 ++++---- .../ButtonViewItem/ButtonRenderer.razor | 11 ---- 4 files changed, 54 insertions(+), 59 deletions(-) create mode 100644 CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/Button.razor delete mode 100644 CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonRenderer.razor diff --git a/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/Button.razor b/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/Button.razor new file mode 100644 index 0000000..1efc46f --- /dev/null +++ b/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/Button.razor @@ -0,0 +1,10 @@ +@namespace CustomViewItem.Blazor.Server.Editors.ButtonViewItem + + + +@code { + [Parameter] + public string Text { get; set; } + [Parameter] + public EventCallback Click { get; set; } +} diff --git a/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonDetailViewItemBlazor.cs b/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonDetailViewItemBlazor.cs index 6cdf9ed..646acfe 100644 --- a/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonDetailViewItemBlazor.cs +++ b/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonDetailViewItemBlazor.cs @@ -1,45 +1,39 @@ -using System; -using DevExpress.ExpressApp; +using DevExpress.ExpressApp; using DevExpress.ExpressApp.Blazor; +using DevExpress.ExpressApp.Blazor.Components; +using DevExpress.ExpressApp.Blazor.Components.Models; using DevExpress.ExpressApp.Editors; using DevExpress.ExpressApp.Model; using Microsoft.AspNetCore.Components; -using DevExpress.ExpressApp.Blazor.Components; +namespace CustomViewItem.Blazor.Server.Editors.ButtonViewItem; + +public interface IModelButtonDetailViewItemBlazor : IModelViewItem; -namespace MySolution.Module.Blazor { - public interface IModelButtonDetailViewItemBlazor : IModelViewItem { } +[ViewItem(typeof(IModelButtonDetailViewItemBlazor))] +public class ButtonDetailViewItemBlazor(IModelViewItem model, Type objectType) : + ViewItem(objectType, model.Id), + IComponentContentHolder, + IComplexViewItem +{ + private ButtonModel componentModel; + private XafApplication application; - [ViewItem(typeof(IModelButtonDetailViewItemBlazor))] - public class ButtonDetailViewItemBlazor : ViewItem, IComplexViewItem { - public class ButtonHolder : IComponentContentHolder { - public ButtonHolder(ButtonModel componentModel) { - ComponentModel = componentModel; - } - public ButtonModel ComponentModel { get; } - RenderFragment IComponentContentHolder.ComponentContent => ComponentModelObserver.Create(ComponentModel, ButtonRenderer.Create(ComponentModel)); - } - private XafApplication application; - public ButtonDetailViewItemBlazor(IModelViewItem model, Type objectType) : base(objectType, model.Id) { } - void IComplexViewItem.Setup(IObjectSpace objectSpace, XafApplication application) { - this.application = application; - } - protected override object CreateControlCore() => new ButtonHolder(new ButtonModel()); - protected override void OnControlCreated() { - if (Control is ButtonHolder holder) { - holder.ComponentModel.Text = "Click me!"; - holder.ComponentModel.Click += ComponentModel_Click; - } - base.OnControlCreated(); - } - public override void BreakLinksToControl(bool unwireEventsOnly) { - if (Control is ButtonHolder holder) { - holder.ComponentModel.Click -= ComponentModel_Click; - } - base.BreakLinksToControl(unwireEventsOnly); - } - private void ComponentModel_Click(object sender, EventArgs e) { - application.ShowViewStrategy.ShowMessage("Action is executed!"); - } + RenderFragment IComponentContentHolder.ComponentContent => + ComponentModelObserver.Create(componentModel, componentModel.GetComponentContent()); + void IComplexViewItem.Setup(IObjectSpace objectSpace, XafApplication application) { + this.application = application; + } + + protected override object CreateControlCore() { + componentModel = new ButtonModel + { + Text = "Click me!", + Click = EventCallback.Factory.Create(this, ComponentModel_Click), + }; + return componentModel; + } + private void ComponentModel_Click() { + application.ShowViewStrategy.ShowMessage("Action is executed!"); } } diff --git a/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonModel.cs b/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonModel.cs index dc5e1b6..5d0b897 100644 --- a/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonModel.cs +++ b/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonModel.cs @@ -1,15 +1,17 @@ -using System; -using DevExpress.ExpressApp.Blazor.Components.Models; +using DevExpress.ExpressApp.Blazor.Components.Models; +using Microsoft.AspNetCore.Components; -namespace MySolution.Module.Blazor { - public class ButtonModel : ComponentModelBase { - public string Text { - get => GetPropertyValue(); - set => SetPropertyValue(value); - } - public void ClickFromUI() { - Click?.Invoke(this, EventArgs.Empty); - } - public event EventHandler Click; +namespace CustomViewItem.Blazor.Server.Editors.ButtonViewItem; + +public class ButtonModel : ComponentModelBase { + public string Text { + get => GetPropertyValue(); + set => SetPropertyValue(value); + } + public EventCallback Click { + get => GetPropertyValue(); + set => SetPropertyValue(value); } + + public override Type ComponentType => typeof(Button); } diff --git a/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonRenderer.razor b/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonRenderer.razor deleted file mode 100644 index 18d7ae7..0000000 --- a/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonRenderer.razor +++ /dev/null @@ -1,11 +0,0 @@ -@using DevExpress.Blazor -@namespace MySolution.Module.Blazor - - ComponentModel.ClickFromUI()) /> - -@code { - [Parameter] - public ButtonModel ComponentModel { get; set; } - public static RenderFragment Create(ButtonModel componentModel) => - @; -} From 23089e2d64abdf7f92423e825a8a4f7f520e2037 Mon Sep 17 00:00:00 2001 From: Efim Bryliakov Date: Mon, 8 Dec 2025 19:35:34 +0400 Subject: [PATCH 2/4] Add launchSettings.json --- .gitignore | 1 - .../Properties/launchSettings.json | 12 ++++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Properties/launchSettings.json diff --git a/.gitignore b/.gitignore index 1c87961..52c59e2 100644 --- a/.gitignore +++ b/.gitignore @@ -49,7 +49,6 @@ BenchmarkDotNet.Artifacts/ project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json *_i.c *_p.c diff --git a/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Properties/launchSettings.json b/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Properties/launchSettings.json new file mode 100644 index 0000000..113ddee --- /dev/null +++ b/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "CustomViewItem.Blazor.Server": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} From be23d230fc86292b75ec831a16b236e63a3f3171 Mon Sep 17 00:00:00 2001 From: Efim Bryliakov Date: Tue, 9 Dec 2025 13:50:47 +0400 Subject: [PATCH 3/4] fix review issues --- .../Editors/ButtonViewItem/Button.razor | 2 +- .../Editors/ButtonViewItem/ButtonDetailViewItemBlazor.cs | 5 ++++- .../Editors/ButtonViewItem/ButtonModel.cs | 5 +++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/Button.razor b/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/Button.razor index 1efc46f..aa8d5e9 100644 --- a/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/Button.razor +++ b/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/Button.razor @@ -6,5 +6,5 @@ [Parameter] public string Text { get; set; } [Parameter] - public EventCallback Click { get; set; } + public EventCallback Click { get; set; } } diff --git a/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonDetailViewItemBlazor.cs b/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonDetailViewItemBlazor.cs index 646acfe..0bab011 100644 --- a/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonDetailViewItemBlazor.cs +++ b/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonDetailViewItemBlazor.cs @@ -5,6 +5,7 @@ using DevExpress.ExpressApp.Editors; using DevExpress.ExpressApp.Model; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; namespace CustomViewItem.Blazor.Server.Editors.ButtonViewItem; @@ -16,6 +17,8 @@ public class ButtonDetailViewItemBlazor(IModelViewItem model, Type objectType) : IComponentContentHolder, IComplexViewItem { + public ButtonModel ComponentModel => componentModel; + private ButtonModel componentModel; private XafApplication application; @@ -29,7 +32,7 @@ protected override object CreateControlCore() { componentModel = new ButtonModel { Text = "Click me!", - Click = EventCallback.Factory.Create(this, ComponentModel_Click), + Click = EventCallback.Factory.Create(this, ComponentModel_Click), }; return componentModel; } diff --git a/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonModel.cs b/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonModel.cs index 5d0b897..9516bcd 100644 --- a/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonModel.cs +++ b/CS/EFCore/CustomViewItem/CustomViewItem.Blazor.Server/Editors/ButtonViewItem/ButtonModel.cs @@ -1,5 +1,6 @@ using DevExpress.ExpressApp.Blazor.Components.Models; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; namespace CustomViewItem.Blazor.Server.Editors.ButtonViewItem; @@ -8,8 +9,8 @@ public string Text { get => GetPropertyValue(); set => SetPropertyValue(value); } - public EventCallback Click { - get => GetPropertyValue(); + public EventCallback Click { + get => GetPropertyValue>(); set => SetPropertyValue(value); } From b92065f45fc30c60d8cb8d89d93ebedf09ba80b1 Mon Sep 17 00:00:00 2001 From: Efim Bryliakov Date: Tue, 9 Dec 2025 14:01:32 +0400 Subject: [PATCH 4/4] add the same edits to XPO project --- .../CustomViewItemXPO.Blazor.Server.csproj | 3 - .../Editors/ButtonViewItem/Button.razor | 10 +++ .../ButtonDetailViewItemBlazor.cs | 71 +++++++++---------- .../Editors/ButtonViewItem/ButtonModel.cs | 29 ++++---- .../ButtonViewItem/ButtonRenderer.razor | 11 --- .../Properties/launchSettings.json | 12 ++++ 6 files changed, 72 insertions(+), 64 deletions(-) create mode 100644 CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/Button.razor delete mode 100644 CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/ButtonRenderer.razor create mode 100644 CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Properties/launchSettings.json diff --git a/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/CustomViewItemXPO.Blazor.Server.csproj b/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/CustomViewItemXPO.Blazor.Server.csproj index f37b948..08928db 100644 --- a/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/CustomViewItemXPO.Blazor.Server.csproj +++ b/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/CustomViewItemXPO.Blazor.Server.csproj @@ -17,9 +17,6 @@ Always - - - diff --git a/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/Button.razor b/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/Button.razor new file mode 100644 index 0000000..67fdf72 --- /dev/null +++ b/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/Button.razor @@ -0,0 +1,10 @@ +@namespace CustomViewItemXPO.Blazor.Server.Editors.ButtonViewItem + + + +@code { + [Parameter] + public string Text { get; set; } + [Parameter] + public EventCallback Click { get; set; } +} diff --git a/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/ButtonDetailViewItemBlazor.cs b/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/ButtonDetailViewItemBlazor.cs index 6cdf9ed..8c604c1 100644 --- a/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/ButtonDetailViewItemBlazor.cs +++ b/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/ButtonDetailViewItemBlazor.cs @@ -1,45 +1,42 @@ -using System; -using DevExpress.ExpressApp; +using DevExpress.ExpressApp; using DevExpress.ExpressApp.Blazor; +using DevExpress.ExpressApp.Blazor.Components; +using DevExpress.ExpressApp.Blazor.Components.Models; using DevExpress.ExpressApp.Editors; using DevExpress.ExpressApp.Model; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; -using DevExpress.ExpressApp.Blazor.Components; +namespace CustomViewItemXPO.Blazor.Server.Editors.ButtonViewItem; + +public interface IModelButtonDetailViewItemBlazor : IModelViewItem; -namespace MySolution.Module.Blazor { - public interface IModelButtonDetailViewItemBlazor : IModelViewItem { } +[ViewItem(typeof(IModelButtonDetailViewItemBlazor))] +public class ButtonDetailViewItemBlazor(IModelViewItem model, Type objectType) : + ViewItem(objectType, model.Id), + IComponentContentHolder, + IComplexViewItem +{ + public ButtonModel ComponentModel => componentModel; + + private ButtonModel componentModel; + private XafApplication application; - [ViewItem(typeof(IModelButtonDetailViewItemBlazor))] - public class ButtonDetailViewItemBlazor : ViewItem, IComplexViewItem { - public class ButtonHolder : IComponentContentHolder { - public ButtonHolder(ButtonModel componentModel) { - ComponentModel = componentModel; - } - public ButtonModel ComponentModel { get; } - RenderFragment IComponentContentHolder.ComponentContent => ComponentModelObserver.Create(ComponentModel, ButtonRenderer.Create(ComponentModel)); - } - private XafApplication application; - public ButtonDetailViewItemBlazor(IModelViewItem model, Type objectType) : base(objectType, model.Id) { } - void IComplexViewItem.Setup(IObjectSpace objectSpace, XafApplication application) { - this.application = application; - } - protected override object CreateControlCore() => new ButtonHolder(new ButtonModel()); - protected override void OnControlCreated() { - if (Control is ButtonHolder holder) { - holder.ComponentModel.Text = "Click me!"; - holder.ComponentModel.Click += ComponentModel_Click; - } - base.OnControlCreated(); - } - public override void BreakLinksToControl(bool unwireEventsOnly) { - if (Control is ButtonHolder holder) { - holder.ComponentModel.Click -= ComponentModel_Click; - } - base.BreakLinksToControl(unwireEventsOnly); - } - private void ComponentModel_Click(object sender, EventArgs e) { - application.ShowViewStrategy.ShowMessage("Action is executed!"); - } + RenderFragment IComponentContentHolder.ComponentContent => + ComponentModelObserver.Create(componentModel, componentModel.GetComponentContent()); + void IComplexViewItem.Setup(IObjectSpace objectSpace, XafApplication application) { + this.application = application; + } + + protected override object CreateControlCore() { + componentModel = new ButtonModel + { + Text = "Click me!", + Click = EventCallback.Factory.Create(this, ComponentModel_Click), + }; + return componentModel; + } + private void ComponentModel_Click() { + application.ShowViewStrategy.ShowMessage("Action is executed!"); } -} +} \ No newline at end of file diff --git a/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/ButtonModel.cs b/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/ButtonModel.cs index dc5e1b6..7804eba 100644 --- a/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/ButtonModel.cs +++ b/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/ButtonModel.cs @@ -1,15 +1,18 @@ -using System; -using DevExpress.ExpressApp.Blazor.Components.Models; +using DevExpress.ExpressApp.Blazor.Components.Models; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; -namespace MySolution.Module.Blazor { - public class ButtonModel : ComponentModelBase { - public string Text { - get => GetPropertyValue(); - set => SetPropertyValue(value); - } - public void ClickFromUI() { - Click?.Invoke(this, EventArgs.Empty); - } - public event EventHandler Click; +namespace CustomViewItemXPO.Blazor.Server.Editors.ButtonViewItem; + +public class ButtonModel : ComponentModelBase { + public string Text { + get => GetPropertyValue(); + set => SetPropertyValue(value); + } + public EventCallback Click { + get => GetPropertyValue>(); + set => SetPropertyValue(value); } -} + + public override Type ComponentType => typeof(Button); +} \ No newline at end of file diff --git a/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/ButtonRenderer.razor b/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/ButtonRenderer.razor deleted file mode 100644 index 18d7ae7..0000000 --- a/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Editors/ButtonViewItem/ButtonRenderer.razor +++ /dev/null @@ -1,11 +0,0 @@ -@using DevExpress.Blazor -@namespace MySolution.Module.Blazor - - ComponentModel.ClickFromUI()) /> - -@code { - [Parameter] - public ButtonModel ComponentModel { get; set; } - public static RenderFragment Create(ButtonModel componentModel) => - @; -} diff --git a/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Properties/launchSettings.json b/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Properties/launchSettings.json new file mode 100644 index 0000000..5ab04de --- /dev/null +++ b/CS/XPO/CustomViewItemXPO/CustomViewItemXPO.Blazor.Server/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "CustomViewItemXPO.Blazor.Server": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +}