Skip to content

Commit

Permalink
Merge pull request #942 from kendallb/feature/map-namespace-of
Browse files Browse the repository at this point in the history
Add support for mapping all types under a specific namespace in an assembly
  • Loading branch information
mookid8000 committed Feb 21, 2021
2 parents 258e216 + b158497 commit b729dfa
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 3 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,21 @@ which will stuff the resulting `IBus` in the container as a singleton and use th
message handlers. Check out the Configuration section on [the official Rebus documentation wiki][REBUS_WIKI] for
more information on how to do this.

If you want to be more specific about what types you map in an assembly, such as if the assembly is shared with other code you can map all the types under a specific namespace like this:

```csharp
Configure.With(someContainerAdapter)
.(...)
.Routing(r => r.TypeBased().MapAssemblyNamespaceOf<SomeMessageType>("namespaceInputQueue"))
.(...);

// have IBus injected in application services for the duration of the application lifetime
// let the container dispose the bus when your application exits
myFavoriteIocContainer.Dispose();
```


License
====

Expand Down
83 changes: 81 additions & 2 deletions Rebus.Tests/Routing/TestTypeBasedRouter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void LogsWarningWhenRouteIsOverwritten()
}

[Test]
public void WorksWithMultipleRoutes ()
public void WorksWithMultipleRoutes()
{
_router
.Map<string>("StringDestination")
Expand All @@ -75,6 +75,55 @@ public void WorksWithMultipleRoutes ()
Assert.That(GetDestinationForBody(78), Is.EqualTo("IntDestination"));
}

[Test]
public void WorksWithAssemblyRouteMapping()
{
_router.MapAssemblyOf<TestNamespaceRouting.AssemblyMessageOne>("AssemblyDestination");

// All these should be mapped from the entire assembly (along with a ton more, but that's beside the point for this test)
Assert.That(GetDestinationForBody(new TestNamespaceRouting.AssemblyMessageOne()), Is.EqualTo("AssemblyDestination"));
Assert.That(GetDestinationForBody(new TestNamespaceRouting.AssemblyMessageTwo()), Is.EqualTo("AssemblyDestination"));
Assert.That(GetDestinationForBody(new TestNamespaceRouting.SubNamespace.AssemblyMessageSubNamespace()), Is.EqualTo("AssemblyDestination"));
Assert.That(GetDestinationForBody(new OtherNamespaceRouting.AssemblyMessageOtherNamespace()), Is.EqualTo("AssemblyDestination"));
}

[Test]
public void WorksWithAssemblyRouteMappingAndDerivedClasses()
{
_router.MapAssemblyDerivedFrom<TestNamespaceRouting.AssemblyMessageBaseClass>("AssemblyDestination");

// All these should be mapped from the entire assembly (along with a ton more, but that's beside the point for this test)
Assert.That(GetDestinationForBody(new TestNamespaceRouting.AssemblyMessageOne()), Is.EqualTo("AssemblyDestination"));
Assert.That(GetDestinationForBody(new TestNamespaceRouting.SubNamespace.AssemblyMessageSubNamespace()), Is.EqualTo("AssemblyDestination"));

// These ones should NOT be mapped
Assert.Throws<AggregateException>(() =>
{
GetDestinationForBody(new TestNamespaceRouting.AssemblyMessageTwo());
});
Assert.Throws<AggregateException>(() =>
{
GetDestinationForBody(new OtherNamespaceRouting.AssemblyMessageOtherNamespace());
});
}

[Test]
public void WorksWithNamespaceRouteMapping()
{
_router.MapAssemblyNamespaceOf<TestNamespaceRouting.AssemblyMessageOne>("AssemblyDestination");

// All these should be mapped from the namespace and sub-namespace
Assert.That(GetDestinationForBody(new TestNamespaceRouting.AssemblyMessageOne()), Is.EqualTo("AssemblyDestination"));
Assert.That(GetDestinationForBody(new TestNamespaceRouting.AssemblyMessageTwo()), Is.EqualTo("AssemblyDestination"));
Assert.That(GetDestinationForBody(new TestNamespaceRouting.SubNamespace.AssemblyMessageSubNamespace()), Is.EqualTo("AssemblyDestination"));

// This one should NOT be mapped
Assert.Throws<AggregateException>(() =>
{
GetDestinationForBody(new OtherNamespaceRouting.AssemblyMessageOtherNamespace());
});
}

[Test]
public void SupportsFallback()
{
Expand Down Expand Up @@ -109,4 +158,34 @@ string GetDestinationForBody(object messageBody)

static Dictionary<string, string> NoHeaders => new Dictionary<string, string>();
}
}
}

// Set up some types we can use to test namespace mapping
namespace Rebus.Tests.Routing.TestNamespaceRouting
{
class AssemblyMessageBaseClass
{
}

class AssemblyMessageOne : AssemblyMessageBaseClass
{
}

class AssemblyMessageTwo
{
}

namespace SubNamespace
{
class AssemblyMessageSubNamespace : AssemblyMessageBaseClass
{
}
}
}

namespace Rebus.Tests.Routing.OtherNamespaceRouting
{
class AssemblyMessageOtherNamespace
{
}
}
84 changes: 83 additions & 1 deletion Rebus/Routing/TypeBased/TypeBasedRouter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Rebus.Logging;
Expand Down Expand Up @@ -41,13 +42,94 @@ public TypeBasedRouter MapAssemblyOf<TMessage>(string destinationAddress)
/// </summary>
public TypeBasedRouter MapAssemblyOf(Type messageType, string destinationAddress)
{
foreach (var typeToMap in messageType.GetTypeInfo().Assembly.GetTypes())
foreach (var typeToMap in messageType.GetTypeInfo().Assembly.GetTypes().Where(t => t.IsClass))
{
SaveMapping(typeToMap, destinationAddress);
}
return this;
}

/// <summary>
/// Maps <paramref name="destinationAddress"/> as the owner of all message types found in the same assembly as <typeparamref name="TDerivedFrom"/>
/// and derived from <typeparamref name="TDerivedFrom"/>
/// </summary>
public TypeBasedRouter MapAssemblyDerivedFrom<TDerivedFrom>(string destinationAddress)
{
MapAssemblyDerivedFrom(typeof(TDerivedFrom), destinationAddress);
return this;
}

/// <summary>
/// Maps <paramref name="destinationAddress"/> as the owner of all message types found in the same assembly as <paramref name="derivedFrom"/>
/// and optionally derived from <paramref name="derivedFrom"/>
/// </summary>
public TypeBasedRouter MapAssemblyDerivedFrom(Type derivedFrom, string destinationAddress)
{
foreach (var typeToMap in derivedFrom.GetTypeInfo().Assembly.GetTypes().Where(t => t.IsClass))
{
if (derivedFrom == null || typeToMap != derivedFrom && derivedFrom.IsAssignableFrom(typeToMap))
{
SaveMapping(typeToMap, destinationAddress);
}
}
return this;
}

/// <summary>
/// Maps <paramref name="destinationAddress"/> as the owner of all message types found in the same assembly as <typeparamref name="TMessage"/> under
/// the namespace that type lives under. So all types within the same namespace will get mapped to that destination address, but not types under
/// other namespaces. This allows you to separate messages for specific queues by namespace and register them all in one go.
/// </summary>
public TypeBasedRouter MapAssemblyNamespaceOf<TMessage>(string destinationAddress)
{
MapAssemblyNamespaceOf(typeof(TMessage), destinationAddress);
return this;
}

/// <summary>
/// Maps <paramref name="destinationAddress"/> as the owner of all message types found in the same assembly as <paramref name="messageType"/> under
/// the namespace that type lives under. So all types within the same namespace will get mapped to that destination address, but not types under
/// other namespaces. This allows you to separate messages for specific queues by namespace and register them all in one go.
/// </summary>
public TypeBasedRouter MapAssemblyNamespaceOf(Type messageType, string destinationAddress)
{
foreach (var typeToMap in messageType.GetTypeInfo().Assembly.GetTypes().Where(t => t.IsClass && t.Namespace != null && t.Namespace.StartsWith(messageType.Namespace ?? string.Empty)))
{
SaveMapping(typeToMap, destinationAddress);
}
return this;
}

/// <summary>
/// Maps <paramref name="destinationAddress"/> as the owner of all message types found in the same assembly as <typeparamref name="TMessage"/> under
/// the namespace that type lives under and derived from <typeparamref name="TDerivedFrom"/>. So all types within the same namespace will
/// get mapped to that destination address, but not types under other namespaces. This allows you to separate messages for specific queues by
/// namespace and register them all in one go.
/// </summary>
public TypeBasedRouter MapAssemblyNamespaceOfDerivedFrom<TMessage, TDerivedFrom>(string destinationAddress)
{
MapAssemblyNamespaceOfDerivedFrom(typeof(TMessage), typeof(TDerivedFrom), destinationAddress);
return this;
}

/// <summary>
/// Maps <paramref name="destinationAddress"/> as the owner of all message types found in the same assembly as <paramref name="messageType"/> under
/// the namespace that type lives under and derived from <paramref name="derivedFrom"/>. So all types within the same namespace will
/// get mapped to that destination address, but not types under other namespaces. This allows you to separate messages for specific queues by
/// namespace and register them all in one go.
/// </summary>
public TypeBasedRouter MapAssemblyNamespaceOfDerivedFrom(Type messageType, Type derivedFrom, string destinationAddress)
{
foreach (var typeToMap in messageType.GetTypeInfo().Assembly.GetTypes().Where(t => t.IsClass && t.Namespace != null && t.Namespace.StartsWith(messageType.Namespace ?? string.Empty)))
{
if (derivedFrom == null || typeToMap != derivedFrom && derivedFrom.IsAssignableFrom(typeToMap))
{
SaveMapping(typeToMap, destinationAddress);
}
}
return this;
}

/// <summary>
/// Maps <paramref name="destinationAddress"/> as the owner of the <typeparamref name="TMessage"/> message type
/// </summary>
Expand Down
63 changes: 63 additions & 0 deletions Rebus/Routing/TypeBased/TypeBasedRouterConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,69 @@ public TypeBasedRouterConfigurationBuilder MapAssemblyOf<TMessage>(string destin
return this;
}

/// <summary>
/// Maps <paramref name="destinationAddress"/> as the owner of all message types found in the same assembly as <paramref name="messageType"/>
/// </summary>
public TypeBasedRouterConfigurationBuilder MapAssemblyOf(Type messageType, string destinationAddress)
{
_configurationActions.Add(r => r.MapAssemblyOf(messageType, destinationAddress));
return this;
}

/// <summary>
/// Maps <paramref name="destinationAddress"/> as the owner of all message types found in the same assembly as <typeparamref name="TDerivedFrom"/>
/// and derived from <typeparamref name="TDerivedFrom"/>
/// </summary>
public TypeBasedRouterConfigurationBuilder MapAssemblyDerivedFrom<TDerivedFrom>(string destinationAddress)
{
_configurationActions.Add(r => r.MapAssemblyDerivedFrom<TDerivedFrom>(destinationAddress));
return this;
}

/// <summary>
/// Maps <paramref name="destinationAddress"/> as the owner of all message types found in the same assembly as <typeparamref name="TMessage"/> under
/// the namespace that type lives under. So all types within the same namespace will get mapped to that destination address, but not types under
/// other namespaces. This allows you to separate messages for specific queues by namespace and register them all in one go.
/// </summary>
public TypeBasedRouterConfigurationBuilder MapAssemblyNamespaceOf<TMessage>(string destinationAddress)
{
_configurationActions.Add(r => r.MapAssemblyNamespaceOf<TMessage>(destinationAddress));
return this;
}

/// <summary>
/// Maps <paramref name="destinationAddress"/> as the owner of all message types found in the same assembly as <paramref name="messageType"/> under
/// the namespace that type lives under. So all types within the same namespace will get mapped to that destination address, but not types under
/// other namespaces. This allows you to separate messages for specific queues by namespace and register them all in one go.
/// </summary>
public TypeBasedRouterConfigurationBuilder MapAssemblyNamespaceOf(Type messageType, string destinationAddress)
{
_configurationActions.Add(r => r.MapAssemblyNamespaceOf(messageType, destinationAddress));
return this;
}

/// <summary>
/// Maps <paramref name="destinationAddress"/> as the owner of all message types found in the same assembly as <typeparamref name="TMessage"/> under
/// the namespace that type lives under. So all types within the same namespace will get mapped to that destination address, but not types under
/// other namespaces. This allows you to separate messages for specific queues by namespace and register them all in one go.
/// </summary>
public TypeBasedRouterConfigurationBuilder MapAssemblyNamespaceOfDerivedFrom<TMessage, TDerivedFrom>(string destinationAddress)
{
_configurationActions.Add(r => r.MapAssemblyNamespaceOfDerivedFrom<TMessage, TDerivedFrom>(destinationAddress));
return this;
}

/// <summary>
/// Maps <paramref name="destinationAddress"/> as the owner of all message types found in the same assembly as <paramref name="messageType"/> under
/// the namespace that type lives under. So all types within the same namespace will get mapped to that destination address, but not types under
/// other namespaces. This allows you to separate messages for specific queues by namespace and register them all in one go.
/// </summary>
public TypeBasedRouterConfigurationBuilder MapAssemblyNamespaceOfDerivedFrom(Type messageType, Type derivedFrom, string destinationAddress)
{
_configurationActions.Add(r => r.MapAssemblyNamespaceOfDerivedFrom(messageType, derivedFrom, destinationAddress));
return this;
}

/// <summary>
/// Maps <paramref name="destinationAddress"/> as a fallback destination to use when none of the configured mappings match
/// </summary>
Expand Down

0 comments on commit b729dfa

Please sign in to comment.