# DetailViewLayoutBuilders - Record Syntax

C#9 introduced a new record syntax (opens new window) which has been implemented within Xenial.Framework LayoutBuilders ensuring that layouts can be built using with expressions. Although not very different from initalizers they make it possible to create a copy of a given record, which is particularly beneficial to a clean fluent syntax in combination with a functional style API.

# Setting the compiler options

Using this feature requires the compiler version to be set in the project. By default the framework will choose the compiler level based on the .NET version being used but it can be overriden by setting the LangVersion (opens new window) property in the*.csproj files.

By far the best way to use this feature is to create a Directory.Build.props file in the same location as the application *.sln file:

<Project>
  <PropertyGroup>
    <LangVersion>9.0</LangVersion>
    <!--<LangVersion>latest</LangVersion> Alternative: just use the latest version, if you want the latest and greatest -->
  </PropertyGroup>
</Project>
1
2
3
4
5
6

For more information on this topic please look at the Microsoft Documentation (opens new window)

TIP

After adding the Directory.Build.props it may be necessary to add the file to the solution file.

To ensure that the compiler is picked up correctly close VisualStudio, delete all bin and obj folders and then restart VisualStudio.

CAUTION

Whilst it is possible to use this in projects targeting .net frameworks below net5 (by adding a class called IsExternalInit in the project) it is not officially supported by Microsoft:

#if !NET5

using System.ComponentModel;

namespace System.Runtime.CompilerServices
{
    /// <summary>
    /// Reserved to be used by the compiler for tracking metadata.
    /// This class should not be used by developers in source code.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    internal static class IsExternalInit
    {
    }
}
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

WARNING

All code should be thoroughly tested after changing the compiler version.

Microsoft has done a great job trying not to break any existing client project, but because it is not supported on the old full framework (.NET4xx) officially, use this technique at your own risk.

# Registration

Registration is exactly the same as in the previous examples, override the AddGeneratorUpdaters in the platform agnostic module and call the updaters.UseDetailViewLayoutBuilders() extension method.








 



 




using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Model.Core;

namespace MyApplication.Module
{
    public sealed partial class MyApplicationModule : ModuleBase
    {
        public override void AddGeneratorUpdaters(ModelNodesGeneratorUpdaters updaters)
        {
            base.AddGeneratorUpdaters(updaters);

            updaters.UseDetailViewLayoutBuilders();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Build the Layout

As this uses C#9 it is now possible to use the Target-typed new expressions feature (opens new window) which removes a little bit of redundancy in the code as shown below:

using DevExpress.Persistent.Base;
using DevExpress.Xpo;

using Xenial.Framework.Layouts;
using Xenial.Framework.Layouts.Items;
using Xenial.Framework.Layouts.Items.Base;
using Xenial.Framework.Layouts.Items.LeafNodes;

namespace MainDemo.Module.BusinessObjects
{
    [Persistent]
    [DefaultClassOptions]
    [DetailViewLayoutBuilder]
    public class Person : XPObject
    {
        private static readonly LayoutBuilder<Person> b = new();
        public static Layout BuildLayout() => new()
        {
            b.HorizontalGroup() with
            {
                Caption = "Person",
                ShowCaption = true,
                RelativeSize = 25,
                Children = new()
                {
                    b.PropertyEditor(m => m.Image) with
                    {
                        ShowCaption = false,
                        RelativeSize = 10,
                    },
                    b.VerticalGroup() with
                    {
                        Children = new()
                        {
                            b.PropertyEditor(m => m.FullName),
                            b.HorizontalGroup() with
                            {
                                Children = new()
                                {
                                    b.PropertyEditor(m => m.FirstName),
                                    b.PropertyEditor(m => m.LastName)
                                }
                            },
                            b.HorizontalGroup() with
                            {
                                Children = new()
                                {
                                    b.PropertyEditor(m => m.Email),
                                    b.PropertyEditor(m => m.Phone)
                                }
                            },
                            b.EmptySpaceItem()
                        }
                    }
                }
            },
            b.TabbedGroup() with
            {
                Children = new()
                {
                    b.Tab("Primary Address", FlowDirection.Horizontal) with
                    {
                        Children = new()
                        {
                            b.VerticalGroup() with
                            {
                                Children = new()
                                {
                                    b.PropertyEditor(m => m.Address1.Street, e => e.CaptionLocation = Locations.Top),
                                    b.HorizontalGroup() with
                                    {
                                        Children = new()
                                        {
                                            b.PropertyEditor(m => m.Address1.City, e => e.CaptionLocation = Locations.Top),
                                            b.PropertyEditor(m => m.Address1.ZipPostal, e => e.CaptionLocation = Locations.Top)
                                        }
                                    },
                                    b.PropertyEditor(m => m.Address1.StateProvince, e => e.CaptionLocation = Locations.Top),
                                    b.PropertyEditor(m => m.Address1.Country, e => e.CaptionLocation = Locations.Top),
                                    b.EmptySpaceItem()
                                }
                            },
                            b.EmptySpaceItem()
                        }
                    },
                    b.Tab("Secondary Address", FlowDirection.Horizontal) with
                    {
                        Children = new()
                        {
                            b.VerticalGroup() with
                            {
                                Children = new()
                                {
                                    b.PropertyEditor(m => m.Address2.Street, e => e.CaptionLocation = Locations.Top),
                                    b.HorizontalGroup() with
                                    {
                                        Children = new()
                                        {
                                            b.PropertyEditor(m => m.Address2.City, e => e.CaptionLocation = Locations.Top),
                                            b.PropertyEditor(m => m.Address2.ZipPostal, e => e.CaptionLocation = Locations.Top)
                                        }
                                    },
                                    b.PropertyEditor(m => m.Address2.StateProvince, e => e.CaptionLocation = Locations.Top),
                                    b.PropertyEditor(m => m.Address2.Country, e => e.CaptionLocation = Locations.Top),
                                    b.EmptySpaceItem()
                                }
                            },
                            b.EmptySpaceItem()
                        }
                    },
                    b.Tab("Additional Addresses") with
                    {
                        Children = new()
                        {
                            b.PropertyEditor(m => m.Addresses)
                        }
                    }
                }
            }
        };
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121

Although the syntax is a lot longer than the fluent builder syntax, it is a little more structured. It's combining both the power of expression trees to specify type safe layouts, as well as a familiar syntax comparable to initializers. However it is more verbose (language limitations require the need to specify the Children directly, which is not needed with normal initializer syntax) so it's use is only recommended when there is a need to specify properties and children at the same time, or when using leaf node types (for example property editors).

# A mixed sample

using DevExpress.Persistent.Base;
using DevExpress.Xpo;

using Xenial.Framework.Layouts;
using Xenial.Framework.Layouts.Items;
using Xenial.Framework.Layouts.Items.Base;
using Xenial.Framework.Layouts.Items.LeafNodes;

namespace MainDemo.Module.BusinessObjects
{
    [Persistent]
    [DefaultClassOptions]
    [DetailViewLayoutBuilder]
    public class Person : XPObject
    {
        private static readonly LayoutBuilder<Person> b = new();
        public static Layout BuildLayout() => new()
        {
            b.HorizontalGroup() with
            {
                Caption = "Person",
                ShowCaption = true,
                RelativeSize = 25,
                Children = new()
                {
                    b.PropertyEditor(m => m.Image) with
                    {
                        ShowCaption = false,
                        RelativeSize = 10,
                    },
                    b.VerticalGroup(
                        b.PropertyEditor(m => m.FullName),
                        b.HorizontalGroup(
                            b.PropertyEditor(m => m.FirstName),
                            b.PropertyEditor(m => m.LastName)
                        ),
                        b.HorizontalGroup(
                            b.PropertyEditor(m => m.Email),
                            b.PropertyEditor(m => m.Phone)
                        ),
                        b.EmptySpaceItem()
                    )
                }
            },
            new LayoutTabbedGroupItem
            {
                b.Tab("Primary Address", FlowDirection.Horizontal,
                    b.VerticalGroup(
                        b.PropertyEditor(m => m.Address1.Street) with { CaptionLocation = Locations.Top },
                        b.HorizontalGroup(
                            b.PropertyEditor(m => m.Address1.City) with { CaptionLocation = Locations.Top },
                            b.PropertyEditor(m => m.Address1.ZipPostal) with { CaptionLocation = Locations.Top }
                        ),
                        b.PropertyEditor(m => m.Address1.StateProvince) with { CaptionLocation = Locations.Top },
                        b.PropertyEditor(m => m.Address1.Country) with { CaptionLocation = Locations.Top },
                        b.EmptySpaceItem()
                    ),
                    b.EmptySpaceItem()
                ),
                b.Tab("Secondary Address", FlowDirection.Horizontal,
                    b.VerticalGroup() with
                    {
                        Children = new()
                        {
                            b.PropertyEditor(m => m.Address2.Street, e => e.CaptionLocation = Locations.Top),
                            b.HorizontalGroup() with
                            {
                                Children = new()
                                {
                                    b.PropertyEditor(m => m.Address2.City, e => e.CaptionLocation = Locations.Top),
                                    b.PropertyEditor(m => m.Address2.ZipPostal, e => e.CaptionLocation = Locations.Top)
                                }
                            },
                            b.PropertyEditor(m => m.Address2.StateProvince, e => e.CaptionLocation = Locations.Top),
                            b.PropertyEditor(m => m.Address2.Country, e => e.CaptionLocation = Locations.Top),
                            b.EmptySpaceItem()
                        }
                    },
                    b.EmptySpaceItem()
                ),
                b.Tab(
                    b.PropertyEditor(m => m.Addresses)
                ) with { Caption = "Additional Addresses" }
            }
        };
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

WARNING

Whilst this sample works without issue mixing syntax styles is not recommended. It may work from a technical standpoint but it adds complexity and harms readability.

Wherever possible coding styles and conventions should be clearly defined and adhered to.