TODO

Panel Extension

Introduction

The Panel Extensions solution includes class libraries that extend the functionality of the Panel platform. SoftwareIDM publishes the code in the Panel Extensions solution to serve as live example code for custom extensions. The Panel Extension solution is configured to use Visual Studio 2013. The libraries in Panel Extensions produce the dlls the lib\Extensions folder, and implement logic for:

  • Rule Functions
  • Rule Special Values
  • Workflow Steps
  • Schedule Steps
  • Health Check Probes
  • Azure AD Scan Commands

The various projects in Panel Extensions provide functionality by implementing interfaces which are generally declared in the SoftwareIDM.PanelModel class. Additionally, most classes implemented in Extension projects must have some level of interaction with the Panel platform web UI. This is accomplished through Panel Platform .NET Attributes.

The Panel extensions solution is located at: https://bitbucket.org/softwareidm/panelextensions

Projects

The Panel Extensions solution is divided into projects, with each project corresponding to a Panel platform module.

SoftwareIDM.PanelExtensions

The base PanelExtensions project implements Provider agnostic logic, for schedule steps, health probes, and rule special values.

Schedule Steps

Health Probes

Special Values

  • Schedule Step Status — list of possible statuses for a Panel schedule step. Translates into Enum ints.
  • Object Modification Type — list of possible values for the ModificationType enumeration on Change records (e.g. Add, Update, Move, Connect, Delete...). Translates into Enum ints.
  • Search Silo — List of silo names, e.g. "FIM:Metaverse". Translates into Guid strings matching ObjectRecord.SearchIndex.
  • Attribute — List of attribute names from all silos.
  • Multi Attribute — List of multi-value attribute names from all silos.
  • Reference Attribute — List of reference attribute names from all silos.

SoftwareIDM.RuleFunctions

The RuleFunctions project implements a large body of common basic functions for use in the Rule Engine. It has classes for:

  • String Functions — Perform operations and transformations on string values
  • Numeric Functions — Perform operations and transformations on number values
  • Date Functions — Perform operations and transformations on date and time values
  • Bool Functions — Perform operations and transformations on boolean values
  • List Functions — Perform list and enumerable operations
  • Active Directory Functions — Basic AD functions, like determining group types, parsing DNs, and converting object sids
  • Aggregate Functions — Perform aggregation across multiple calls in a particular context

SoftwareIDM.ReportingExtension

The ReportingExtension project implements a schedule step and a workflow step for sending reports attached to emails.

SoftwareIDM.RuleTests

The RuleTests project is a VisualStudio Unit Test project that has a variety of Rule Engine samples. It is intended to be used as a test project and playground for working with the Rule Engine, and may be used to validate Workflow triggers and template values, Health Probe fail rules, and Schedule conditions.

In order to run any of the tests in RuleTests, you should verify that the RuleTests project is configured to build with a CPU target of x64. You will also need to change your Visual Studio test settings to have x64 as the default processor architecture.

x64 Test Settings

SoftwareIDM.WorkflowExtension

The WorkflowExtension project implements basic workflows and support structure for the panel platform workflow module.

Workflow Steps

Special Values

  • Workflow Counters — list of counters for process workflow history

Rule Functions

Link Functions implements the Link and RestLink rule functions that generate a URL with a cipher code that can be used to activate a workflow step. The actual link functions produce a special string that is detected and converted to a URL at run-time when the workflow is processed.

Lookup Functions implements helper rule functions that lookup objects in the Panel platform database by ObjectID, so that their properties can be looked up for rules. The Rule engine is only able to execute Lookup Functions server-side, so they may not be used for Schedule conditions.

SoftwareIDM.AzureExtension

The AzureExtension project implements extensions used by the AzureAD Provider.

SoftwareIDM.SyncExtension

The SyncExtension project implements extensions for the Sync Engine Provider.

  • Sync Engine Schedule Steps
  • Sync Engine and Portal Health Probes
  • Special Values for looking up Run History counters and string constants for CS objects
  • Rule functions for the Scheduler to lookup pending operations in the sync engine. These rule functions can only be used for schedule conditions because they must run on the client and access the WMI.

Creating a Custom Extension

To create a custom extension project, start by creating a new .NET class library. The library project must have the following settings.

  • Target .NET framework 4.5
  • Build for x64 only
  • Must have a strong name (a self-generated .snk file is fine)
  • Should build to, or be copied to the lib\Extensions folder
  • All non-GAC dlls referenced by the library (including panel platform libraries), should have copy-local set to false

After the project has been created, the file-system location of any non-GAC dlls used by the extension project should be added to the DllPath section of config settings. This will allow them to be discovered by the Assembly Resolve handler.

Once the project is created, you should add references (and set copy-local = False) to lib\Newtonsoft.Json, lib\MongoDB.Bson, lib\SoftwareIDM.PanelHelper and lib\SoftwareIDM.PanelModel.

If the library is intended to do work server side, such as looking up database objects for a Rule Function you will want to add a reference to SoftwareIDM.PanelModule.

You may add references to other panel platform projects depending on what the extension is being created to do. For example, SyncExtension project also adds references to SoftwareIDM.SyncModel and SoftwareIDM.SyncMonitor.

Functionality in a custom extension is exposed in the web settings interface using C# attributes. The ClassDoc and MemberDoc attributes are used for naming and documenting classes and properties, and UIAttribute is used for controlling IO elements in the settings interface.

Note that to expose a class in an extension library, you must declare the class public, implement the correct interface(s), and annotate the class and properties with attributes.

Attributes

ClassDoc and MemberDoc

The ClassDoc attribute is used to annotate classes to adjust how the appear in the panel platform UI. It has the following properties:

  • Name — Re-names the class so for the UI. e.g. "ObjectRecord" is renamed to "Object Record".
  • Description — Use to provide either a description or instructions. If it's a class that appears in settings (such as schedule step), the description will appear at the top of the Details expansion.
  • Link — Use to provide a URL for further documentation for the class. The URL appears in the Help link.
  • RuleDoc — Use to indicate that the class is a type that should be available in the Object Properties helper for the Rule Engine. Typically this property should be omitted for Extension project classes.

The MemberDoc attribute is used to annotate methods and properties within a class:

  • Name — Re-names the property or method.
  • Description — Provides a description or instructions. If the Member is a Rule Function, the description should indicate how to use the function. If it's a property that defines settings input, the description will appear as a tooltip on the UI input element.

It is generally permissible to use HTML markup in the values for ClassDoc and MemberDoc, since these values are considered trusted.

Example:

[ClassDoc("Perform operations and transformations on numeric values", "Numeric Functions")]
public class CommonNumFunctions: IRuleFunctions
{
    // Numeric Functions
    [MemberDoc("Returns Int64.MaxValue")]
    public static AttributeValue MaxLong()
    {
        return new AttributeValue(long.MaxValue);
    }

    [MemberDoc("Returns Int64.MinValue")]
    public static AttributeValue MinLong()
    {
        return new AttributeValue(long.MinValue);
    }
    ...
}

BsonElement

The BsonElement attribute is declared in the MongoDB.Bson.Attributes namespace. This attribute is used to re-map the serialization name of a property to reduce space used in the database. It is appropriate to use the BsonElement attribute on properties for custom workflow steps, because each property name will be saved in full for each workflow instance created.

Example:

public class SendReportDefBase
{
    [BsonElement("re"),
    UIAttribute(UIAttrKind.Text, true, Choices = "js:helpers.reportSelect", NoLabel = true),
    MemberDoc("The name of the report to send. Must exactly match one of the saved report configurations")]
    public string Report { get; set; }

    [BsonElement("ft"), MemberDoc("File Type to send report as", Name = "File Type"),
    UIAttribute(UIAttrKind.Text, true, Default = "excel", Detail = true, Choices = "excel:Excel|avp:Attribute Value Pair|delimited:Delimited|json:JSON")]
    public string FileType { get; set; }
    ...
}

UI Attribute

The UIAttribute attribute is used to describe how a property should be exposed in the settings interface. It has the most settings of all the Panel attributes.

  • Kind — Required, what type of input to use
    • UIAttrKind.Text produces a text input
    • UIAttrKind.Encrypted produces password input, and the value is automatically ciphered using the password API
    • UIAttrKind.Bool produces a checkbox
    • UIAttrKind.Label produces a read-only label element
    • UAttrKind.LongText produces a textarea multi-line input
  • Required — Indicates whether the value is mandatory in settings. Applies javascript validation.
  • Default — Initializes new objects to a default value in the settings UI.
  • Size — Use with Text, LongText, and Encrypted input kinds to control the width of the input. Default value is 20.
  • NoLabel — Use to hide the auto-generated name label. This is particularly useful in the header of a settings strip.
  • Detail — Use to indicate whether the element should appear in the strip header, or in the expandable body.
  • CSSClass — Use to attach an additional css class to an input. For example, adding CSSClass="monospace" to a LongText input will make the text entry use a monospace font.
  • RuleHelp — If RuleHelp is set to true, then the rule helper dialog interface will pop-up when the user double-clicks or ctrl+clicks in the input.
  • Choices — Using Choices with a Text input will convert it into a drop-down list. There are two formats supported:
    • String format: "Value 1|Value 2|Value 3" or "value1:Label 1|value2:Label 2|value3:Label 3"
    • Javascript function: "js:helpers.reportSelect"

Panel platform javascript drop-down lists include:

  • js:helpers.argumentSelect2 — list of values that appear in the Argument field of run history objects. Includes things like program arguments, or run profile names.
  • js:helpers.recordSelect2 — list of values that appear in the RecordOf field of run history objects. Includes things like MA Ids, or program names.
  • js:helpers.azureProviderSelect — list of providers defined in AzureAD settings. Value maps to Guid provider Id.
  • js:helpers.maSelect — list of Management Agent identifiers across all Sync Engine providers.
  • js:helpers.reportSelect — list of reports defined in Report settings.
  • js:helpers.ruleContextTypeSelect — list of classes that can be used to trigger workflows, or to list in the Rule Object Properties helper.
  • js:helpers.runProfileSelect — list of run profiles. If it appears in the same extension class as maSelect, only run profiles from the selected MA will be displayed.
  • js:helpers.scopeSelect — list of silo/search index definitions. Value maps to SearchIndex string.
  • js:helpers.serviceSelect — list of host-names identified in Schedule settings as running Panel Service.
  • js:helpers.syncProviderSelect — list of Sync Engine providers defined in MIM and AADSync settings.
  • js:helpers.timeUnitSelect — list of time units supported by the SoftwareIDM.PanelModel.DateDefinition class for calculating relative offset dates.

Example:

[ClassDoc("Performs an operation on a windows service as part of a workflow", "Service Task")]
public class ServiceTaskDef : IWorkflowStepDefinition
{
    [BsonElement("sr"), UIAttribute(UIAttrKind.Text, true, Default="localhost", Detail=true)]
    public string Server { get; set; }

    [BsonElement("sv"), UIAttribute(UIAttrKind.Text, true, NoLabel=true)]
    public string Service { get; set; }

    [BsonElement("op"), UIAttribute(UIAttrKind.Text, true, NoLabel=true, Choices="Stop|Start|Restart")]
    public string Operation { get; set; }

    [BsonElement("ne"), StepRefUIAttribute()]
    public StepRef Next { get; set; }

    [BsonElement("w"),
    UIAttribute(UIAttrKind.Bool, Detail=true),
    MemberDoc(Name="Wait for Status", Description="Indicates whether to wait (up to one minute) for service to stop or start. Causes other workflow operations to block")]
    public bool WaitStatus { get; set; }

    public Type RunType()
    {
        return typeof(ServiceTask);
    }

    public string SearchText()
    {
        return String.Join(", ", Server, Service, Operation);
    }
}

Interfaces

Schedule Steps

To create a custom schedule step must implement SoftwareIDM.PanelModel.IScheduleStepDefinition and SoftwareIDM.PanelModel.IRunScheduleStep in separate classes. The first class contains the implementation for the settings interface, and the second class contains the logic to run the step.

Implementing IScheduleStepDefinition

The schedule definition class has several mandatory read-only properties. All other properties will be for collecting settings from the user interface.

  • Name — The name of the schedule step that should appear in schedule history records. The name is not displayed in settings and may be dynamically generated based on setting values.
  • LockLevel — The lock level should be either Shared or Exclusive, and helps control what steps may run in parallel.
  • LockName — The lock name is combined with lock level and defines the scope of the lock. Two steps may not run in parallel if one of them is LockLevel.Exclusive, and they have the same LockName.
  • RunType() — This method returns the type of the corresponding IRunScheduleStep implementation.

Due to the range of read-only properties, it can be a good idea to set the JSON class serialization to OptIn only. Then the settings properties may be annotated with the JsonProperty attribute.

Example:

[JsonObject(MemberSerialization = MemberSerialization.OptIn),
ClassDoc(Name = "Truncate Ops Log", Description = "Truncate the operations log of MIM or AAD Sync.", Link = "[docs]/SyncEngine#!truncate-ops-log")]
public class TruncateOps : IScheduleStepDefinition
{
    // helper called by implementation class
    public MSSyncDefinition GetEnv()
    {
        var set = LoadSetting.Load<MSSyncSettings>();
        if (set != null && set.Data != null)
        {
            foreach (var def in set.Data)
            {
                if (def.Id == Environment)
                {
                    return def;
                }
            }
        }

        return null;
    }

    [JsonProperty, UIAttribute(UIAttrKind.Text, true, NoLabel = true, Choices = "js:helpers.syncProviderSelect", Size = 20), MemberDoc("Provider to run in context of")]
    public Guid Environment { get; set; }

    [JsonProperty, UIAttribute(UIAttrKind.Text, true, NoLabel = true, Size = 15, TypeValid = UIAttrType.Int32), MemberDoc("Number of days of run history to retain", Name = "Days to Retain")]
    public int DaysToRetain { get; set; }

    public string Name
    {
        get { return String.Format("Truncate Ops Log to {0} Days", DaysToRetain); }
    }

    public TruncateOps() { }

    public ScheduleLockLevel LockLevel
    {
        get { return ScheduleLockLevel.Exclusive; }
    }

    public string LockName(string scheduler)
    {
        return Environment.ToString();
    }

    public Type RunType()
    {
        return typeof(RunTruncateOps);
    }
}

Implementing IRunScheduleStep

The IRunScheduleStep interface defines an event for writing logging output, a Run method for executing the step, and inherits from IDisposable.

  • public event ProgressEvent ReportProgress = delegate {}; — The ReportProgress delegate makes it possible to log Verbose, Normal, Warning, and Error messages in both PanelTool and Panel Service.
  • The Run method executes the actual step. A new instance of class is created every time the schedule executes. The Run method should return a HistoryRecord describing the schedule outcome, even if it is just a stub. The Run method may usually assume it has thread-exclusive access to local variables EXCEPT that when the service is stopped, Dispose() will be called by a separate thread, and Run may be interrupted. For this reason, it may be desirable to place brief segments of code in a critical section.
  • The Dispose method is called after a schedule step terminates normally, and it is called when possible if the schedule must terminate abnormally, such as when the service is stopped.

Example:

public sealed class RunTruncateOps : IRunScheduleStep
{
    public event ProgressEvent ReportProgress = delegate { };

    System.Management.ManagementObjectSearcher searcher;
    public HistoryRecord Run(IScheduleStepDefinition data)
    {
        var def = (TruncateOps)data;
        var env = def.GetEnv();
        if (env == null)
        {
            ReportProgress.Invoke(this, new ProgressEventArgs(ProgressLogLevel.Error, "Configuration Error"));
            return null;
        }

        var searchPath = @"root\MicrosoftIdentityIntegrationServer";
        if (!String.IsNullOrEmpty(env.SyncServer))
        {
            searchPath = String.Format(@"\\{0}\root\MicrosoftIdentityIntegrationServer", env.SyncServer);
        }

        var date = DateTime.Now.ToUniversalTime().AddDays(def.DaysToRetain * -1.0);
        try
        {
            lock (searcher)
            {
                searcher = new System.Management.ManagementObjectSearcher(
                    searchPath,
                    "select * from MIIS_Server");
            }

            foreach (System.Management.ManagementObject sv in searcher.Get())
            {
                var result = sv.InvokeMethod("ClearRuns", new object[] { date.ToString(Constants.WMIDATEFORMAT) });
                ReportProgress.Invoke(this, new ProgressEventArgs(result.ToString()));
            }
        }
        catch (Exception e)
        {
            ReportProgress.Invoke(this, new ProgressEventArgs(ProgressLogLevel.Error, e.Message));
            return new HistoryRecord { RecordOf = def.Name, Argument = def.Environment, Result = "WMI Error" };
        }

        ReportProgress.Invoke(this, new ProgressEventArgs("Finished {0}", def.Name));
        return new HistoryRecord { RecordOf = def.Name, Argument = def.Environment, Result = "success" };
    }

    public void Dispose()
    {
        if (searcher != null)
        {
            lock (searcher)
            {
                searcher.Dispose();
            }
        }
    }
}

Workflow Steps

To create a custom workflow step you must implement SoftwareIDM.PanelModel.IWorkflowStepDefinition and SoftwareIDM.PanelModel.IRunWorkflowStep in separate classes. The first class contains the implementation for the settings interface, and the second class contains the logic to run the step.

Implementing IWorkflowStepDefinition

The workflow definition class has two methods. Properties will be used for collecting settings from the user interface if annotated with attributes. Other properties may be used to capture the results of workflow execution. Generally properties should be annotated with BsonElement to provide a short-name.

  • RunType() — Returns the corresponding IRunWorkflowStep type.
  • SearchText() — Called after the workflow instance executes and is being saved. Returns a string that can be indexed to expose the workflow result for full-text search.

StepRefUIAttribute

The StepRefUIAttribute attribute marks the property as a reference for another step in the workflow, and will cause the step-select drop-down list to be displayed.

Example:

[ClassDoc("Runs an arbitrary script or program as part of a workflow.", "Run Program")]
public class RunProgramDef : IWorkflowStepDefinition
{
    [BsonElement("pr"), UIAttribute(UIAttrKind.Text, true, NoLabel=true), MemberDoc("Program or script to run")]
    public string Program { get; set; }

    [BsonElement("ar"), UIAttribute(UIAttrKind.Text, RuleHelp=true, Size=60, Detail=true), MemberDoc("Arguments to pass to script")]
    public string Arguments { get; set; }

    [BsonElement("ne"), StepRefUIAttribute(), MemberDoc("Step to run after program terminates")]
    public StepRef Next { get; set; }

    [BsonElement("so"), MemberDoc("First kilobyte of captured StdOut buffer")]
    public string StdOut { get; set; }

    [BsonElement("se"), MemberDoc("First kilobyte of captured StdErr buffer")]
    public string StdErr { get; set; }

    public Type RunType()
    {
        return typeof(RunProgram);
    }

    public string SearchText()
    {
        return String.Join(", ", Program, Arguments, StdOut, StdErr);
    }
}

Implementing IRunWorkflowStep

The IRunWorkflow step class contains the logic for running a workflow. Unlike schedule steps, multiple workflows can share the same class instance, and the Run method may be called multiple times.

The first time the class instance is needed the Init() method is called. Then Run(IWorkflowStepDefinition) is called for each occurrence of the step. Finally, at the end of the Process Workflows iteration the Dispose() method is called. This allows re-use of expensive objects, like SMTP connections across multiple workflows instances.

Unlike with schedule steps, the Dispose() method will not be called on a separate thread from Run().

Example:

public sealed class SendEmail : IRunWorkflowStep
{
    SmtpClient Client;
    EmailSettings Email;

    public void Init()
    {
        Email = LoadSetting.Load<EmailSettings>();

        System.Net.ServicePointManager.ServerCertificateValidationCallback =
                new System.Net.Security.RemoteCertificateValidationCallback((_1, _2, _3, _4) => true);

        Client = new SmtpClient(Email.Server, Email.Port);
        Client.Timeout = 10 * 1000;
        Client.EnableSsl = Email.UseTLS;

        if (!String.IsNullOrEmpty(Email.Credential.User))
        {
            Client.Credentials = new System.Net.NetworkCredential(Email.Credential.User, Email.Credential.Decode());
        }
    }

    public StepRef Run(IWorkflowStepDefinition step)
    {
        var data = (SendEmailDef)step;

        if (String.IsNullOrEmpty(data.Recipient) || !Regex.IsMatch(data.Recipient, @"[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}", RegexOptions.IgnoreCase))
        {
            data.Recipient = Email.FallbackRecipient;
        }

        var msg = new MailMessage(Email.FromAddress, data.Recipient, data.Subject, data.Message);
        msg.IsBodyHtml = data.SendAsHtml;

        Client.Send(msg);

        return data.Next;
    }

    public void Dispose()
    {
        Client.Dispose();
    }
}

Health Probes

To create a custom health probe you must implement SoftwareIDM.PanelModel.IHealthProbeDefinition and SoftwareIDM.PanelModel.IRunHealthProbe in separate classes. The first class contains the implementation for the settings interface, and the second class contains the logic to run the step. If you want to return a custom probe result you may choose to sub-class SoftwareIDM.PanelModel.ProbeResult.

Implementing IHealthProbeDefinition

The health probe definition class has one method and one property. Other properties will be used for collecting settings from the user interface if annotated with UIAttribute.

  • RunType() — Returns the corresponding IRunHealthProbe type.
  • FailRule — Rule for determining if health probe passes. Most health probes should use the Default UIAttribute option to specify a default rule.

Example:

[ClassDoc("Checks run status of a service. <br />Result context.Value is numeric ServiceControllerStatus (4 is running). StringValue is translated into string (e.g. Running, Stopped).", "Service Status")]
public class ServiceProbeDef : IHealthProbeDefinition
{
    [UIAttribute(UIAttrKind.Text, true, NoLabel=true),
    MemberDoc("The network name of the server the service is installed on.")]
    public string Server { get; set; }

    [UIAttribute(UIAttrKind.Text, true, NoLabel=true),
    MemberDoc("The name of the service to check.")]
    public string Service { get; set; }

    public Type RunType()
    {
        return typeof(ServiceProbe);
    }

    [UIAttribute(UIAttrKind.Text, Size=60, RuleHelp=true, Default="context.StringValue != \"Running\"", Detail=true),
    MemberDoc(Name="Fail Rule")]
    public string FailRule { get; set; }
}

Implementing IRunHealthProbe

Like IRunWorkflowStep, IRunHealthProbe inherits from IDisposable and has an Init() method. The Init() method is called the first time the health probe is invoked after the service starts. The Dispose() method is called before the service exits, it is possible for this to happen from a separate thread while the health check is executing.

The Test(IHealthProbeDefinition) method should return a ProbeResult, typically by setting the Value property.

It is not necessary to handle all exceptions within the health probe, since any errors in a health probe will be caught by the caller, then placed in the Error and Trace values of a ProbeResult.

Example:

public sealed class ServiceProbe : IRunHealthProbe
{
    public void Init() { }

    public ProbeResult Test(IHealthProbeDefinition data)
    {
        var probe = (ServiceProbeDef)data;
        var service = new ServiceController(probe.Service, probe.Server);
        return new ServiceProbeResult(service.Status);
    }

    public void Dispose() { }
}

Sub-Classing ProbeResult

There are two common reasons to subclass ProbeResult. Generally, this is done to override the StringValue property to provide custom formatting of the Value object. In some cases it may be necessary to attach additional data to the result, as in the case of SoftwareIDM.PanelExtension.DBDiskProbeResult which has the property public Dictionary<string, int> Disks which is used to store data about free space on multiple drives.

Example:

[BsonDiscriminator("spr"), ClassDoc(Name = "Service Status Probe Result")]
public sealed class ServiceProbeResult : ProbeResult
{
    public ServiceProbeResult(ServiceControllerStatus status)
    {
        Value = (int)status;
    }

    public override string StringValue
    {
        get
        {
            return Enum.GetName(typeof(ServiceControllerStatus), Value);
        }
    }
}

Rule Functions

To create custom rule functions you must create a class that implements SoftwareIDM.PanelModel.IRuleFunctions. This interface does not define any members, it merely exists to flag a class as containing rule functions. If a class has the IRuleFunctions interface and is documented with the ClassDoc attribute, the Panel platform will search for static methods to add as Rule Functions.

All Rule Functions must have a return type of AttributeValue. This class is defined in the SoftwareIDM.PanelModel library and provides several helpful features for the Rule Engine. In particular, it supports automatic type coercion, and late value binding.

To create an AttributeValue you can simply pass a regular .NET object to the constructor. This automatically set both the underlying Value property, and the DataType enumeration. To convert an AttributeValue to a desired .NET type, simply call the .AsType(DateType) method with a type enumeration argument. You can also use the .AsBoolean shortcut property, and .ToString() method.

The underlying rule engine has logic for converting many different data types, and if a mapping exists the conversion will be performed. If a mapping doesn't exist, this generally means the values are logically incompatible. For example, DateTime maps to true if it's greater than DateTime.MinValue, but there is no mapping from Boolean to DateTime. If no mapping exists then null will be returned.

When creating a Rule Function, each argument must also be an AttributeValue and the Rule Engine only supports functions with up to five arguments. To get around this limitation, it is permissible for the final argument to be either an array (AttributeValue[]) or a dictionary (Dictionary<string, AttributeValue>).

Examples:

[ClassDoc("Perform operations and transformations on dates", "Date Functions")]
public class CommonDateFunctions : IRuleFunctions
{

}

[MemberDoc("Accepts a number of days to return as a TimeSpan.")]
public static AttributeValue Days(AttributeValue numberOfDays)
{
    return new AttributeValue(TimeSpan.FromDays((double)numberOfDays.AsType(AttributeType.Double)));
}

[MemberDoc("Returns Parameters[0] &amp;&amp; Parameters[1] .... <br />Uses javascript like boolean coercion with shortcircuit evaluation")]
public static AttributeValue And(AttributeValue[] Parameters)
{
    bool ret = true;
    foreach (var p in Parameters)
    {
        ret = ret && p.AsBoolean;
        if (!ret)
        {
            return new AttributeValue(false);
        }
    }

    return new AttributeValue(true);
}

Copyright © SoftwareIDM

Table of Contents