Getting Started
Architecture
NServiceBus
Transports
Persistence
ServiceInsight
ServicePulse
ServiceControl
Monitoring

Building a custom feature

Component: NServiceBus
NuGet Package: NServiceBus (7.x)

This sample illustrates how to build a custom NServiceBus feature. In this feature, some diagnostics are performed:

Both of these are implemented as features that depend on the diagnostics feature.

Diagnostics feature

public class DiagnosticsFeature :
    Feature
{
    internal DiagnosticsFeature()
    {
        EnableByDefault();
    }

    protected override void Setup(FeatureConfigurationContext context)
    {
        var container = context.Container;
        container.ConfigureComponent<CustomLogger>(DependencyLifecycle.SingleInstance);
    }
}

The diagnostics feature allows all dependencies to be easily toggled, enabling or disabling them through configuration. In this case, it is enabled by default.

Custom logger

The feature in this sample injects a custom logger that can be used by other features:

public class CustomLogger
{
    static ILog log = LogManager.GetLogger<CustomLogger>();

    public IDisposable StartTimer(string name)
    {
        return new Log(name);
    }

    public void WriteSaga(IContainSagaData sagaData)
    {
        var serialized = JsonConvert.SerializeObject(sagaData, Formatting.Indented);
        log.Warn($"Saga State: \r\n{serialized}");
    }

    class Log :
        IDisposable
    {
        string name;
        Stopwatch stopwatch;

        public Log(string name)
        {
            stopwatch = Stopwatch.StartNew();
            this.name = name;
        }

        public void Dispose()
        {
            log.Warn($"{name} took {stopwatch.ElapsedMilliseconds}ms to process");
        }
    }
}

Handler timing feature

The feature depends on the diagnostics feature:

public class HandlerTimerFeature :
    Feature
{
    internal HandlerTimerFeature()
    {
        EnableByDefault();
        DependsOn<DiagnosticsFeature>();
    }

    protected override void Setup(FeatureConfigurationContext context)
    {
        var pipeline = context.Pipeline;
        pipeline.Register(
            stepId: "HandlerTimer",
            behavior: typeof(HandlerTimerBehavior),
            description: "Logs handler time");
    }
}

Behavior

The pipeline behavior that performs the handler timing:

class HandlerTimerBehavior :
    Behavior<IInvokeHandlerContext>
{
    CustomLogger logger;

    public HandlerTimerBehavior(CustomLogger logger)
    {
        this.logger = logger;
    }

    public override async Task Invoke(IInvokeHandlerContext context, Func<Task> next)
    {
        var handlerName = context.MessageHandler.Instance.GetType().Name;
        using (logger.StartTimer(handlerName))
        {
            await next();
        }
    }
}

Saga state audit feature

This feature depends on both the Diagnostics and Saga features.

public class SagaStateAuditFeature :
    Feature
{
    internal SagaStateAuditFeature()
    {
        EnableByDefault();
        DependsOn<Sagas>();
        DependsOn<DiagnosticsFeature>();
    }

    protected override void Setup(FeatureConfigurationContext context)
    {
        var pipeline = context.Pipeline;
        pipeline.Register(
            stepId: "SagaStateAudit",
            behavior: typeof(SagaStateAuditBehavior),
            description: "Logs Saga State");
    }
}

Behavior

The pipeline behavior that captures the saga state:

class SagaStateAuditBehavior :
    Behavior<IInvokeHandlerContext>
{
    CustomLogger logger;

    public SagaStateAuditBehavior(CustomLogger logger)
    {
        this.logger = logger;
    }

    public override async Task Invoke(IInvokeHandlerContext context, Func<Task> next)
    {
        await next();
        if (context.Extensions.TryGet(out ActiveSagaInstance activeSagaInstance))
        {
            var instance = activeSagaInstance.Instance.Entity;
            logger.WriteSaga(instance);
        }
    }
}

Related Articles