# ModelBuilders - Buddy class approach

The second way to leverage ModelBuilders in code is through the use of 'buddy' classes. This should be considered the prefered approach for model builders as it realises the power of generics ensuring more robust code and substantially easier refactoring. It requires more effort because of the need to write a second class but in doing one can take full advantage of the power bestowed by C#.

As in the documentation on the inline approach the same basic business object will be used as an example.

# Class Level











 
 















using System;
using DevExpress.ExpressApp.ConditionalAppearance;
using DevExpress.ExpressApp.Model;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl;
using DevExpress.Persistent.Validation;
using DevExpress.Xpo;

namespace MainDemo.Module.BusinessObjects
{
    [DefaultClassOptions]
    [ModelDefault("Caption", "Task")]
    public class DemoTask : BaseObject 
    {
        public DemoTask(Session session) : base(session) { }

        [ToolTip("View, assign or remove contacts for the current task")]
        [Association("Contact-DemoTask")]
        public XPCollection<Contact> Contacts
        {
            get
            {
                return GetCollection<Contact>(nameof(Contacts));
            }
        }
    }
}
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

Replacing the two class level attributes requires the creation of a new derived class from the ModelBuilder<T> type and then overriding it's Build() method to apply the attributes. As before use the built in methods WithDefaultClassOptions and HasCaption to apply those attributes to the business class.







 







 
 



using System;
using DevExpress.ExpressApp.DC;
using Xenial.Framework.ModelBuilder;

namespace MainDemo.Module.BusinessObjects
{
    public class DemoTaskModelBuilder : ModelBuilder<DemoTask>
    {
        public DemoTaskModelBuilder(ITypeInfo typeInfo) : base(typeInfo) { }

        public override void Build()
        {
            base.Build();

            this.WithDefaultClassOptions()
                .HasCaption("Task");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

With that done, as before with the inline approach, it is necessary to register the ModelBuilder in the CustomizeTypesInfo method and then to call the Build() method. This can be done with the built in CreateModelBuilder<T>() extension method on the ITypesInfo class.





















 
 
 








using System;
using System.Collections.Generic;
using System.Linq;

using DevExpress.ExpressApp;
using DevExpress.ExpressApp.DC;

using Xenial.Framework;
using Xenial.Framework.ModelBuilders;

using MainDemo.Module.BusinessObjects;

namespace MyApplication.Module
{
    public class MyApplicationModule : ModuleBase
    {
        public override void CustomizeTypesInfo(ITypesInfo typesInfo)
        {
            base.CustomizeTypesInfo(typesInfo);

            typesInfo
                .CreateModelBuilder<DemoTaskModelBuilder>()
                .Build();

            //Alternative:
            //var builder = new DemoTaskModelBuilder(typesInfo.FindTypeInfo<DemoTask>());
            //builder.Build();
        }
    }
}
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

Now the attributes can be removed from the business object's code.











 
 















using System;
using DevExpress.ExpressApp.ConditionalAppearance;
using DevExpress.ExpressApp.Model;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl;
using DevExpress.Persistent.Validation;
using DevExpress.Xpo;

namespace MainDemo.Module.BusinessObjects
{
    // [DefaultClassOptions]
    // [ModelDefault("Caption", "Task")]
    public class DemoTask : BaseObject
    {
        public DemoTask(Session session) : base(session) { }

        [ToolTip("View, assign or remove contacts for the current task")]
        [Association("Contact-DemoTask")]
        public XPCollection<Contact> Contacts
        {
            get
            {
                return GetCollection<Contact>(nameof(Contacts));
            }
        }
    }
}
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

TIP

By following the recommended naming covention of ModelClass.ModelBuilder.cs for these model builders and applying the suffix ModelBuilder to the original class name
one can make full use of file naming in VisualStudio.

# Property Level

Property level attributes follow exactly the same convention.

Register the ModelBuilder in the module's CustomizeTypesInfo method and call the Build() method.















 
 



using System;
using DevExpress.ExpressApp.DC;
using Xenial.Framework.ModelBuilder;

namespace MainDemo.Module.BusinessObjects
{
    public class DemoTaskModelBuilder : ModelBuilder<DemoTask>
    {
        public DemoTaskModelBuilder(ITypeInfo typeInfo) : base(typeInfo) { }

        public override void Build()
        {
            base.Build();

            For(m => m.Contacts)
                .HasTooltip("View, assign or remove contacts for the current task");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Use the built in method HasTooltip to apply the attribute to the business class's property.





















 
 
 








using System;
using System.Collections.Generic;
using System.Linq;

using DevExpress.ExpressApp;
using DevExpress.ExpressApp.DC;

using Xenial.Framework;
using Xenial.Framework.ModelBuilders;

using MainDemo.Module.BusinessObjects;

namespace MyApplication.Module
{
    public class MyApplicationModule : ModuleBase
    {
        public override void CustomizeTypesInfo(ITypesInfo typesInfo)
        {
            base.CustomizeTypesInfo(typesInfo);

            typesInfo
                .CreateModelBuilder<DemoTaskModelBuilder>()
                .Build();

            //Alternative:
            //var builder = new DemoTaskModelBuilder(typesInfo.FindTypeInfo<DemoTask>());
            //builder.Build();
        }
    }
}
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

Remove the attribute from the original code.

















 










using System;
using DevExpress.ExpressApp.ConditionalAppearance;
using DevExpress.ExpressApp.Model;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl;
using DevExpress.Persistent.Validation;
using DevExpress.Xpo;

namespace MainDemo.Module.BusinessObjects
{
    [DefaultClassOptions]
    [ModelDefault("Caption", "Task")]
    public class DemoTask : BaseObject
    {
        public DemoTask(Session session) : base(session) { }

        // [ToolTip("View, assign or remove contacts for the current task")]
        [Association("Contact-DemoTask")]
        public XPCollection<Contact> Contacts
        {
            get
            {
                return GetCollection<Contact>(nameof(Contacts));
            }
        }
    }
}
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

# Summary

As with the inline approach this allows the application of attributes to code that may reside in a different assembly and to which access may not be possible whislt leveraging all of the benefits that C# has to offer.

# In Conclusion

On completion there should be three distinct classes as illustrate below.

using System;
using DevExpress.ExpressApp.ConditionalAppearance;
using DevExpress.ExpressApp.Model;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl;
using DevExpress.Persistent.Validation;
using DevExpress.Xpo;

namespace MainDemo.Module.BusinessObjects
{
    public class DemoTask : BaseObject
    {
        public DemoTask(Session session) : base(session) { }

        [Association("Contact-DemoTask")]
        public XPCollection<Contact> Contacts
        {
            get
            {
                return GetCollection<Contact>(nameof(Contacts));
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using DevExpress.ExpressApp.DC;
using Xenial.Framework.ModelBuilder;

namespace MainDemo.Module.BusinessObjects
{
    public class DemoTaskModelBuilder : ModelBuilder<DemoTask>
    {
        public DemoTaskModelBuilder(ITypeInfo typeInfo) : base(typeInfo) { }

        public override void Build()
        {
            base.Build();

            this.WithDefaultClassOptions()
                .HasCaption("Task");

            For(m => m.Contacts)
                .HasTooltip("View, assign or remove contacts for the current task");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using System.Collections.Generic;
using System.Linq;

using DevExpress.ExpressApp;
using DevExpress.ExpressApp.DC;

using Xenial.Framework;
using Xenial.Framework.ModelBuilders;

using MainDemo.Module.BusinessObjects;

namespace MyApplication.Module
{
    public class MyApplicationModule : ModuleBase
    {
        public override void CustomizeTypesInfo(ITypesInfo typesInfo)
        {
            base.CustomizeTypesInfo(typesInfo);

            typesInfo
                .CreateModelBuilder<DemoTaskModelBuilder>()
                .Build();

            //Alternative:
            //var builder = new DemoTaskModelBuilder(typesInfo.FindTypeInfo<DemoTask>());
            //builder.Build();
        }
    }
}
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