< Summary

Information
Class: NexMediator.Core.NexMediatorOptions
Assembly: NexMediator.Core
File(s): /home/runner/work/NexMediator/NexMediator/src/NexMediator.Core/NexMediatorOptions.cs
Line coverage
79%
Covered lines: 74
Uncovered lines: 19
Coverable lines: 93
Total lines: 196
Line coverage: 79.5%
Branch coverage
69%
Covered branches: 25
Total branches: 36
Branch coverage: 69.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%22100%
.cctor()100%11100%
AddBehavior(...)83.33%6691.66%
AddBehavior(...)0%4260%
AddBehavior(...)100%210%
RemoveBehavior(...)83.33%6693.33%
Validate()100%1010100%
OrderBehaviors(...)100%11100%
HasBehavior(...)50%2280%
ImplementsPipelineBehavior(...)50%22100%
HasBehaviorOutOfOrder(...)100%22100%
get_BehaviorType()100%11100%

File(s)

/home/runner/work/NexMediator/NexMediator/src/NexMediator.Core/NexMediatorOptions.cs

#LineLine coverage
 1using Microsoft.Extensions.DependencyInjection;
 2using Microsoft.Extensions.DependencyInjection.Extensions;
 3using NexMediator.Abstractions.Context;
 4using NexMediator.Abstractions.Interfaces;
 5using NexMediator.Pipeline.Behaviors;
 6
 7namespace NexMediator.Core;
 8
 9/// <summary>
 10/// Configuration options for registering and ordering pipeline behaviors.
 11/// </summary>
 12public class NexMediatorOptions
 13{
 14    private readonly IServiceCollection _services;
 2815    private readonly Dictionary<Type, BehaviorRegistration> _behaviorMap = new();
 16
 317    private static readonly Type LoggingBehaviorType = typeof(LoggingBehavior<,>);
 318    private static readonly Type FluentValidationBehaviorType = typeof(FluentValidationBehavior<,>);
 319    private static readonly Type CachingBehaviorType = typeof(CachingBehavior<,>);
 320    private static readonly Type TransactionBehaviorType = typeof(TransactionBehavior<,>);
 21
 22    /// <summary>
 23    /// Initializes a new instance of <see cref="NexMediatorOptions"/>.
 24    /// </summary>
 25    /// <param name="services">
 26    /// Service collection to register behaviors and context.
 27    /// </param>
 2828    public NexMediatorOptions(IServiceCollection services)
 2829    {
 2830        _services = services ?? throw new ArgumentNullException(nameof(services));
 2831    }
 32
 33    /// <summary>
 34    /// Adds a pipeline behavior with a specific execution order.
 35    /// Registers both interface and concrete type for DI.
 36    /// </summary>
 37    /// <param name="behaviorType">Open-generic behavior type.</param>
 38    /// <param name="order">Execution order (lower runs first).</param>
 39    /// <returns>Same <see cref="NexMediatorOptions"/> for chaining.</returns>
 40    public NexMediatorOptions AddBehavior(Type behaviorType, int order)
 1441    {
 1442        if (behaviorType == null)
 043            throw new ArgumentNullException(nameof(behaviorType));
 1444        if (!behaviorType.IsGenericTypeDefinition)
 145            throw new ArgumentException("Type must be open generic.", nameof(behaviorType));
 1346        if (!ImplementsPipelineBehavior(behaviorType))
 147            throw new ArgumentException("Type must implement INexPipelineBehavior<,>.", nameof(behaviorType));
 48
 49        // Track execution order
 1250        _behaviorMap[behaviorType] = new BehaviorRegistration(behaviorType, order);
 51
 52        // Register open-generic interface and concrete type
 1253        _services.AddTransient(typeof(INexPipelineBehavior<,>), behaviorType);
 1254        _services.AddTransient(behaviorType);
 55
 1256        return this;
 1257    }
 58
 59    /// <summary>
 60    /// Adds a pipeline behavior, optionally enabling correlation support.
 61    /// Registers with scoped lifetime if logging, otherwise transient.
 62    /// </summary>
 63    /// <param name="behaviorType">Open-generic behavior type.</param>
 64    /// <param name="order">Execution order.</param>
 65    /// <param name="enableCorrelation">
 66    /// If true and logging behavior, registers correlation context and scoped behavior.
 67    /// </param>
 68    /// <returns>Same <see cref="NexMediatorOptions"/> for chaining.</returns>
 69    public NexMediatorOptions AddBehavior(Type behaviorType, int order, bool enableCorrelation)
 070    {
 071        if (behaviorType == null)
 072            throw new ArgumentNullException(nameof(behaviorType));
 73
 074        if (enableCorrelation && behaviorType == LoggingBehaviorType)
 075        {
 076            _services.TryAddScoped<ICorrelationContext, CorrelationContext>();
 077            _services.AddScoped(behaviorType);
 078        }
 79        else
 080        {
 081            _services.AddTransient(behaviorType);
 082        }
 83
 84        // Track execution order
 085        _behaviorMap[behaviorType] = new BehaviorRegistration(behaviorType, order);
 86        // Register open-generic interface
 087        _services.AddTransient(typeof(INexPipelineBehavior<,>), behaviorType);
 88
 089        return this;
 090    }
 91
 92    /// <summary>
 93    /// Adds a pipeline behavior of the specified generic type.
 94    /// </summary>
 95    /// <typeparam name="TBehavior">Open-generic behavior type.</typeparam>
 96    /// <param name="order">Execution order.</param>
 97    /// <returns>Same <see cref="NexMediatorOptions"/> for chaining.</returns>
 98    public NexMediatorOptions AddBehavior<TBehavior>(int order)
 99        where TBehavior : class
 0100        => AddBehavior(typeof(TBehavior), order);
 101
 102    /// <summary>
 103    /// Removes a previously added behavior and its DI registrations.
 104    /// </summary>
 105    /// <param name="behaviorType">Open-generic behavior type to remove.</param>
 106    /// <returns>Same <see cref="NexMediatorOptions"/> for chaining.</returns>
 107    public NexMediatorOptions RemoveBehavior(Type behaviorType)
 2108    {
 2109        if (behaviorType == null)
 0110            throw new ArgumentNullException(nameof(behaviorType));
 111
 2112        if (_behaviorMap.Remove(behaviorType))
 1113        {
 114            // Remove all service descriptors for this behavior
 1115            var descriptors = _services
 2116                .Where(d => d.ImplementationType == behaviorType)
 1117                .ToList();
 118
 7119            foreach (var descriptor in descriptors)
 2120            {
 2121                _services.Remove(descriptor);
 2122            }
 1123        }
 124
 2125        return this;
 2126    }
 127
 128    /// <summary>
 129    /// Validates the current behavior configuration and returns warnings.
 130    /// </summary>
 131    /// <returns>List of warning messages.</returns>
 132    public IReadOnlyList<string> Validate()
 20133    {
 20134        var messages = new List<string>();
 135
 136        // Check for duplicate execution orders
 20137        var duplicateGroups = _behaviorMap.Values
 8138            .GroupBy(b => b.Order)
 26139            .Where(g => g.Count() > 1);
 64140        foreach (var group in duplicateGroups)
 2141        {
 6142            var names = string.Join(", ", group.Select(b => b.BehaviorType.Name));
 2143            messages.Add($"Multiple behaviors with order {group.Key}: {names}");
 2144        }
 145
 146        // Warn if built-in behaviors are out of recommended order
 20147        if (HasBehaviorOutOfOrder(LoggingBehaviorType, 1)
 20148            || HasBehaviorOutOfOrder(FluentValidationBehaviorType, 2)
 20149            || HasBehaviorOutOfOrder(CachingBehaviorType, 3)
 20150            || HasBehaviorOutOfOrder(TransactionBehaviorType, 4))
 2151        {
 2152            messages.Add(
 2153                "Built-in behaviors are not in recommended order: " +
 2154                "Logging (1), FluentValidation (2), Caching (3), Transaction (4)");
 2155        }
 156
 20157        return messages;
 20158    }
 159
 160    /// <summary>
 161    /// Orders a list of behavior instances by their configured execution order.
 162    /// </summary>
 163    /// <param name="behaviors">Unordered behavior instances.</param>
 164    /// <returns>Ordered behaviors.</returns>
 165    public IEnumerable<object> OrderBehaviors(IEnumerable<object> behaviors)
 8166        => behaviors.OrderBy(b =>
 8167        {
 8168            var type = b.GetType().IsGenericType
 8169                ? b.GetType().GetGenericTypeDefinition()
 8170                : b.GetType();
 8171            return _behaviorMap.TryGetValue(type, out var reg)
 8172                ? reg.Order
 8173                : int.MaxValue;
 16174        });
 175
 176    /// <summary>
 177    /// Checks if an open-generic behavior type is registered.
 178    /// </summary>
 179    public bool HasBehavior(Type behaviorType)
 18180    {
 18181        if (behaviorType == null)
 0182            throw new ArgumentNullException(nameof(behaviorType));
 18183        return _behaviorMap.ContainsKey(behaviorType);
 18184    }
 185
 186    private bool ImplementsPipelineBehavior(Type type)
 13187        => type.GetInterfaces()
 25188               .Any(i => i.IsGenericType &&
 25189                         i.GetGenericTypeDefinition() == typeof(INexPipelineBehavior<,>));
 190
 191    private bool HasBehaviorOutOfOrder(Type behaviorType, int expectedOrder)
 78192        => _behaviorMap.TryGetValue(behaviorType, out var reg)
 78193           && reg.Order != expectedOrder;
 194
 28195    private record BehaviorRegistration(Type BehaviorType, int Order);
 196}