Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for mapping all types under a specific namespace in an assembly #942

Merged
merged 4 commits into from
Feb 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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