Interceptors
Interceptors allow you to run custom server-side logic before and after Dataverse operations. By implementing the ITableRecordInterceptor interface, you can hook into the lifecycle of record creation, updates, deletions, associations, and disassociations.
Overview
Common use cases for interceptors include:
- Setting default field values before a record is created (e.g. assigning an owner)
- Validating or transforming data before it is persisted
- Enforcing business rules that go beyond field-level validation
- Audit logging after records are created, updated, or deleted
- Triggering side effects such as sending notifications or updating related records
Note
Interceptors run on the server and apply to all operations for all tables. Use the
TableRecord.TableNameproperty to filter logic to specific tables.
Getting Started
1. Create an Interceptor
Create a class that implements ITableRecordInterceptor and provide implementations for each method. Return Task.CompletedTask for any hooks you don't need.
public class MyInterceptor : ITableRecordInterceptor
{
public Task OnBeforeCreateAsync(TableRecord record)
{
if (record.TableName == "contact")
{
// Set a default value before the record is created
record.Properties["statuscode"] = new ChoiceValue(1);
}
return Task.CompletedTask;
}
public Task OnAfterCreateAsync(TableRecord record) => Task.CompletedTask;
public Task OnBeforeUpdateAsync(TableRecord record) => Task.CompletedTask;
public Task OnAfterUpdateAsync(TableRecord record) => Task.CompletedTask;
public Task OnBeforeDeleteAsync(TableRecordReference record) => Task.CompletedTask;
public Task OnAfterDeleteAsync(TableRecordReference record) => Task.CompletedTask;
public Task OnBeforeAssociateAsync(TableRecord record) => Task.CompletedTask;
public Task OnAfterAssociateAsync(TableRecord record) => Task.CompletedTask;
public Task OnBeforeDisassociateAsync(TableRecord record) => Task.CompletedTask;
public Task OnAfterDisassociateAsync(TableRecord record) => Task.CompletedTask;
}
2. Register in Dependency Injection
Register your interceptor in the DI container in your project's Program.cs file:
builder.Services.AddTransient<ITableRecordInterceptor, MyInterceptor>();
Since interceptors are resolved from the DI container, you can inject any required services through constructor injection.
Lifecycle Hooks
The interface provides before and after hooks for five types of operations:
Create
OnBeforeCreateAsync and OnAfterCreateAsync run around new record creation. The before hook is ideal for setting default values or validating required fields.
public async Task OnBeforeCreateAsync(TableRecord record)
{
if (record.TableName == "contact")
{
// Assign the current user as the owning contact
var authState = await _authStateProvider.GetAuthenticationStateAsync();
var userId = authState.User.GetUserId();
if (userId != null)
{
record.Properties["ppp_owningcontact"] = new LookupValue
{
TableName = "contact",
Value = userId.Value
};
}
}
}
Update
OnBeforeUpdateAsync and OnAfterUpdateAsync run around record updates. Use the before hook to enforce business rules or modify values before they are persisted.
Delete
OnBeforeDeleteAsync and OnAfterDeleteAsync run around record deletion. Note that these hooks receive a TableRecordReference (table name and ID) rather than a full TableRecord.
Associate & Disassociate
OnBeforeAssociateAsync / OnAfterAssociateAsync and OnBeforeDisassociateAsync / OnAfterDisassociateAsync run when many-to-many relationships are created or removed.
Injecting Services
Because interceptors are registered in the DI container, you can inject services via constructor injection. This is useful for accessing authentication state, logging, or other application services.
internal class SetOwnerInterceptor(
AuthenticationStateProvider authStateProvider) : ITableRecordInterceptor
{
public async Task OnBeforeCreateAsync(TableRecord record)
{
var authState = await authStateProvider.GetAuthenticationStateAsync();
// Use authState to set owner fields...
}
// ... other methods return Task.CompletedTask
}
API Reference
ITableRecordInterceptor Class
Methods
Name | Parameters | Type | Description |
|---|---|---|---|
OnAfterAssociateAsync | TableRecord record | Task | Invoked after a record has been associated with another record via a many-to-many relationship. Useful for updating related aggregates, emitting domain events, or maintaining referential integrity. |
OnAfterCreateAsync | TableRecord record | Task | Invoked immediately after a new record has been created. Use this for post‑creation actions such as logging, notifications, or triggering downstream workflows. |
OnAfterDeleteAsync | TableRecordReference record | Task | Invoked after a record has been deleted. Useful for cleanup tasks, audit logging, or cascading domain events. |
OnAfterDisassociateAsync | TableRecord record | Task | Invoked after a record has been disassociated from another record. Useful for cleanup, updating aggregates, or triggering follow‑up processes. |
OnAfterUpdateAsync | TableRecord record | Task | Invoked after an existing record has been updated. Useful for audit logging, cache invalidation, or triggering follow‑up processes. |
OnBeforeAssociateAsync | TableRecord record | Task | Invoked before a record is associated with another record via a many-to-many relationship. Use this to validate the association, enforce relationship rules, or prevent invalid links. |
OnBeforeCreateAsync | TableRecord record | Task | Invoked immediately before a new record is created. Use this to validate, mutate, or prepare the record prior to persistence. |
OnBeforeDeleteAsync | TableRecordReference record | Task | Invoked before a record is deleted. Use this to enforce deletion rules, perform soft‑delete conversions, or block deletion under certain conditions. |
OnBeforeDisassociateAsync | TableRecord record | Task | Invoked before a record is disassociated from another record. Use this to validate the disassociation, enforce constraints, or block removal of required relationships. |
OnBeforeUpdateAsync | TableRecord record | Task | Invoked before an existing record is updated. Use this to validate changes, enforce business rules, or modify the record prior to the update being persisted. |
OnAfterAssociateAsyncanother record via a many-to-many relationship. Useful for updating related aggregates,
emitting domain events, or maintaining referential integrity.
OnAfterCreateAsyncUse this for post‑creation actions such as logging, notifications,
or triggering downstream workflows.
OnAfterDeleteAsyncUseful for cleanup tasks, audit logging, or cascading domain events.
OnAfterDisassociateAsyncanother record. Useful for cleanup, updating aggregates,
or triggering follow‑up processes.
OnAfterUpdateAsyncUseful for audit logging, cache invalidation, or triggering
follow‑up processes.
OnBeforeAssociateAsyncrecord via a many-to-many relationship. Use this to validate the association, enforce
relationship rules, or prevent invalid links.
OnBeforeCreateAsyncUse this to validate, mutate, or prepare the record prior to persistence.
OnBeforeDeleteAsyncUse this to enforce deletion rules, perform soft‑delete conversions,
or block deletion under certain conditions.
OnBeforeDisassociateAsyncrecord. Use this to validate the disassociation, enforce
constraints, or block removal of required relationships.
OnBeforeUpdateAsyncUse this to validate changes, enforce business rules, or modify
the record prior to the update being persisted.
