Skip to content

Trigger registration

Koen edited this page Apr 29, 2021 · 7 revisions
⚠️ WARNING
The content in this article may be outdated with the release of V2.3.0

Triggers are registered either with the DbContext- or with a dependency injection container. Assuming that you're using DI:

services.AddTransient<IBeforeSaveTrigger<Student>, AssignDefaultCreatedDate>();

This will ensure that each individual trigger invocation uses a new instance of AssignDefaultCreatedDate. If you want to share state amongst trigger invocations you can use Scoped or Singleton registrations. Scoped triggers are scoped on the current ServiceProvider which is typically managed by the calling code.

What if you want to implement multiple triggers? Lets say we have a trigger as such:

public class RemoveEmailAddressesFromCommentContent : IBeforeSaveTrigger<Comment>, IAfterSaveTrigger<Comment>
{
    private readonly IEmailService _emailService;
    private List<string> _foundEmailAddresses;

    public RemoveEmailAddressesFromCommentContent(IEmailService emailService) {
        this._emailService = emailService;
    }

    public Task BeforeSave(ITriggerContext<Comment> context, CancellationToken cancellationToken)
    {
        if (context.ChangeType is ChangeType.Added or ChangeType.Modified)
        {
            _foundEmailAddresses = Helpers.FindAndReplaceEmailAddresses(context.Entity.Content);
        }
    }

    public Task AfterSave(ITriggerContext<Comment> context, CancellationToken cancellationToken)
    {
        if (_foundEmailAddresses != null) 
        {
            _emailSevice.SendEmail(context.AuthorEmailAddress, "Never leak your email address like that! We've removed the following email addresses: " + string.Join(", ", _foundEmailAddresses));
        }
    }
}

State needs to be shared between the BeforeSave and AfterSave trigger. This can be accomplished by registering them with your DI container:

services.AddScoped<RemoveEmailAddressFromCommentContent>();
services.AddScoped<IBeforeSaveTrigger<Comment>>(sp => sp.GetService<RemoveEmailAddressFromCommentContent>());
services.AddScoped<IAfterSaveTrigger<Comment>>(sp => sp.GetService<RemoveEmailAddressFromCommentContent>());

Sometimes you just want to register all triggers automatically, this can be accomplished with a little helper method:

void RegisterAssemblyTriggers(Type genericTriggerType)
{
    var triggerCandidates = typeof(Program)
        .Assembly
        .GetTypes()
        .Where(x => x.IsClass);

    foreach (var triggerCandidate in triggerCandidates)
    {
        foreach (var @interface in triggerCandidate.GetInterfaces())
        {
            if (@interface.IsConstructedGenericType && @interface.GetGenericTypeDefinition() == genericTriggerType)
            {
                services.TryAddScoped(triggerCandidate);

                services.AddScoped(@interface, serviceProvider => serviceProvider.GetRequiredService(triggerCandidate));
            }
        }
    }
}

RegisterAssemblyTriggers(typeof(IBeforeSaveTrigger<>));
RegisterAssemblyTriggers(typeof(IAfterSaveTrigger<>));
RegisterAssemblyTriggers(typeof(IAfterSaveFailedTrigger<>));
Clone this wiki locally