Skip to content

Plugin RegisteredEvent

Daryl LaBar edited this page Oct 16, 2024 · 23 revisions

Implement this abstract method in your plugin class to be able to specify when your plugin should run, what columns are required, and based on the context, what method of the plugin should be executed.

The RegisteredEventBuilder class is used to define RegisteredEvent(s) the plugin will execute for. Here is an example usage:

protected override IEnumerable<RegisteredEvent> CreateEvents()
{
    return new RegisteredEventBuilder(PipelineStage.PreOperation, MessageType.Create, MessageType.Update)
        .ForEntities<Contact>()
        .WithExecuteAction(OnUserEmailRouterAccessApproved)
        .WithValidator(new RequirementValidator()
            .Updated(new SystemUser { EmailRouterAccessApproval = SystemUser_EmailRouterAccessApproval.Approved }))

        .WithAssertValidator(new RequirementValidator()
            .Contains<Contact>(ContextEntity.CoalesceTargetPreImage, c => new { c.FullName, c.EmailAddress1 }),
                "Name and Email address are required to notify admin of email router access approval!")
        .Build();
}

and here is a line-by-line breakdown of what it means:


RegisteredEventBuilder Constructor - This will limit the plugin to run only if the plugin execution context is for the PreOperation stage of a Create or Update message. Any other plugin event will cause the DLaBGenericPluginBase to throw an exception:

new RegisteredEventBuilder(PipelineStage.PreOperation, MessageType.Create, MessageType.Update)

ForEntities - This will limit the plugin to run only run if the registered table is Contact. Any other execution of the plugin for a different table will cause the DLaBGenericPluginBase to throw an exception. This is optional, and if no table/entity is defined, the plugin can be run with any table type:

.ForEntities<Contact>()

WithExecuteAction - Rather than executing the default ExecuteInternal method of the plugin, the OnUserEmailRouterAccessApproved method will be called instead. This is optional, and if no execute action is defined, the ExecuteInternal method of the plugin will be called:

.WithExecuteAction(OnUserEmailRouterAccessApproved)

This allows for multiple RegisteredEvent's to be registered with different entry points into the plugin, for example having a OnCreate method for creates and OnUpdate method for updates:

return new RegisteredEventBuilder(PipelineStage.PreOperation, MessageType.Create)
        .WithExecuteAction(OnCreate)
    .And(PipelineStage.PreOperation, MessageType.Update)
        .WithExecuteAction(OnUpdate);

WithValidator - Defines the validation to be run to determine if the plugin execution should be skipped without error. This is optional, and if no validator is defined, the execute action will always be called. In this example, if the EmailRouterAccessApproval has not been updated to Approved then the plugin execution will be skipped without error:

.WithValidator(new RequirementValidator()
    .Updated(new SystemUser { EmailRouterAccessApproval = SystemUser_EmailRouterAccessApproval.Approved }))

Note

Only one RequirementValidator can be defined per RegisteredEvent.

See the RequirementValidator section for more information on defining a validator.


WithAssertValidator - Defines a requirement that if not true, will cause an exception to be thrown. This is optional, and if no assert validator is defined, no exception will be thrown. Assert validators will only be evaluated after the standard validator (if present) has passed validation, In this example, if the Target (coalesced with the pre-image) does not contain a non-null value for fullname and emailaddress an InvalidPluginExecutionException will be thrown with the message "Name and Email address are required to notify admin of email router access approval!":

.WithAssertValidator(new RequirementValidator()
    .Contains<Contact>(ContextEntity.CoalesceTargetPreImage, c => new { c.FullName, c.EmailAddress1 }),
        "Name and Email address are required to notify admin of email router access approval!")

The RequirementValidator is used to determine if a the plugin execution context meets the requirements for the registered event. This can be used with the RegisteredEventBuilder.WithValidator to control if the plugin execution should be skipped entirely, or the RegisteredEventBuilder.WithAssertValidator to throw an exception if the context fails validation.

The requirement validator has a combination of fluent methods made up of 8 verbs (Contains, ContainsNullable, ContainsNull, Missing, MissingOrNull, Updated, Changed, Cleared), as well as an optional postfix "Any".

The 8 Verbs:

  • Contains - Determines if the entity contains all the defined attributes with a (non-null) value, or if the Entity overload is used, the specified values.
  • ContainsNullable - Same as Contains, but allows null values.
  • ContainsNull - Same as Contains, but values must be null.
  • Missing - Requires all columns are not present in the entity's Attributes collection.
  • MissingOrNull - Requires all columns are not present in the entity's Attributes collection, or if they are present, the are null.
  • Not - Requires exact list of specific values to not be present.
  • Updated - Determines if the target contains the attribute with a (non-null) value, and the value is different than the value in the pre-image or, if the target contains a particular value for the attribute.
  • Changed - Same as Updated, but allows null values.
  • Cleared - Same as Updated, but values must be null.

Important

Since setting a string value to the empty string (or white spaces) will result in the column being updated to null in the database, columns that have empty string values will be treated as null.

Post Fix Any:

  • Any - Only one of the columns must match the requirements. For example ContainsAny<Contact>(ContextEntity.Target, c => new { c.EMailAddress1, c.EMailAddress2 }) will be considered valid if the target has a non-null value for either EMailAddress1 or EMailAddress2.

Warning

PreImages do not return an attribute in the collection if the attribute is null. Therefore, any requirement for the PreImage or the CoalesceTargetPreImage being null, nullable, or have a specific value which happens to be null, will be considered valid if the column is not in the Entity.Attributes collection. This should not be an issue if the PreImage includes the null or nullable column, but if the PreImage registration does not include the column, then this will result in false validations.

Requirement Validator Example The unit testing for the Requirement Validator does a good job of describing the expected results of different calls using an entity with two columns, A and B, in various states (missing from the attributes collection or in the collection but null or empty or populated)

private static readonly Dictionary<string, Expected> IsValidByFunction = new () {
    
    //                                                               <******** A Null *********>      <******** A Empty ********>      <********** A ***********>       <********* No A **********> 
    //                                                               ANull  AnBn   AnBe   ANullB      AEmpty AeBn   AeBe   AeB         A      ABNull ABe    AB          None   BNull  BEmpty B
    { nameof(RequirementValidator.Changed),             new Expected(false, true,  true,  true,  /**/ false, true,  true,  true,  /**/ false, true,  true,  true,  /**/ false, false, false, false) },
    { nameof(RequirementValidator.ChangedAny),          new Expected(true,  true,  true,  true,  /**/ true,  true,  true,  true,  /**/ true,  true,  true,  true,  /**/ false, true,  true,  true ) },
    { nameof(RequirementValidator.Cleared),             new Expected(false, true,  true,  false, /**/ false, true,  true,  false, /**/ false, false, false, false, /**/ false, false, false, false) },
    { nameof(RequirementValidator.ClearedAny),          new Expected(true,  true,  true,  true,  /**/ true,  true,  true,  true,  /**/ false, true,  true,  false, /**/ false, true,  true,  false) },
    { nameof(RequirementValidator.Contains),            new Expected(false, false, false, false, /**/ false, false, false, false, /**/ false, false, false, true,  /**/ false, false, false, false) },
    { nameof(RequirementValidator.ContainsAny),         new Expected(false, false, false, true,  /**/ false, false, false, true,  /**/ true,  true,  true,  true,  /**/ false, false, false, true ) },
    { nameof(RequirementValidator.ContainsAnyNull),     new Expected(true,  true,  true,  true,  /**/ true,  true,  true,  true,  /**/ false, true,  true,  false, /**/ false, true,  true,  false) },
    { nameof(RequirementValidator.ContainsAnyNullable), new Expected(true,  true,  true,  true,  /**/ true,  true,  true,  true,  /**/ true,  true,  true,  true,  /**/ false, true,  true,  true ) },
    { nameof(RequirementValidator.ContainsNull),        new Expected(false, true,  true,  false, /**/ false, true,  true,  false, /**/ false, false, false, false, /**/ false, false, false, false) },
    { nameof(RequirementValidator.ContainsNullable),    new Expected(false, true,  true,  true,  /**/ false, true,  true,  true,  /**/ false, true,  true,  true,  /**/ false, false, false, false) },
    { nameof(RequirementValidator.Missing),             new Expected(false, false, false, false, /**/ false, false, false, false, /**/ false, false, false, false, /**/ true,  false, false, false) },
    { nameof(RequirementValidator.MissingAny),          new Expected(true,  false, false, false, /**/ true,  false, false, false, /**/ true,  false, false, false, /**/ true,  true,  true,  true ) },
    { nameof(RequirementValidator.MissingOrNull),       new Expected(true,  true,  true,  false, /**/ true,  true,  true,  false, /**/ false, false, false, false, /**/ true,  true,  true,  false) },
    { nameof(RequirementValidator.MissingOrNullAny),    new Expected(true,  true,  true,  true,  /**/ true,  true,  true,  true,  /**/ true,  true,  true,  false, /**/ true,  true,  true,  true ) },
    { nameof(RequirementValidator.Not),                 new Expected(true,  true,  true,  true,  /**/ true,  true,  true,  true,  /**/ true,  true,  true,  false, /**/ true,  true,  true,  true ) },
    { nameof(RequirementValidator.NotAny),              new Expected(true,  true,  true,  false, /**/ true,  true,  true,  false, /**/ false, false, false, false, /**/ true,  true,  true,  false) },
    { nameof(RequirementValidator.Updated),             new Expected(false, false, false, false, /**/ false, false, false, false, /**/ false, false, false, true,  /**/ false, false, false, false) },
    { nameof(RequirementValidator.UpdatedAny),          new Expected(false, false, false, true,  /**/ false, false, false, true,  /**/ true,  true,  true,  true,  /**/ false, false, false, true ) },
};