Ok, I figured out a very clean way to do this!
First of all we'll need an implementation of IHandlerSelector
, this can select a handler based on our opinion on the matter, or remain neutral (by returning null
, which means "no opinion").
/// <summary>
/// Emits an opinion about a component's lifestyle only if there are exactly two available handlers and one of them has a PerWebRequest lifestyle.
/// </summary>
public class LifestyleSelector : IHandlerSelector
{
public bool HasOpinionAbout(string key, Type service)
{
return service != typeof(object); // for some reason, Castle passes typeof(object) if the service type is null.
}
public IHandler SelectHandler(string key, Type service, IHandler[] handlers)
{
if (handlers.Length == 2 && handlers.Any(x => x.ComponentModel.LifestyleType == LifestyleType.PerWebRequest))
{
if (HttpContext.Current == null)
{
return handlers.Single(x => x.ComponentModel.LifestyleType != LifestyleType.PerWebRequest);
}
else
{
return handlers.Single(x => x.ComponentModel.LifestyleType == LifestyleType.PerWebRequest);
}
}
return null; // we don't have an opinion in this case.
}
}
I made it so the opinion is very limited on purpose. I'll be having an opinion only if there are exactly two handlers and one of them has PerWebRequest
lifestyle; meaning the other one is probably the non-HttpContext alternative.
We need to register this selector with Castle. I do so before I start registering any other components:
container.Kernel.AddHandlerSelector(new LifestyleSelector());
Lastly I wish I had any clue as to how I could copy my registration to avoid this:
container.Register(
AllTypes
.FromAssemblyContaining<EmailService>()
.Where(t => t.Name.EndsWith("Service"))
.WithService.Select(IoC.SelectByInterfaceConvention)
.LifestylePerWebRequest()
);
container.Register(
AllTypes
.FromAssemblyContaining<EmailService>()
.Where(t => t.Name.EndsWith("Service"))
.WithService.Select(IoC.SelectByInterfaceConvention)
.LifestylePerThread()
);
If you can figure out a way to clone a registration, change the lifestyle and register both of them (using either container.Register
or IRegistration.Register
), please post it as an answer here! :)
Update: In testing, I need to uniquely name the identical registrations, I did so like this:
.NamedRandomly()
public static ComponentRegistration<T> NamedRandomly<T>(this ComponentRegistration<T> registration) where T : class
{
string name = registration.Implementation.FullName;
string random = "{0}{{{1}}}".FormatWith(name, Guid.NewGuid());
return registration.Named(random);
}
public static BasedOnDescriptor NamedRandomly(this BasedOnDescriptor registration)
{
return registration.Configure(x => x.NamedRandomly());
}