samy
samy

Reputation: 14962

Resolving components by name with a default fallback if name doesn't exist in Castle

I have a directory with multiple subdirectories:

/Xml
  /Documents
  /Registrations
  /Stuff
  /...

Users can drop xml files in these subdirectories, and I have a reactive FileSystemWatcher catching the files after some inactive time (to ensure that the files are ready to process).

The "Documents" folder needs a different process than the other folders so I created two components inheriting the same interface:

public interface IXmlMessageHandler { void ProcessMessage(FileInformation fi); }

public class DocumentsXmlMessageHandler: IXmlMessageHandler { /* SNIP ... */ }

public class DefaultXmlMessageHandler: IXmlMessageHandler { /* SNIP ... */ }

I'm used to the typed factories in Castle v3.2 so I decided to use the same pattern I usually reach for when some behaviors need to be routed: register a catch-all component as the default, then provide additional components for specific behaviors (here is an exemple where the Null Object Pattern is discussed). The difference was that I would need to route by folder instead of by type, but typed factories allow that easily by letting you define the components names at resolution time. What I was thinking was creating components whose name could match the folder they're linked to, and the default one would pop up when a non matching folder would be found.

So I created a typed factory, the factory selector, and registered my components

public interface IXmlMessageHandlerFactory {
    IXmlMessageHandler RetrieveMessageHandler(FileInformation fi);
}

public class XmlHandlerFactorySelector : DefaultTypedFactoryComponentSelector {
    protected override string GetComponentName(System.Reflection.MethodInfo method, object[] arguments) {
        return (arguments[0] as FileInformation).FolderName.ToLower();
    }
}

Classes.FromAssemblyInThisApplication()
.BasedOn<IXmlMessageHandler>().WithService.AllInterfaces()
.Configure(c => {
    var name = c.Implementation.Name.Replace("XmlMessageHandler", string.Empty).ToLower();
    c.Named(name);
    if (name == "default")
    {
        c.IsDefault();
    }
})

When I call my typed factory with a FileInformation coming from the "Documents" directory, the component is resolved correctly. But when I call the typed factory with any other directory name, it fails because it cannot find the name, but it doesn't seem to take into account the fact that there is a default implementation that could match it.

Any way to manage a named resolution of the component with a fallback on the default component if the name is not found?

Upvotes: 0

Views: 1147

Answers (2)

samy
samy

Reputation: 14962

In the Breaking changes txt file for Castle V3 there is an explicit reference to this very problem:

change - Typed factory using DefaultTypedFactoryComponentSelector when resolving component by name will not fallback to resolving by type if component with that name can not be found and will throw an exception instead.

  • id - typedFactoryFallbackToResolveByTypeIfNameNotFound
  • impact - medium
  • fixability - easy

description - Original behavior from v2.5 could lead to bugs in cases when named component was not registered or the name was misspelleed and a wrong component would be picked leading to potentially severe issues in the application. New version adapts fail-fast approach in those cases to give dvelopers immediate feedback the configuration is wrong.

fix - Actual fix depends on which part of the behavior you want:

  • If you do care about the fallback behavior, that is get the component by name and if not present fallback to resolve by type, you can specify it explicitly when registering your factory: .AsFactory(new DefaultTypedFactoryComponentSelector(fallbackToResolveByTypeIfNameNotFound: true));

I don't really like that there is no alternate "official" way of doing this, since the property is marked as being present only for backward compatibility and its usage not being recommended, but at least it works.

If you know of a better way, I'd be very interested in hearing it :)

Upvotes: 2

Crixo
Crixo

Reputation: 3070

Whatup about a custom selector instead? Create a class that implements ITypedFactoryComponentSelector and link it to your typed factory.

ITypedFactoryComponentSelector gives more flexibilty: you have access to the container through the func as return parameter

Upvotes: 0

Related Questions