Skip to main content
A newer version of this page is available. .
All docs
V22.1

Custom Functions in the Expression Editor (ASP.NET Web Forms)

  • 4 minutes to read

This topic describes how to add a custom function to the Expression Editor or remove a function from the list of available functions.

Add a Custom Function

Implement a Custom Function

To create a custom function, create an object that descends from the ReportCustomFunctionOperatorBase abstract class.

The following code defines a custom function CustomFormatFunction(string format, object arg0):

#region #usings
using DevExpress.XtraReports.Expressions;
using System;
#endregion

namespace CustomFunctionInExpressionAspNetCore.Services
{
    #region #CustomFormatFunction
    public class CustomFormatFunction : ReportCustomFunctionOperatorBase
    {
        public override string FunctionCategory
            => "Custom";
        public override string Description
            => "CustomFormatFunction(string format, object arg0)" +
            "\r\nConverts an arg0 value to a string based on a specified format";
        public override bool IsValidOperandCount(int count)
            => count == 2;
        public override bool IsValidOperandType(int operandIndex, int operandCount, Type type)
            => true;
        public override int MaxOperandCount
            => 2;
        public override int MinOperandCount
            => 2;
        public override object Evaluate(params object[] operands)
        {
            string res = String.Format(operands[0].ToString(), operands[1]);
            return res;
        }
        public override string Name
            => "CustomFormatFunction";
        public override Type ResultType(params Type[] operands)
        {
            return typeof(string);
        }
    }
    #endregion
}

The following code defines a custom function [].CountDistinct(object arg0):

using DevExpress.Data.Filtering;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace CustomFunctionInExpressionAspNetCore.Services {
    class CountDistinctCustomAggregate : ICustomAggregateFormattable, ICustomAggregateBrowsable {
        static readonly CountDistinctCustomAggregate instance = new CountDistinctCustomAggregate();
        public static void Register() {
            CriteriaOperator.RegisterCustomAggregate(instance);
        }
        public static void Unregister() {
            CriteriaOperator.UnregisterCustomAggregate(instance);
        }
        public string Name { get { return nameof(CountDistinct); } }

        public int MinOperandCount => -1;

        public int MaxOperandCount => -1;

        public string Description => "This is a custom aggregate function";

        Type ICustomAggregate.ResultType(params Type[] operands) {
            return typeof(int);
        }
        object ICustomAggregate.CreateEvaluationContext() {
            return new HashSet<object>();
        }
        bool ICustomAggregate.Process(object context, object[] operands) {
            var ctx = (HashSet<object>)context;
            ctx.Add(operands[0]);
            return false;
        }
        object ICustomAggregate.GetResult(object context) {
            var ctx = (HashSet<object>)context;
            return ctx.Count;
        }
        string ICustomAggregateFormattable.Format(Type providerType, params string[] operands) {
            return string.Format("COUNT(distinct {0})", operands[0]);
        }
        public static object CountDistinct<T>(IEnumerable<T> collection, Expression<Func<T, object>> arg) {
            throw new InvalidOperationException("This method should not be called explicitly.");
        }

        public bool IsValidOperandCount(int count) {
            return true;
        }

        public bool IsValidOperandType(int operandIndex, int operandCount, Type type) {
            return true;
        }
    }
}

Register a Function on the Server

Call the CustomFunctions.Register method at application startup:

public class Startup {
    //...
    public void ConfigureServices(IServiceCollection services) {
        // ...
        services.ConfigureReportingServices(configurator => {
            configurator.ConfigureReportDesigner(designerConfigurator => {
                //...
                DevExpress.XtraReports.Expressions.CustomFunctions.Register(new CustomFormatFunction());
            });
            //...
        });
        //...
    }
    //...
}

Register a Function on the Client

Handle the BeforeRender event and use the following code to add the CustomFormatFunction to the list of available functions in the Expression Editor:

function OnBeforeRender(event) {
    DevExpress.Analytics.Localization.loadMessages({
        "CustomStringId.CustomFormatFunction":
            "CustomFormatFunction(string format, object arg0)\r\n" +
            "Converts an arg0 value to a string based on the specified format.",
        "CustomStringId.CountDistinctCustomAggregate":
            "[].CountDistinct(object arg0)\r\n" +
            "Counts the number of distinct values in the specified collection."

    });
    DevExpress.Reporting.Designer.Widgets.reportFunctionDisplay.push({
        display: "Custom",
        items: {
            CustomFormatFunction: [{
                paramCount: 2,
                text: "CustomFormatFunction('{0:C}', 0)",
                displayName: "CustomFormatFunction(format, arg0)",
                descriptionStringId: "CustomStringId.CustomFormatFunction"
            }]
        }
    });

    var aggregate = DevExpress.Reporting.Designer.Widgets.reportFunctionDisplay.filter(x => x.category === 'Aggregate')[0];
    aggregate.items['CountDistinct'] = [{ 
        paramCount: 1, 
        text: '[].CountDistinct()', 
        displayName: 'CountDistinct()', 
        descriptionStringId: `CustomStringId.CountDistinctCustomAggregate`}];

    delete DevExpress.Reporting.Designer.Widgets.reportFunctionDisplay[1].items["LocalDateTimeThisYear"];
}

The code uses the ReportCustomFunctionOperatorBase.Name property value as the name of the function to register.

Result

The following image shows a custom function (CustomFormatFunction) in the list of available functions in the Expression Editor:

Remove a Function from the List of Available Functions

Handle the BeforeRender event and remove a function from the DevExpress.Reporting.Designer.Widgets.reportFunctionDisplay collection. To get access to a function, use the following notation:

DevExpress.Reporting.Designer.Widgets.reportFunctionDisplay[index].items[“function_name”]
where index is a function category index.

The following code removes the LocalDateTimeThisYear function from the DateTime category:

function OnBeforeRender(event) {
<!-- ... -->
    delete DevExpress.Reporting.Designer.Widgets.reportFunctionDisplay[1].items["LocalDateTimeThisYear"];
}

Unregister a Function

If you remove a function from the DevExpress.Reporting.Designer.Widgets.reportFunctionDisplay collection on the client, the function is still registered and expressions with that function remain valid and can be evaluated. You can type the function name and compose a new expression manually.

If you have registered a custom function, you can call the CustomFunctions.Unregister method at application startup to unregister it:

void Application_Start(object sender, EventArgs e) {
    // ...
    DevExpress.XtraReports.Expressions.CustomFunctions.Unregister("CustomFormatFunction");
}

After this code is executed, the CustomFormatFunction function is not available in the Expression Editor and won’t be evaluated in expressions.