NServiceBus
Tayra integrates with NServiceBus through pipeline behaviors that automatically encrypt and decrypt [PersonalData] fields in messages flowing through the endpoint. It also provides a built-in ErasePersonalDataHandler for GDPR erasure workflows. If your organization already uses NServiceBus, adding Tayra is a natural fit — both are commercial products designed for production .NET systems.
Prerequisites
Tayra.NServiceBus requires NServiceBus 9.x or later. NServiceBus 9 introduced the Microsoft dependency injection integration that Tayra relies on for resolving ITayra.
Install
dotnet add package Tayra.NServiceBusInstall-Package Tayra.NServiceBusSetup
Register Tayra core services and a key store in your DI container, then call UseTayra() on the endpoint pipeline configuration:
services.AddTayra(opts => opts.LicenseKey = licenseKey); // defaults to InMemoryKeyStore; use PostgreSQL, Vault, etc. in production
var endpointConfiguration = new EndpointConfiguration("MyEndpoint");
endpointConfiguration.UseTransport<LearningTransport>();
// Register both Tayra behaviors on the pipeline
endpointConfiguration.Pipeline.UseTayra();Pipeline.UseTayra() does two things:
- Registers
TayraIncomingBehaviorin the incoming logical message pipeline stage - Registers
TayraOutgoingBehaviorin the outgoing logical message pipeline stage
You can also configure options through the DI container:
services.ConfigureTayraNServiceBus(opts =>
{
opts.EncryptOutbound = true; // default
opts.DecryptInbound = true; // default
});Prerequisites
Tayra core services must be registered via services.AddTayra() (optionally chaining a production key store) before the endpoint starts. The behaviors resolve ITayra from the NServiceBus dependency injection container at runtime.
Options
services.ConfigureTayraNServiceBus(opts =>
{
opts.EncryptOutbound = true; // default
opts.DecryptInbound = true; // default
});| Property | Type | Default | Description |
|---|---|---|---|
EncryptOutbound | bool | true | Encrypt PII fields on outbound messages before they reach the transport |
DecryptInbound | bool | true | Decrypt PII fields on inbound messages before they reach the handler |
Messages
Annotate your NServiceBus message classes with [DataSubjectId] and [PersonalData]:
public class CreateCustomerCommand : ICommand
{
[DataSubjectId]
public string CustomerId { get; set; }
[PersonalData]
public string Name { get; set; }
[PersonalData]
public string Email { get; set; }
public string AccountType { get; set; } // Not encrypted
}
public class CustomerCreatedEvent : IEvent
{
[DataSubjectId]
public string CustomerId { get; set; }
[PersonalData]
public string Name { get; set; }
[PersonalData]
public string Email { get; set; }
public DateTime CreatedAt { get; set; }
}How the Behaviors Work
NServiceBus processes messages through a staged pipeline. Tayra registers two behaviors at the logical message level:
TayraIncomingBehavior-- Runs in the incoming logical message stage, before the message handler. Decrypts all[PersonalData]fields so your handler receives cleartext.TayraOutgoingBehavior-- Runs in the outgoing logical message stage, before the message reaches the transport. Encrypts all[PersonalData]fields so personal data is protected in transit and at rest.
Your handlers work with plaintext values and do not need to know about encryption:
public class CreateCustomerHandler : IHandleMessages<CreateCustomerCommand>
{
public async Task Handle(
CreateCustomerCommand message,
IMessageHandlerContext context)
{
// message.Name and message.Email are already decrypted
var customer = await SaveCustomer(message);
await context.Publish(new CustomerCreatedEvent
{
CustomerId = message.CustomerId,
Name = message.Name, // Will be encrypted by TayraOutgoingBehavior
Email = message.Email, // Will be encrypted by TayraOutgoingBehavior
CreatedAt = DateTime.UtcNow,
});
}
}Graceful Error Handling
If decryption or encryption fails (e.g., a key store is temporarily unavailable), the behavior logs a warning and allows the message to proceed. This prevents transient key store failures from blocking message processing. Failed messages still benefit from NServiceBus's built-in retry and error queue mechanisms.
Sagas
Saga data classes work the same way as regular messages. Annotate saga data properties with [PersonalData] and the behaviors handle encryption transparently:
public class OnboardingPolicy :
Saga<OnboardingPolicyData>,
IAmStartedByMessages<CreateCustomerCommand>
{
public async Task Handle(
CreateCustomerCommand message,
IMessageHandlerContext context)
{
// message.Name is already decrypted
Data.CustomerName = message.Name;
Data.CustomerEmail = message.Email;
}
protected override void ConfigureHowToFindSaga(
SagaPropertyMapper<OnboardingPolicyData> mapper)
{
mapper.MapSaga(s => s.CustomerId)
.ToMessage<CreateCustomerCommand>(m => m.CustomerId);
}
}
public class OnboardingPolicyData : ContainSagaData
{
[DataSubjectId]
public string CustomerId { get; set; }
[PersonalData]
public string CustomerName { get; set; }
[PersonalData]
public string CustomerEmail { get; set; }
}Saga Persistence
When NServiceBus persists saga data to its storage backend, the TayraOutgoingBehavior has already encrypted the PII fields. This means saga data at rest contains encrypted values regardless of which saga persister you use (SQL, RavenDB, Azure Table Storage, etc.).
GDPR Erasure Handler
Tayra includes a built-in handler for GDPR Article 17 "right to erasure" workflows:
// Send the command through NServiceBus
await endpointInstance.SendLocal(
new ErasePersonalDataCommand("cust-42", "GDPR Article 17 request"));The built-in ErasePersonalDataHandler:
- Calls
cryptoEngine.DeleteAllKeysAsync(dataSubjectId)to delete all encryption keys - Logs the erasure with the data subject ID and reason
- Publishes a
PersonalDataErasedEventfor downstream handlers
Erasure Event
The PersonalDataErasedEvent is published by the built-in handler and can be consumed by your own handlers for audit logging, notifications, or cascading operations:
public class AuditErasureHandler : IHandleMessages<PersonalDataErasedEvent>
{
private readonly ILogger<AuditErasureHandler> _logger;
public AuditErasureHandler(ILogger<AuditErasureHandler> logger)
{
_logger = logger;
}
public Task Handle(
PersonalDataErasedEvent message,
IMessageHandlerContext context)
{
_logger.LogInformation(
"Personal data erased for {Subject} at {Time}. Reason: {Reason}",
message.DataSubjectId, message.ErasedAt, message.Reason);
return Task.CompletedTask;
}
}See Also
- Getting Started -- End-to-end encryption tutorial
- MassTransit Integration -- MassTransit message pipeline encryption
- Wolverine Integration -- Wolverine message pipeline encryption
- Key Stores -- Production key store options
