Building a Custom Feature

Component: NServiceBus | Nuget: NServiceBus (Version: 5.x)

Introduction

This sample illustrates how to build a custom Feature. In this Feature some diagnostics is performed:

Both of these are implemented as dependent Features that depend on the Diagnostics Feature.

Diagnostics Feature

Edit
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 enables all dependent to be easily toggled enabling or disabling it through configuration. In this case it is enabled by default.

Custom Logger

This Feature injects a custom logger into the container that can then be used by below Features.

Edit
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

This feature depends on both the Diagnostics Feature.

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

    protected override void Setup(FeatureConfigurationContext context)
    {
        var pipeline = context.Pipeline;
        pipeline.Register<Registration>();
    }

    class Registration :
        RegisterStep
    {
        public Registration()
            : base(
                stepId: "HandlerTimer",
                behavior: typeof(HandlerTimerBehavior),
                description: "Logs a warning if a handler take more than a specified time")
        {
            InsertBefore(WellKnownStep.InvokeHandlers);
        }
    }
}

Behavior

The pipeline behavior that performs the handler timing.

Edit
class HandlerTimerBehavior :
    IBehavior<IncomingContext>
{
    CustomLogger logger;

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

    public void Invoke(IncomingContext context, Action next)
    {
        var handlerName = context.MessageHandler.Instance.GetType().Name;
        using (logger.StartTimer(handlerName))
        {
            next();
        }
    }
}

Saga State Audit Feature

This feature depends on both the Diagnostics and Saga Features.

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

    protected override void Setup(FeatureConfigurationContext context)
    {
        var pipeline = context.Pipeline;
        pipeline.Register<Registration>();
    }

    class Registration :
        RegisterStep
    {
        public Registration()
            : base(
                stepId: "SagaStateAudit",
                behavior: typeof(SagaStateAuditBehavior),
                description: "Logs Saga State")
        {
            InsertBefore(WellKnownStep.InvokeSaga);
        }
    }
}

Behavior

The pipeline behavior that captures the saga state.

Edit
class SagaStateAuditBehavior :
    IBehavior<IncomingContext>
{
    CustomLogger logger;

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

    public void Invoke(IncomingContext context, Action next)
    {
        next();
        var saga = context.MessageHandler.Instance as Saga;
        if (saga != null)
        {
            var instance = saga.Entity;
            logger.WriteSaga(instance);
        }
    }
}

Related Articles


Last modified