Skip to content

Commit

Permalink
Wording and marked as reviewed
Browse files Browse the repository at this point in the history
  • Loading branch information
mauroservienti authored Dec 31, 2024
1 parent 9b415e9 commit 6ca3638
Showing 1 changed file with 18 additions and 19 deletions.
37 changes: 18 additions & 19 deletions nservicebus/pipeline/manipulate-with-behaviors.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Manipulate pipeline with behaviors
summary: Manipulating the message-handling pipeline with behaviors
component: Core
versions: '[4.0,)'
reviewed: 2022-05-13
reviewed: 2024-12-31
related:
- nservicebus/pipeline/steps-stages-connectors
redirects:
Expand All @@ -12,25 +12,24 @@ redirects:
- nservicebus/pipeline/customizing-v6
---

Pipelines are made up of a group of steps acting on the same level of abstraction. This allows a number of scenarios, such as:
Pipelines are made up of a group of steps acting on the same level of abstraction. This allows several scenarios, such as:

* Defining a step that works with the "incoming physical" message before it has been deserialized.
* Defining a step that is executed before and after each handler invocation (keeping in mind that there can be multiple message handlers per message).
* Defining a step executed before and after each handler invocation (keeping in mind that there can be multiple message handlers per message).

Extending the pipeline is done with a custom behavior implementing `Behavior<TContext>`. `TContext` is the context of the stage that the behavior belongs to. A list of possible pipeline stages to which a behavior can be attached can be found in the [Steps, Stages, and Connectors article](steps-stages-connectors.md).
Extending the pipeline is done with a custom behavior implementing `Behavior<TContext>`. `TContext` is the context of the stage to which the behavior belongs. A list of possible pipeline stages to which a behavior can be attached can be found in the [Steps, Stages, and Connectors article](steps-stages-connectors.md).

Custom behavior should not rely on ordering within the same stage. If a custom behavior requires access to information that is generated by a previous stage, put the behavior into the next stage of the pipeline. If a custom behavior must create information that other behaviors rely on, place it in appropriate stages before those behaviors.
Custom behaviors should not rely on ordering within the same stage. If they require access to information generated by a previous stage, place them in the next pipeline stage. If they must create information that other behaviors rely on, place them in appropriate stages before those behaviors.

snippet: SamplePipelineBehavior

In the above code snippet the `SampleBehavior` class derives from the Behavior contract and targets the incoming context. This tells the framework to execute this behavior after the incoming raw message has been deserialized and a matching message type has been found. At runtime, the pipeline will call the `Invoke` method of each registered behavior passing in as arguments the current message context and an action to invoke the next behavior in the pipeline.
In the above code snippet, the `SampleBehavior` class derives from the Behavior contract and targets the incoming context. This tells the framework to execute this behavior after the incoming raw message has been deserialized and a matching message type has been found. At runtime, the pipeline will call each registered behavior's `Invoke` method, passing in as arguments the current message context and an action to invoke the _next_ behavior in the pipeline.

> [!WARNING]
> Each behavior is responsible to call the next step in the pipeline chain by invoking `next()`.
> Each behavior is responsible for calling the next step in the pipeline chain by invoking `next().`
## Add a new step


To add a custom behavior to the pipeline, register it from the endpoint configuration:

snippet: RegisterBehaviorEndpointConfiguration
Expand All @@ -40,17 +39,17 @@ Behaviors can also be registered from a `Feature` as shown below:
snippet: RegisterBehaviorFromFeature

> [!WARNING]
> Behaviors are only created once and the same instance is reused on every invocation of the pipeline. Consequently, every behavior dependency behave as a singleton, even if a different option was specified when registering it in [dependency injection](/nservicebus/dependency-injection/). Furthermore, the behavior and all dependencies must be thread-safe. Storing state in a behavior instance should be avoided since it will cause the state to be shared across all message handling sessions. This could lead to unwanted side effects.
> Behaviors are only created once, and the same instance is reused for every pipeline invocation. Consequently, every behavior dependency behaves as a singleton, even if a different option was specified during [dependency injection](/nservicebus/dependency-injection/) configuration. Furthermore, the behavior and all dependencies must be thread-safe. Storing state in a behavior instance should be avoided since it will cause the state to be shared across all message handling sessions. This could lead to unwanted side effects.
## Replace an existing step

To replace the implementation of an existing step, substitute it with a custom behavior:

snippet: ReplacePipelineStep

In order to replace the existing step, it is necessary to provide a step ID. The most reliable way of determining the step ID is to find the step definition in the NServiceBus source code.
A step ID must be provided to replace the existing step. The most reliable way to determine the step ID is to find the step definition in the NServiceBus source code.

Note that step IDs are hard-coded strings and may change in the future resulting in an unexpected behavior change. When replacing built-in steps, create automatic tests that will detect potential ID changes or step removal.
Note that step IDs are hard-coded strings and may change in the future, resulting in an unexpected behavior change. When replacing built-in steps, create automatic tests to detect potential ID changes or step removal.

> [!NOTE]
> Steps can also be registered from a [feature](features.md).
Expand All @@ -63,43 +62,43 @@ partial: disable

## Exception handling

Exceptions thrown from a behavior's `Invoke` method bubble up the chain. If the exception is not handled by any behavior, the message is considered faulty which results in putting the message back in the queue (and rolling back the transaction, when configured) or moving it to the error queue (depending on the endpoint's [recoverability](/nservicebus/recoverability/) configuration).
Exceptions thrown from a behavior's `Invoke` method bubble up the chain. If any behavior does not handle the exception, the message is considered faulty, which results in putting the message back in the queue (and rolling back the transaction when configured) or moving it to the error queue (depending on the endpoint's [recoverability](/services/recoverability/) configuration).

### MessageDeserializationException

If a message fails to deserialize, a `MessageDeserializationException` exception is thrown by the `DeserializeLogicalMessagesBehavior` behavior. In this case, the message is immediately moved to the error queue to avoid blocking the system with poison messages.

## Skip serialization

When writing extensions to the pipeline it may be necessary to either take control of the serialization or to skip it entirely. One example of this is with the [callbacks feature](/nservicebus/messaging/callbacks.md). Callbacks skip serialization for integers and enums and instead embed them in the message headers.
When writing extensions to the pipeline, it may be necessary to take control of the serialization or skip it entirely. One example is the [callbacks feature](/nservicebus/messaging/callbacks.md). Callbacks skip serialization for integers and enums; instead, embed them in the message headers.

To skip serialization, implement a behavior that targets `IOutgoingLogicalMessageContext`. For example, the following behavior skips serialization if the message is an integer, placing it in a header instead.

snippet: SkipSerialization

On the receiving side, this header can then be extracted and decisions on the incoming message processing pipeline can be made based on it.
On the receiving side, this header can then be extracted, and decisions on the incoming message processing pipeline can be made based on it.

## Sharing data between behaviors

Sometimes a parent behavior might need to pass information to a child behavior and vice versa. The `context` parameter of a behavior's `Invoke` method facilitates passing data between behaviors. The context is similar to a shared dictionary which allows adding and retrieving information from different behaviors.
Sometimes, a parent behavior might need to pass information on to a child behavior and vice versa. The `context` parameter of a behavior's `Invoke` method facilitates passing data between behaviors. The context is similar to a shared dictionary, which allows for adding and retrieving information from different behaviors.

snippet: SharingBehaviorData

> [!NOTE]
> Contexts are not thread-safe.
> [!NOTE]
> In NServiceBus version 6 and above, the context respects the stage hierarchy and only allows adding new entries in the scope of the current context. A child behavior (later in the pipeline chain) can read and even modify entries set by a parent behavior (earlier in the pipeline chain) but entries added by the child cannot be accessed from the parent.
> In NServiceBus version 6 and above, the context respects the stage hierarchy and only allows new entries to be added in the scope of the current context. A child behavior (later in the pipeline chain) can read and even modify entries set by a parent behavior (earlier in the pipeline chain), but entries added by the child cannot be accessed by the parent.
## Injecting dependencies into behaviors

snippet: InjectingDependencies

Dependencies injected into the constructor of a behavior become singletons regardless of their actual scope on the dependency injection container. In order to create instances per request or scoped dependencies it is required to use the `IServiceProvider` that is available as `Builder` property on the context.
Dependencies injected into the constructor of a behavior become singletons regardless of their actual scope on the dependency injection container. In order to create instances per request or scoped dependencies, it is required to use the `IServiceProvider` that is available as `Builder` property on the context.

The service provider available via the context varies depending on the pipeline stage. [Pipeline stages used within the context of an incoming message](/nservicebus/pipeline/steps-stages-connectors.md#stages-incoming-pipeline-stages) exposes a new child service provider created for each incoming message. All other use cases exposes the root service provider.
The service provider available via the context varies depending on the pipeline stage. [Pipeline stages used within the context of an incoming message](/nservicebus/pipeline/steps-stages-connectors.md#stages-incoming-pipeline-stages) exposes a new child service provider created for each incoming message. All other use cases expose the root service provider.

Behaviors in the [outgoing pipeline stage](/nservicebus/pipeline/steps-stages-connectors.md#stages-outgoing-pipeline-stages) exhibit different behaviors depending on how they are invoked. For example, when an outgoing behavior is invoked within the context of a message session the root service provider is exposed while when the same outgoing behavior is invoked within the scope of an incoming message, the child service provider for the incoming message will be used.
Behaviors in the [outgoing pipeline stage](/nservicebus/pipeline/steps-stages-connectors.md#stages-outgoing-pipeline-stages) exhibit different behaviors depending on how they are invoked. For example, when an outgoing behavior is invoked within the context of a message session, the root service provider is exposed. In contrast, when the same outgoing behavior is invoked within the scope of an incoming message, the child service provider for the incoming message will be used.

partial: options

Expand Down

0 comments on commit 6ca3638

Please sign in to comment.