Sending messages back and forth is all fine and good, but one way or another, something has to actually handle and process those messages. Inside of FubuMVC, message handling is done with handler actions. Handler actions are nothing more than a public method on a public type that has a single input parameter for the message type that it processes:
public class SimpleHandler
{
public void Handle(PingMessage message)
{
Console.WriteLine("I got a ping!");
}
}
Note that there is absolutely no mandatory FubuMVC-specific interfaces or attributes or base types. One of FubuMVC's primary design objectives from the beginning was to eliminate direct coupling from your application code to FubuMVC itself. FubuMVC has also strived to reduce the amount of mandatory cruft in your application code like attributes, fluent interfaces, marker interfaces, and mandatory base classes that are so prevalent in many other frameworks in the .Net space.
See also Cascading Messages for other valid handler method signatures.
Asynchronous Handlers
If you're trying to be more efficient at runtime by taking advantage of asynchronous processing, you can make the signature
of your handler action methods return a Task
or Task<T>
if you're exposing a cascading message.
A sample, async handler method is shown below:
public interface IPongWriter
{
Task WritePong(PongMessage message);
}
public class AsyncHandler
{
private readonly IPongWriter _writer;
public AsyncHandler(IPongWriter writer)
{
_writer = writer;
}
public Task Handle(PongMessage message)
{
return _writer.WritePong(message);
}
}
Handler Dependencies
At runtime, the handler objects are created by the underlying StructureMap container for your application, meaning that you can inject service dependencies into your handler objects:
public interface IMyService
{
}
public class ServiceUsingHandler
{
private readonly IMyService _service;
// Using constructor injection to get dependencies
public ServiceUsingHandler(IMyService service)
{
_service = service;
}
public void Consume(PingMessage message)
{
// do stuff using IMyService with the PingMessage
// input
}
}
The handler objects are built by a nested container scoped to the current message.
See Integration with StructureMap for IoC for more information.
How FubuMVC Finds Handlers
FubuMVC uses StructureMap 4.0's type scanning support to find handler classes and candidate methods from known assemblies based on naming conventions.
By default, FubuMVC is looking for public classes in the main application assembly with names matching these rules:
- Type name ends with "Handler"
- Type name ends with "Consumer"
- Type closes the open generic type
IStatefulSaga<T>
for classes implementing sagas
From the types, FubuMVC looks for any public instance method that accepts a single parameter that is assumed to be the message type.
Customize Handler Discovery
The easiest way to use the FubuMVC service bus functionality is to just code against the default conventions. However, if you wish to deviate from those naming conventions you can either supplement the handler discovery or replace it completely with your own conventions.
At a minimum, you can disable the built in discovery, add a new handler source through the FindBy()
methods, or register specific
handler classes with the code below:
public class CustomHandlerApp : FubuTransportRegistry<AppSettings>
{
public CustomHandlerApp()
{
// Turn off the default handler conventions
// altogether
Handlers.DisableDefaultHandlerSource();
// Custom handler finding through common options
Handlers.FindBy(source =>
{
source.UseThisAssembly();
source.IncludeClassesSuffixedWithHandler();
});
// Include candidate methods from a specific class or
// classes
Handlers.Include(typeof(SimpleHandler), typeof(AsyncHandler));
// Include a specific handler class with a generic argument
Handlers.Include<SimpleHandler>();
}
}
If you want to build your own custom handler source, the easiest thing to do is to subclass the HandlerSource
class from
FubuMVC and configure it in its constructor function like so:
public class MyHandlerSource : HandlerSource
{
public MyHandlerSource()
{
// Search through other assemblies
UseAssembly(typeof(PingMessage).GetTypeInfo().Assembly);
// And the application assembly
UseThisAssembly();
// Use a custom filter to determine handler types
IncludeTypes(type => type.Closes(typeof(IHandler<>)));
// Or look for types with a known suffix
IncludeClassesSuffixedWith("Handler");
// Or use some built in filters
IncludeClassesSuffixedWithConsumer();
}
}
If you want to go off the beaten path and do some kind of special handler discovery, you can directly implement
the IHandlerSource
interface in your own code.
Let's say that you're converting an application to FubuMVC that previously used a service bus that exposed message handling through
an interface like IHandler
shown below:
public interface IHandler<T>
{
void Handle(T message);
}
An implementation of a custom action source that can discover IHandler<T>
classes and methods may look like the following:
public class MyCustomHandlerSource : IHandlerSource
{
public async Task<HandlerCall[]> FindCalls(Assembly applicationAssembly)
{
var types = await TypeRepository
.FindTypes(applicationAssembly, TypeClassification.Concretes | TypeClassification.Closed);
var candidates = types.Where(x => x.Closes(typeof(IHandler<>)));
return candidates
.SelectMany(findCallsPerType)
.ToArray();
}
private static IEnumerable<HandlerCall> findCallsPerType(Type type)
{
return type
.GetMethods()
.Where(m => m.Name == nameof(IHandler<object>.Handle))
.Select(m => new HandlerCall(type, m));
}
}
Finally, you can direct FubuMVC to use your custom handler sources with code like this inside of your FubuTransportRegistry
class representing your application:
public class AppWithCustomHandlerSources : FubuTransportRegistry<AppSettings>
{
public AppWithCustomHandlerSources()
{
// Add a source by type
Handlers.FindBy<MyHandlerSource>();
// Add a source object directly
Handlers.FindBy(new MyCustomHandlerSource());
}
}
Subclass or Interface Handlers
FubuMVC will allow you to use handler methods that work against interfaces or abstract types to apply or reuse
generic functionality across messages. Let's say that some subset of your messages implement some kind of
IMessage
interface like this one and an implentation of it below:
public interface IMessage { }
public class MessageOne : IMessage { }
You can handle the MessageOne
specifically with a handler action like this:
public class SpecificMessageHandler
{
public void Consume(MessageOne message)
{
}
}
You can also create a handler for IMessage
like this one:
public class GenericMessageHandler
{
private readonly Envelope _envelope;
public GenericMessageHandler(Envelope envelope)
{
_envelope = envelope;
}
public void Consume(IMessage message)
{
Console.WriteLine($"Got a message from {_envelope.Source}");
}
}
When FubuMVC handles the MessageOne
message, it first calls all the specific handlers for that message type,
then will call any handlers that handle a more generic message type (interface or abstract class most likely) where
the specific type can be cast to the generic type. You can clearly see this behavior by examining the handler chain diagnostics.