Найдено решение, хотя у меня было две проблемы.
Первая проблема связана с тем, как объект был зарегистрирован/сконфигурирован с ядром Ninject в методе IContainer.Configure
моего класса NinjectObjectBuilder
. Изучив существующие реализации nServiceBus's ObjectBuilder
с использованием других контейнеров IoC, я отметил, что общий подход к регистрации заключался в регистрации самого конкретного типа, а также всех интерфейсов, которые были реализованы. В Ninject это означает «привязку конкретного типа к себе», а затем привязывает каждый интерфейс, который также реализует тип. Это было довольно просто, за исключением того, что я нашел после профилирования с dotTrace, было то, что в случае активации Singleton не показалось, что я действительно получаю ссылки на Singleton. Фактически, произойдет то, что я получаю новый объект в зависимости от типа обслуживания. Например, бетонный тип UnicastBus
реализует IBus
, а также IStartableBus
и зарегистрирован в области Singleton. nServiceBus ожидает получить тот же объект, если запрашивается IBus
или IStartableBus
, если они являются одноточечными и обе связаны с одной и той же реализацией. Интерпретация Singleton Ninject по отношению к службе или интерфейсу - другими словами, вы получаете один и тот же экземпляр UnicastBus
каждый раз, когда вы запрашиваете IBus
; однако вы получаете новый, отличный UnicastBus
для запроса на IStartableBus
. Так я решил это реализовать метод IContainer.Configure
следующим образом:
void IContainer.Configure(Type concreteComponent,
ComponentCallModelEnum callModel) {
if (HasComponent(concreteComponent))
return;
var services = concreteComponent.GetAllServices()
.Where(t => t != concreteComponent);
var instanceScope = GetInstanceScopeFrom(callModel);
// Bind the concrete type to itself ...
kernel
.Bind(concreteComponent)
.ToSelf()
.InScope(instanceScope);
// Bind "aliases" to the binding for the concrete component
foreach (var service in services)
kernel
.Bind(service)
.ToMethod(ctx => ctx.Kernel.Get(concreteComponent))
.InScope(instanceScope);
}
Это решается вопрос о разрешении одноэлементных экземпляров в соответствии с ожиданиями nServiceBus в. Однако у меня все еще была проблема получения сообщений «призрака» в моих обработчиках. После расчесывания файлов журнала log4net, профилирования и, наконец, чтения вопроса, как обсуждалось here и here. Проблема связана с обработчиками взаимных событий, которые привязаны во время ввода свойств. В частности, проблема возникает из-за того, что UnicastBus имеет значение Transport
, установленное mutliple times. Вот фрагмент кода из UnicastBus.CS в кодовой базе nServiceBus:
public virtual ITransport Transport
{
set
{
transport = value;
transport.StartedMessageProcessing += TransportStartedMessageProcessing;
transport.TransportMessageReceived += TransportMessageReceived;
transport.FinishedMessageProcessing += TransportFinishedMessageProcessing;
transport.FailedMessageProcessing += TransportFailedMessageProcessing;
}
}
Подумав об этом, я задавался вопросом, почему это свойство быть установлено несколько раз. UnicastBus
зарегистрирован в области singleton с помощью nServiceBus, и я только что исправил эту проблему, как обсуждалось выше. Оказывается, Ninject при активации объекта - будь то новый или из его внутреннего кеша - все равно будет выглядеть, чтобы ввести свойства объекта. Он будет называть классы эвристики инъекций, зарегистрированные в контейнере его внутреннего ядра и на основе их ответа (т. Е. Результат вызова их реализации bool ShouldInject(MemberInfo member)
), вводят свойства перед каждой активацией. Таким образом, решение заключалось в том, чтобы запретить Ninject выполнять инъекцию свойств в случаях, которые были ранее активированы, и были одиночными. Мое решение состояло в том, чтобы создать новую стратегию вложения ресурсов, которая отслеживала ранее активированные экземпляры, которые не были временными в области видимости и пропускали стратегию впрыска по умолчанию для запросов активации для таких экземпляров. Моя стратегия выглядит так:
/// <summary>
/// Only injects properties on an instance if that instance has not
/// been previously activated. This forces property injection to occur
/// only once for instances within a scope -- e.g. singleton or within
/// the same request, etc. Instances are removed on deactivation.
/// </summary>
public class NewActivationPropertyInjectStrategy : PropertyInjectionStrategy {
private readonly HashSet<object> activatedInstances = new HashSet<object>();
public NewActivationPropertyInjectStrategy(IInjectorFactory injectorFactory)
: base(injectorFactory) { }
/// <summary>
/// Injects values into the properties as described by
/// <see cref="T:Ninject.Planning.Directives.PropertyInjectionDirective"/>s
/// contained in the plan.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="reference">A reference to the instance being
/// activated.</param>
public override void Activate(IContext context,
InstanceReference reference) {
if (activatedInstances.Contains(reference.Instance))
return; // "Skip" standard activation as it was already done!
// Keep track of non-transient activations...
// Note: Maybe this should be
// ScopeCallback == StandardScopeCallbacks.Singleton
if (context.Binding.ScopeCallback != StandardScopeCallbacks.Transient)
activatedInstances.Add(reference.Instance);
base.Activate(context, reference);
}
/// <summary>
/// Contributes to the deactivation of the instance in the specified context.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="reference">A reference to the instance being
/// deactivated.</param>
public override void Deactivate(IContext context,
InstanceReference reference) {
activatedInstances.Remove(reference.Instance);
base.Deactivate(context, reference);
}
}
Моя реализация в настоящее время работает. Единственная проблема, с которой я столкнулся, - это «замена» существующей стратегии активации для инъекций свойств. Я думал о создании настраиваемого ядра (и это может быть лучший способ пойти); однако вы не можете поддерживать «предварительно настроенное» ядро для nServiceBus. На данный момент у меня есть метод расширения, который добавляет новые компоненты в любое ядро Ninject.
public static void ConfigureForObjectBuilder(this IKernel kernel) {
// Add auto inject heuristic
kernel.Components.Add<IInjectionHeuristic, AutoInjectBoundPropertyTypeHeuristic>();
// Replace property injection activation strategy...
/* NOTE: I don't like this! Thinking about replacing the pipeline component
* in Ninject so that it's smarter and selects our new activation
* property inject strategy for components in the NServiceBus DLLs and
* uses the "regular strategy" for everything else. Also, thinking of
* creating a custom kernel.
*/
kernel.Components.RemoveAll<IActivationStrategy>();
kernel.Components.Add<IActivationStrategy,
NewActivationPropertyInjectStrategy>();
// The rest of the "regular" Ninject strategies ...
kernel.Components.Add<IActivationStrategy, MethodInjectionStrategy>();
kernel.Components.Add<IActivationStrategy, InitializableStrategy>();
kernel.Components.Add<IActivationStrategy, StartableStrategy>();
kernel.Components.Add<IActivationStrategy, BindingActionStrategy>();
kernel.Components.Add<IActivationStrategy, DisposableStrategy>();
}
Это откровенная хак на данный момент, так как не существует механизма на компоненте контейнера ядра для «замены» существующего компонента. И, поскольку я хотел переопределить существующее поведение стратегии впрыска свойств, у меня не могло быть более одного из тех конкретных типов стратегий в ядре за раз. Другая проблема с этой текущей реализацией заключается в том, что любые другие настраиваемые компоненты IActivationStrategy
, которые могли быть сконфигурированы, будут потеряны. Я хотел написать код, который получит все компоненты IActivationStrategy
в списке, удалит их из ядра, заменит стратегию впрыска свойств в созданном мной списке и затем добавит их обратно в ядро, тем самым эффективно заменив их. Тем не менее, контейнер компонента ядра поддерживает только общий метод Add
, и мне не хотелось писать фанк-код для создания динамического вызова.
** EDIT ** После того, как я опубликовал вчера, я решил лучше справиться с этой стратегией. Вот что я сделал, связывая все в метод расширения для настройки Ninject ядра:
public static void ConfigureForObjectBuilder(this IKernel kernel) {
// Add auto inject heuristic
kernel.Components.Add<IInjectionHeuristic, AutoInjectBoundPropertyTypeHeuristic>();
// Get a list of all existing activation strategy types
// with exception of PropertyInjectionStrategy
var strategies = kernel.Components.GetAll<IActivationStrategy>()
.Where(s => s.GetType() != typeof (PropertyInjectionStrategy))
.Select(s => s.GetType())
.ToList();
// Add the new property injection strategy to list
strategies.Add(typeof (NewActivationPropertyInjectStrategy));
// Remove all activation strategies from the kernel
kernel.Components.RemoveAll<IActivationStrategy>();
// Add the list of strategies
var addMethod = kernel.Components.GetType().GetMethod("Add");
strategies
.ForEach(
t => addMethod
.MakeGenericMethod(typeof (IActivationStrategy), t)
.Invoke(kernel.Components, null)
);
}
Спасибо, Андреас. Я прочитал дискуссию, а также наткнулся на эту тему: http://tech.groups.yahoo.com/group/nservicebus/message/5977 (похоже, что-то похожее?) Дает мне некоторое представление о том, в чем проблема. Если я найду что-нибудь, вы поделитесь со всеми. –