# ModelBuilders - Advanced

The documentation on ModelBuilders has, thus far, concentrated on the different ways in which they can be utilised in code. What has not be touched upon as yet is their support for Domain Specific Language (DSL) within individual projects. Whilst neither complicated or difficult to implement their use with DSL does require a basic understanding of c# generics and concepts such as type inference and generic constraints.

# Custom attributes

ModelBuilders were designed to be extensible a consequence of which is that they only deal with the following primitives:

  • IModelBuilder<TClassType> : IModelBuilder
  • IPropertyBuilder<TPropertyType, TClassType> : IPropertyBuilder
  • System.Attribute

The benefit that this brings is that solutions and individual projects are not tied to the specific attributes supported by XAF. ModelBuilders have the ability to support custom attributes. As an example of this the code below illustrates a custom attribute ExportFormatAttibute and then illustrates how it can be referenced in a ModelBuilder.































 

 
 







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

namespace MainDemo.Module.BusinessObjects
{
    [AttributeUsage(
        AttributeTargets.Class | AttributeTargets.Property, 
        AllowMultiple = false, 
        Inherited = false
    )]
    public sealed class ExportFormatAttribute : Attribute
    {
        public string Format { get; }

        public ExportFormatAttribute(string format)
        {
            Format = format;
        }

        public ExportFormatAttribute()
        {
            Format = "My Default Format";
        }
    }

    public class DemoTaskModelBuilder : ModelBuilder<DemoTask>
    {
        public DemoTaskModelBuilder(ITypeInfo typeInfo) : base(typeInfo) { }

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

            this.WithAttribute(new ExportFormatAttribute("My Format"));

            For(m => m.Contacts)
                .WithAttribute<ExportFormatAttribute>();
        }
    }
}
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

By making use of the WithAttribute() method it has been possible to apply the custom attribute to the business object.

TIP

There is another overload that accepts an action to configure the attribute.

.WithAttribute<ExportFormatAttribute>(a => a.Format = "My Format");
1

WARNING

Given that the majority of attributes are written to be immutable this convenience method may not as expected.

# Domain-extensions

Most developers will at some point encounter the difficulties that can occur when using domain types such as currency or dates. Whilst consistency is the ultimate goal it can be hard to achieve when for example a decimal type can describe money, percentages, pressure or any other unit and the United States persist with a date format that is unrecognisable to the vast majority. Within XAF itself it is not easy to solve these issues however the Xenial.Framework allows the creation of extension methods that can provide consistency in these situations and help to keep code DRY (opens new window).

The code below illustrates the application of a domain extension for the custom attribute ExportFormatAttribute by adding an extension method for the IModelBuilder<TClassType> and IPropertyBuilder<TPropertyType, TClassType> interfaces.

using MailClient.Module.BusinessObjects;

namespace Xenial.Framework.ModelBuilders
{
    public static partial class ModelBuilderDomainExtension
    {
        public static IModelBuilder<TClassType> HasExportFormat<TClassType>(
            this IModelBuilder<TClassType> modelBuilder, 
            string exportFormat = null
        )
        {
            if (exportFormat == null)
            {
                return modelBuilder.WithAttribute<ExportFormatAttribute>();
            }

            return modelBuilder.WithAttribute(new ExportFormatAttribute(exportFormat));
        }

        public static IPropertyBuilder<TPropertyType, TClassType> HasExportFormat<TPropertyType, TClassType>(
            this IPropertyBuilder<TPropertyType, TClassType> propertyBuilder, 
            string exportFormat = null
        )
        {
            if (exportFormat == null)
            {
                return propertyBuilder.WithAttribute<ExportFormatAttribute>();
            }

            return propertyBuilder.WithAttribute(new ExportFormatAttribute(exportFormat));
        }
    }
}
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

TIP

To make full use of IntelliSense and have it see these domain extensions automatically use the Xenial.Framework.ModelBuilders namespace. If using multiple domain extensions classes can be marked as partial and reused across different files and projects.

WARNING

Avoid using the IModelBuilder and IPropertyBuilder interfaces directly as that may prevent these extension methods from remaining type safe. See the type safe section for more information.

# Combining attributes

Xenial.Framework is not restricted to the application of a single custom attributes it is possible to apply multiple attributes at once as well as is illustrated in the code below.

namespace Xenial.Framework.ModelBuilders
{
    public static partial class ModelBuilderDomainExtension
    {
        public static IPropertyBuilder<TPropertyType, TClassType> AsNumeric<TPropertyType, TClassType>(
            this IPropertyBuilder<TPropertyType, TClassType> propertyBuilder
        )
        {
            return propertyBuilder
                .HasExportFormat("n0")
                .HasDisplayFormat("{0:n0}")
                .HasEditMask("n0");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

The code above illustrates succinctly that the addition of multiple custom attributes isn't difficult but it also masks a distict issue. This domain extension only makes sense for properties of type int. The next section examines how it is possible to circumvent this issue.