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

Custom Functions in the Expression Editor (ASP.NET MVC)

  • 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.

The custom function described in this topic is available in the Expression Editor invoked from the Report Designer. If you want your custom function to be available for SQL queries in the Data Source Wizard or elsewhere in the application outside of Report components, implement the ICustomFunctionOperatorFormattable interface and register the function with the CriteriaOperator.RegisterCustomFunction method. Review the following help topic for more information: Custom 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:

void Application_Start(object sender, EventArgs e) {
    // ...
    DevExpress.Data.Filtering.CriteriaOperator.RegisterCustomFunction(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.