Resolving services with named parameters - autofac

I have the issue that resolving services registered with named parameters lead to wrong resolution:
The registration is like that:
builder.RegisterType<CviStaticCacheManager>()
.As<ICacheManager>()
.Named<ICacheManager>(STATIC_CACHE)
.SingleInstance();
builder.RegisterType<CviRequestCacheManager>()
.As<ICacheManager>()
.Named<ICacheManager>(REQUEST_CACHE)
.InstancePerLifetimeScope();
Everything's working fine if I resolve a service which is registered like that:
builder.RegisterType<PermissionService>().As<IPermissionService>()
.WithParameter(ResolvedParameter.ForNamed<ICacheManager>(STATIC_CACHE))
.InstancePerLifetimeScope();
My problem is that I have some registration which I can't modify that resolves services like that (I can't use ResolveKeyed/ResolveNamed!):
var cm = AutofacDependencyResolver.Current.RequestLifetimeScope.Resolve<ICacheManager>()
Now I always get an instance of CviStaticCacheManager - doesn't matter in which order I might register it.
I tried to fix this issue in a module - but then I get always CviRequestCacheManager:
protected override void AttachToComponentRegistration(IComponentRegistry registry, IComponentRegistration registration)
{
registration.Preparing += OnComponentPreparing;
}
private static void OnComponentPreparing(object sender, PreparingEventArgs e)
{
e.Parameters = e.Parameters.Union(
new[]
{
new ResolvedParameter(
(p, i) => p.ParameterType == typeof (ICacheManager),
(p, i) => i.ResolveKeyed<ICacheManager>(REQUEST_CACHE))
});
}
I'm really frustrated and I'm looking for a solution. Maybe somebody can help me.
Best Regards
Jörg

after commenting
//app.UseAutofacMiddleware(container);
//app.UseAutofacMvc();
it suddenly works.
I don't need owin integration at the moment - but I really have no idea why the order of registered components seems to be affected if I enable calling app.UseAutofacMvc();
Regards
Jörg

Related

How does the logging module for Autofac and NLog work?

I am still fairly new to Autofac and Nlog and I need some help in understanding what is taking place in my Autofac LoggingModule for Nlog. It works as expected thanks to following the injecting-nlog-with-autofacs-registergeneric. But rather than just copy paste, I would like to make sure I understand what is occurring in each method (Load & AttachToComponentRegistration). If you could review my thoughts and further clarify anything I have incorrect (quite a bit I am sure), I would greatly appreciate it. Thank you in advance!
Database Target using Nlog
Dependency Injection using Autofac
ASP.NET MVC web app for learning
Dvd Libary app (DvdAdd, DvdEdit, DvdDelete, DvdList)
LoggingModule
public class LoggingModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder
.Register((c, p) => new LogService(p.TypedAs<Type>()))
.AsImplementedInterfaces();
}
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
{
registration.Preparing +=
(sender, args) =>
{
var forType = args.Component.Activator.LimitType;
var logParameter = new ResolvedParameter(
(p, c) => p.ParameterType == typeof(ILog),
(p, c) => c.Resolve<ILog>(TypedParameter.From(forType)));
args.Parameters = args.Parameters.Union(new[] { logParameter });
};
}
}
My understanding of the code within Load()
c - The parameter c, provided to the expression, is the component context(an IComponentContext object) in which the component is being created. The context in which a service can be accessed or a component's dependencies resolved.
p - An IEnumerable with the incoming parameter set
AsImplementedInterfaces - Autofac allows its users to register the types explicitly or implicitly. While "As" is used for explicit registrations, "AsImplementedInterfaces" and "AsSelf" are used for implicit ones. In other words, the container automatically registers the implementation against all the interfaces it implements.
Thoughts: The Load method code registers a new LogService class (which represents "c") with the type of logger (which represents "p") as the constructor parameter for the LogService class
Questions:
Are my thoughts above correct?
Should it be SingleInstance or should it / will it only live as long as the calling classes scope? (I am thinking about my Unit Of Work)
My understanding of the code within AttachToComponentRegistration()
AttachToComponentRegistration method - Override to attach module-specific functionality to a component registration.
AttachToComponentRegistration Parameters:
IComponentRegistry componentRegistry - Provides component registrations according to the services they provide.
IComponentRegistration registration - Describes a logical component within the container.
registration.Preparing - Fired when a new instance is required. The instance can be provided in order to skip the regular activator, by setting the Instance property in the provided event arguments.
var forType = args.Component.Activator.LimitType;
args = Autofac.Core.PreparingEventArgs - Fired before the activation process to allow parameters to be changed or an alternative instance to be provided.
Component = PreparingEventArgs.Component Property - Gets the component providing the instance being activated
Activator = IComponentRegistration.Activator Property - Gets the activator used to create instances.
LimitType = IInstanceActivator.LimitType Property - Gets the most specific type that the component instances are known to be castable to.
Thoughts on forType - As I understand it, this variable holds the Name and FullName of the calling class from where the logging service is being called?
forType Debugger Image
Questions:
Are my thoughts forType correct?
var logParameter = new ResolvedParameter(
(p, c) => p.ParameterType == typeof(ILog),
(p, c) => c.Resolve<ILog>(TypedParameter.From(forType)));
ResolvedParameter - can be used as a way to supply values dynamically retrieved from the container,
e.g. by resolving a service by name.
Thoughts on logParameter - This is where I start to get lost. So does, it check that the Parameter is of Type ILog and if so it will then resolve it with the constructor parameter and pass in forType variable?
Questions:
Are my thoughts on logParameter above correct?
args.Parameters = args.Parameters.Union(new[] { logParameter });
args.Parameters = PreparingEventArgs.Parameters Property - Gets or sets the parameters supplied to the activator.
args.Parameters.Union = Produces the set union of two sequences by using the default equality comparer. Returns an System.Collections.Generic.IEnumerable`1 that contains the elements from both input sequences, excluding duplicates.
Thoughts on args.Parameters - I really do not know at this point other than to guess that it returns a collection of Parameters and removes duplicates?
Questions:
Could you help talk me through what is going on in args.Parameters?
logParameter Debugger Image
Nlog Database Table Image
LogService class
public class LogService : ILog
{
private readonly ILogger _log;
public LogService(Type type)
{
_log = LogManager.GetLogger(type.FullName);
}
public void Debug(string message, params object[] args)
{
Log(LogLevel.Debug, message, args);
}
public void Info(string message, params object[] args)
{
Log(LogLevel.Info, message, args);
}
public void Warn(string message, params object[] args)
{
Log(LogLevel.Warn, message, args);
}
public void Error(string message, params object[] args)
{
Log(LogLevel.Error, message, args);
}
public void Error(Exception ex)
{
Log(LogLevel.Error, null, null, ex);
}
public void Error(Exception ex, string message, params object[] args)
{
Log(LogLevel.Error, message, args, ex);
}
public void Fatal(Exception ex, string message, params object[] args)
{
Log(LogLevel.Fatal, message, args, ex);
}
private void Log(LogLevel level, string message, object[] args)
{
_log.Log(typeof(LogService), new LogEventInfo(level, _log.Name, null, message, args));
}
private void Log(LogLevel level, string message, object[] args, Exception ex)
{
_log.Log(typeof(LogService), new LogEventInfo(level, _log.Name, null, message, args, ex));
}
}
ILog interface
public interface ILog
{
void Debug(string message, params object[] args);
void Info(string message, params object[] args);
void Warn(string message, params object[] args);
void Error(string message, params object[] args);
void Error(Exception ex);
void Error(Exception ex, string message, params object[] args);
void Fatal(Exception ex, string message, params object[] args);
}
There's a lot to unpack here. You're not really asking for an answer to a specific question so much as a code walkthrough and explanation of an existing solution that works, so I might suggest posting to StackExchange Code Review if you need much more than what I'm going to give you here. Not trying to be unhelpful, but, like, if your question is, "Is my thinking right?" and the answer is "sort of," there's a lot of discussion on each individual point to explain why "sort of" is the answer (or "no," or "yes," as the case may be). It can turn into a lengthy answer, followed up by additional questions for clarification, which require yet additional answers... and StackOverflow isn't really a discussion forum capable of those sorts of things.
[i.e., I'll take probably an hour and write up an answer here... but I can't promise I'll actually be back to follow up on anything because there are other questions to answer and other things I need to allocate time to. StackOverflow is really more about "How do I...?" or other things that have a single, reasonably concrete answer.]
First, I recommend diving in yourself with a debugger on some breakpoints to actually see what's going on. For example, you asked what's in LimitType in one area - you could pretty easily answer that one by just sticking a breakpoint on that line and looking at the value. This will be a good way to follow up for additional clarification yourself - breakpoints for the win.
Second, I recommend spending some time with the Autofac docs. There's a lot of documentation out there that can answer questions.
The NLog module here appears to be based on the log4net module in the documentation which has a bit more explanation of what's going on.
There's an explanation of parameters (like TypedParameter) and how they're used.
Given the docs can round out some things that may not be clear, rather than try to address each "are my thoughts correct" item, let me just heavily annotate the module and hope that clarifies things.
// General module documentation is here:
// https://autofac.readthedocs.io/en/latest/configuration/modules.html
public class LoggingModule : Module
{
// Load basically registers types with the container just like
// if you were doing it yourself on the ContainerBuilder. It's
// just a nice way of packaging up a set of registrations so
// they're not all in your program's "Main" method or whatever.
protected override void Load(ContainerBuilder builder)
{
// This is a lambda registration. Docs here:
// https://autofac.readthedocs.io/en/latest/register/registration.html#lambda-expression-components
// This one uses both the component context (c) and the incoming
// set of parameters (p). In this lambda, the parameters are NOT the set of constructor
// parameters that Autofac has resolved - they're ONLY things that
// were MANUALLY specified. In this case, it's assuming a TypedParameter
// with a System.Type value is being provided manually. It's not going
// to try resolving that value from the container. This is going hand-in-hand
// with the logParameter you see in AttachToComponentRegistration.
// Parameter docs are here:
// https://autofac.readthedocs.io/en/latest/resolve/parameters.html
// In general if you resolve something that has both manually specified parameters
// and things that can be resolved by Autofac, the manually specified parameters
// will take precedence. However, in this lambda it's very specifically looking
// for a manually specified parameter.
// You'll want to keep this as a default InstancePerDependency because you probably
// want this to live as long as the thing using it and no longer. Likely
// NLog already has object pooling and caching built in so this isn't as
// expensive as you think, but I'm no NLog expert. log4net does handle
// that for you.
builder
.Register((c, p) => new LogService(p.TypedAs<Type>()))
.AsImplementedInterfaces();
}
// This method attaches a behavior (in this case, an event handler) to every
// component registered in the container. Think of it as a way to run a sort
// of "global foreach" over everything registered.
protected override void AttachToComponentRegistration(
IComponentRegistry componentRegistry,
IComponentRegistration registration)
{
// The Preparing event is called any time a new instance is needed. There
// are docs for the lifetime events but Preparing isn't on there. Here are the
// docs and the issue I filed on your behalf to get Preparing documented.
// https://autofac.readthedocs.io/en/latest/lifetime/events.html
// https://github.com/autofac/Documentation/issues/69
// You can see the Preparing event here:
// https://github.com/autofac/Autofac/blob/6dde84e5b0a3f82136a0567a84da498b04e1fa2d/src/Autofac/Core/IComponentRegistration.cs#L83
// and the event args here:
// https://github.com/autofac/Autofac/blob/6dde84e5b0/src/Autofac/Core/PreparingEventArgs.cs
registration.Preparing +=
(sender, args) =>
{
// The Component is the thing being resolved - the thing that
// needs a LogService injected. The Component.Activator is the
// thing that is actually going to execute to "new up" an instance
// of the Component. The Component.Activator.LimitType is the actual
// System.Type of the thing being resolved.
var forType = args.Component.Activator.LimitType;
// The docs above explain ResolvedParameter - basically a manually
// passed in parameter that can execute some logic to determine if
// it satisfies a constructor or property dependency. The point of
// this particular parameter is to provide an ILog to anything being
// resolved that happens to have an ILog constructor parameter.
var logParameter = new ResolvedParameter(
// p is the System.Reflection.ParameterInfo that describes the
// constructor parameter that needs injecting. c is the IComponentContext
// in which the resolution is being done (not used here). If this
// method evaluates to true then this parameter will be used; if not,
// it will refuse to provide a value. In this case, if the parameter
// being injected is an ILog, this ResolvedParameter will tell Autofac
// it can provide a value.
(p, c) => p.ParameterType == typeof(ILog),
// p and c are the same here, but this time they're used to actually
// generate the value of the parameter - the ILog instance that should
// be injected. Again, this will only run if the above predicate evaluates
// to true. This creates an ILog by manually resolving from the same
// component context (the same lifetime scope) as the thing that
// needs the ILog. Remember earlier that call to p.AsTyped<Type>()
// to get a parameter? The TypedParameter thing here is how that
// value gets poked in up there. This Resolve call will effectively
// end up calling the lambda registration.
(p, c) => c.Resolve<ILog>(TypedParameter.From(forType)));
// The thing being resolved (the component that consumes ILog) now
// needs to be told to make use of the log parameter, so add it into
// the list of parameters that can be used when resolving that thing.
// If there's an ILog, Autofac will use this specified parameter to
// fulfill the requirement.
args.Parameters = args.Parameters.Union(new[] { logParameter });
};
}
}
Something missing from this that's present in the log4net module example is the ability to do property injection for the logger. However, I'm not going to solve that here; you can look at the example right in the documentation and take that as an exercise to work on if you need that functionality.
I hope that helps. I'll probably not be coming back to follow up on additional questions, so if this isn't enough, I very, very much recommend setting some breakpoints, maybe setting up some tiny minimal-repro unit tests, that sort of thing, and do some deeper exploration to get clarity. Honestly, it's one thing to have someone else explain it, but it's another to actually see it in action and dive into the source of various projects. You'll come out with a fuller understanding with the latter approach, even if it's potentially not as fast.

What is the proper way to resolve a reference to a service fabric stateless service?

I have been developing a system which is heavily using stateless service fabric services. I thought I had a good idea how things worked however I am doing something slightly different now and it is obvious my understanding is limited.
I have a cluster of 5 nodes and all my services have an instance count of -1 for simplicity currently. With everything on every node it means I can watch a single node for basic behavior correctness.
I just added a new service which needs an instance count of 1. However it seems I am unable to resolve this service correctly. Instead SF tries to resolve the service on each machine which fails for all machines except the one where the single service exists.
It was my assumption that SF would automagically resolve a reference to a service anywhere in the cluster. If that reference fails then it would automagically resolve a new reference and so on. It appears that this is not correct at least for the way I am currently doing things.
I can find an instance using code similar to this however what happens if that instance fails. How do I get another reference?
I can resolve for every call like this however that seems like a terrible idea when I really only want to resolve a IXyzService and pass that along.
This is how I am resolving services since I am using the V2 custom serialization.
var _proxyFactory = new ServiceProxyFactory(c =>
{
return new FabricTransportServiceRemotingClientFactory(
serializationProvider: new CustomRemotingSerializationProvider(Logger)
);
});
var location = new Uri("fabric:/xyz/abcService");
var proxy = _proxyFactory.CreateServiceProxy<TService>(location);
This does actually work however it appears to only resolve a service on the same machine. So ServiceA would resolve a reference to ServiceB on the same machine. However if ServiceB doesn't exist on the machine for a valid reason then the resolution would fail.
Summary:
What is the correct way for ServiceA to use the V2 custom serialization ServiceProxyFactory to resolve an interface reference to ServiceB wherever ServiceA and ServiceB are in the cluster?
Update:
The evidence it doesn't work is the call to resolve hangs forever. According to this link that is correct behavior because the service will eventually come up. However only 1 node ever resolved it correctly and that is the node where the single instance is active. I have tried several things even waiting 30 seconds just to make sure it wasn't an init issue.
var proxy = _proxyFactory.CreateServiceProxy<TService>(location);
// Never gets here except on the one node.
SomethingElse(proxy);
Listener code
This essentially follows the V2 custom serialization tutorial almost exactly.
var listeners = new[]
{
new ServiceInstanceListener((c) =>
{
return new FabricTransportServiceRemotingListener(c, this, null,
new CustomRemotingSerializationProvider(Logger));
})
};
public class HyperspaceRemotingSerializationProvider : IServiceRemotingMessageSerializationProvider
{
#region Private Variables
private readonly ILogger _logger;
private readonly Action<RequestInfo> _requestAction;
private readonly Action<RequestInfo> _responseAction;
#endregion Private Variables
public CustomRemotingSerializationProvider(ILogger logger, Action<RequestInfo> requestAction = null, Action<RequestInfo> responseAction = null)
{
_logger = logger;
_requestAction = requestAction;
_responseAction = responseAction;
}
public IServiceRemotingRequestMessageBodySerializer CreateRequestMessageSerializer(Type serviceInterfaceType, IEnumerable<Type> requestWrappedTypes,
IEnumerable<Type> requestBodyTypes = null)
{
return new RequestMessageBodySerializer(_requestAction);
}
public IServiceRemotingResponseMessageBodySerializer CreateResponseMessageSerializer(Type serviceInterfaceType, IEnumerable<Type> responseWrappedTypes,
IEnumerable<Type> responseBodyTypes = null)
{
return new ResponseMessageBodySerializer(_responseAction);
}
public IServiceRemotingMessageBodyFactory CreateMessageBodyFactory()
{
return new MessageBodyFactory();
}
}
Connection code
_proxyFactory = new ServiceProxyFactory(c =>
{
return new FabricTransportServiceRemotingClientFactory(
serializationProvider: new CustomRemotingSerializationProvider(Logger)
);
});
// Hangs here - tried different partition keys or not specifying one.
var proxy = _proxyFactory.CreateServiceProxy<TService>(location, ServicePartitionKey.Singleton);

karaf osgi getServiceReference returns null

Does anyone have any experience with getServiceReference returning null for what seems like no reason?
The following bundle registers the service, and then proceeds to confirm that it's registered (whether or not this is even a valid test from the same package, idk).
package db.connector;
...
public class Activator implements BundleActivator {
private static ServiceRegistration registration;
...
public void start(BundleContext _context) throws Exception {
DatabaseConnector dbc = new DatabaseConnectorImpl();
registration = context.registerService(
DatabaseConnector.class.getName(),
dbc, null);
checkServiceRegistered();
}
...
public void checkServiceRegistered() {
System.out.println("Printing all entries:");
ServiceReference sr = context.getServiceReference(DatabaseConnector.class.getName());
DatabaseConnector dbc = (DatabaseConnector) context.getService(sr);
List<Protocol> result = dbc.getAllProtocols();
for(int i=0; i<result.size(); i++) {
Protocol p = result.get(i);
System.out.println("\t" + p.getId()+": "+p.getName()+"("+p.getOwner()+")");
}
}
}
The output runs successfully, everything seems OK. Checking in the karaf webconsole, the service seems to be registered correctly:
267 [db.connector.DatabaseConnector] database-connector (144)
The code to get the registered service is as follows:
import db.connector.DatabaseConnector;
...
public List<Protocol> printAllEntries() {
ServiceReference sr = Activator.getContext().getServiceReference(DatabaseConnector.class.getName());
DatabaseConnector dbc = (DatabaseConnector) Activator.getContext().getService(sr);
return dbc.getAllProtocols();
}
...
The DatabaseConnector bundle exports the correct package, and the one using the service imports the same.
What could possibly be going wrong here? I'm at a complete loss.
It looks alright.
What comes to mind: Is the ordering ok? Are you sure the registration is done before checking the reference? The way you check in printAllEntries you check if the service is there on just that moment. As OSGi bundles can come and go, this isn't a reliable way to check. You should use either a ServiceTracker, or better still something like Declarative Services or Blueprint.
You could add a ServiceListener to the BundleContext, then you can print out what's happening in what order.
Hope this helps.
Turns out, it was just that I didn't refresh the OSGi bundles. My servlet was pointing to a now-obsolete bundle ID, so of course the service find was failing.

autofac registration issue in release v2.4.5.724

I have the following registration
builder.Register<Func<Type, IRequestHandler>>(
c => request => (IRequestHandler)c.Resolve(request));
Basically I am trying to register a factory method that resolves an instance of IRequestHandler from a given type.
This works fine until the version 2.4.3.700. But now I am getting a the following error..
Cannot access a disposed object.
Object name: 'This resolve operation has already ended. When
registering components using lambdas,
the IComponentContext 'c' parameter to
the lambda cannot be stored. Instead,
either resolve IComponentContext again
from 'c', or resolve a Func<> based
factory to create subsequent
components from.'.
UPDATE
I was trying to limit autofac's exposure to the rest of the projects in the solution. Nick, thanks for the hint, now my registration looks like this...
builder.Register<Func<Type,IRequestHandler>>(c =>
{
var handlers = c.Resolve<IIndex<Type,RequestHandler>>();
return request => handlers[request];
});
The c in this expression is a temporary, so this code while previously functional, is broken. Autofac 2.4.5 detects this problem while earlier versions silently ignored it.
To fix the issue, explicitly resolve IComponentContext:
builder.Register<Func<Type, IRequestHandler>>(c => {
var ctx = c.Resolve<IComponentContext>();
return request => (IRequestHandler)ctx.Resolve(request));
});
The functionality you're emulating here might be better represented using keys and indexes, e.g. see Interrupted chain of IoC or http://code.google.com/p/autofac/wiki/TypedNamedAndKeyedServices.
I had a similar problem as the user6130. I wanted to avoid using IIndex in my class implementation and pass in a service resolver into my constructor instead.
So now I have my service implementation with the following constructor:
public MvcMailer(Converter<string, MailerBase> mailerResolver)
{
_resolver = mailerResolver;
}
I wanted to used keyed services without directly relying on the Autofac namespace. I was getting the same error until I restructured the configuration as such.
1) Scan for all my mailer implementations and index via class name (could be improved)
builder.RegisterAssemblyTypes(System.Reflection.Assembly.GetExecutingAssembly())
.Where(t => t.Name.EndsWith("Mailer")).Keyed<Mvc.Mailer.MailerBase>(t => t.Name.Replace("Mailer", "").ToLower());
2) Register the converter in Autofac config
builder.Register<System.Converter<string,Mvc.Mailer.MailerBase>>(c => {
var all = c.Resolve<Autofac.Features.Indexed.IIndex<string,Mvc.Mailer.MailerBase>>();
return delegate(string key)
{
return all[key];
};
});
3) Register like other types of components and let Autofac handle the Converter injection
builder.RegisterType<Mailers.MvcMailer>().As<Mailers.IMailer>();

Resolving a collection of services from a service type

I have a rather complex bit of resolving going on in Autofac. Basically I want all the objects in the container which implement a specifically named method with a specific argument type. I have implemented some somewhat insane code to get it for me
var services = (from registrations in _componentContext.ComponentRegistry.Registrations
from service in registrations.Services
select service).Distinct();
foreach (var service in services.OfType<Autofac.Core.TypedService>())
{
foreach (var method in service.ServiceType.GetMethods().Where(m => m.Name == "Handle"
&& m.GetParameters().Where(p => p.ParameterType.IsAssignableFrom(implementedInterface)).Count() > 0))
{
var handler = _componentContext.Resolve(service.ServiceType);
method.Invoke(handler, new Object[] { convertedMessage });
}
}
My problem arises in that the handler returned the the resolution step is always the same handler and I cannot see a way to resolve a collection of the the registrations which are tied to the service as one might normally do with container.Resolve>().
I feel like I'm pushing pretty hard against what AutoFac was designed to do and might do better with a MEF based solution. Is there an easy AutoFac based solution to this issue or should I hop over to a more composition based approach?
G'day,
In MEF you could use 'Method Exports' for this (http://mef.codeplex.com/wikipage?title=Declaring%20Exports) but that might be a bit drastic. There are a couple of ways to achieve what you want in Autofac.
You can make the above code work by searching for registrations rather than services:
var implementorMethods = _componentContext.ComponentRegistry.Registrations
.Select(r => new {
Registration = r,
HandlerMethod = r.Services.OfType<TypedService>()
.SelectMany(ts => ts.ServiceType.GetMethods()
.Where(m => m.Name == "Handle" && ...))
.FirstOrDefault()
})
.Where(im => im.HandlerMethod != null);
foreach (var im in implementorMethods)
{
var handler = _componentContext.ResolveComponent(im.Registration, new List<Parameter>());
im.HandlerMethod.Invoke(handler, new object[] { convertedMessage });
}
I.e. implementorMethods is a list of the components implementing a handler method, along with the method itself. ResolveComponent() doesn't rely on a service to identify the implementation, so there's no problem with the service not uniquely identifying a particular implementor.
This technique in general will probably perform poorly (if perf is a concern here) but also as you suspect will work against the design goals of Autofac (and MEF,) eliminating some of the benefits.
Ideally you need to define a contract for handlers that will let you look up all handlers for a message type in a single operation.
The typical recipe looks like:
interface IHandler<TMessage>
{
void Handle(TMessage message);
}
Handlers then implement the appropriate interface:
class FooHandler : IHandler<Foo> { ... }
...and get registered at build-time like so:
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(typeof(FooHandler).Assembly)
.AsClosedTypesOf(typeof(IHandler<>));
To invoke the handlers, define a message dispatcher contract:
interface IMessageDispatcher
{
void Dispatch(object message);
}
...and then its implementation:
class AutofacMessageDispatcher : IMessageDispatcher
{
static readonly MethodInfo GenericDispatchMethod =
typeof(AutofacMessageDispatcher).GetMethod(
"GenericDispatch", BindingFlags.NonPublic | BindingFlags.Instance);
IComponentContext _cc;
public AutofacMessageDispatcher(IComponentContext cc)
{
_cc = cc;
}
public void Dispatch(object message)
{
var dispatchMethod = GenericDispatchMethod
.MakeGenericMethod(message.GetType());
dispatchMethod.Invoke(this, new[] { message });
}
void GenericDispatch<TMessage>(TMessage message)
{
var handlers = _cc.Resolve<IEnumerable<IHandler<TMessage>>>();
foreach (var handler in handlers)
handler.Handle(message);
}
}
...which is registered like so:
builder.RegisterType<AutofacMessageDispatcher>()
.As<IMessageDispatcher>();
The component that feeds in the messages will then resolve/use IMessageDispatcher to get the messages to the handlers.
var dispatcher = _cc.Resolve<IMessageDispatcher>();
dispatcher.Dispatch(message);
There are still ways to do this without the interface, but all rely on creating some kind of contract that uniquely defines handlers for a particular message (e.g. a delegate.)
In the long run the generic handler pattern will be the easiest to maintain.
Hope this helps, Nick.