2015-02-09 39 views
5

Mam interfejsu, wdrażania i cel:Jak wykonać opcjonalną zależność w AutoFac?

public interface IPerson { public string Name { get; } } 
public class Person: IPerson { public string Name { get { return "John"; } } } 
public class Target { public Target(IPerson person) {} } 

Używam Autofac związać rzeczy razem:

builder.RegisterType<Person>().As<IPerson>().SingleInstance(); 

Problemem jest to, że IPerson mieszka we wspólnym zespole, Person żyje w wtyczce (która może lub nie musi być), a Target mieszka w głównej aplikacji, która ładuje wtyczki. Jeśli nie ma zainstalowanych wtyczek, które implementują IPerson, Autofac balistycznych nie będzie w stanie rozwiązać zależności Target. I nie mogę tego za to winić.

Jednak wiem, że Target jest w stanie obsłużyć brak numeru IPerson i byłby bardziej niż szczęśliwy, aby uzyskać zamiast niego null. W rzeczywistości jestem prawie pewny, że wszystkie komponenty, które polegają na IPerson, są przygotowane do wzięcia na siebie. Więc jak mogę powiedzieć Autofacowi - "W porządku, kochanie, nie martw się, po prostu daj mi z powrotem null, dobrze?"

Jednym ze sposobów znalazłem jest dodanie parametru domyślnego Target:

public class Target { public Target(IPerson person = null) {} } 

To działa, ale muszę to zrobić dla wszystkich elementów, które wymagają IPerson. Czy mogę też zrobić to na odwrót? Jakoś powiedzieć Autofac "Jeśli wszystko inne zawiedzie w celu rozwiązania IPerson, zwróć wartość null"?

+0

Zobacz także 'ResolveOptional()'. – abatishchev

+0

Jeśli "Osoba" żyje w wtyczce, jak ją zarejestrować? Gdzie jest to wezwanie? 'builder.RegisterType () .As () .SingleInstance();' –

+1

@SriramSakthivel - każda wtyczka ma metodę 'Initialize (ContainerBuilder)', która jest wywoływana przez główną aplikację. Wtyczki rejestrują tam swoje komponenty. –

Odpowiedz

3

Można użyć tej składni:

builder.RegisterType<Target>().WithParameter(TypedParameter.From<IPerson>(null)); 

Niestety

builder.Register(c => (IPerson)null).As<IPerson>(); 
    // will throw : Autofac.Core.DependencyResolutionException: A delegate registered to create instances of 'ConsoleApplication17.Program+IPerson' returned null. 

i

builder.RegisterInstance<IPerson>(null).As<IPerson>(); 
    // will throw : Unhandled Exception: System.ArgumentNullException: Value cannot be null. 

Jeśli nie chcesz dodać WithParameter dla każdej rejestracji, można dodać moduł, który zrobi to za ciebie

public class OptionalAutowiringModule : Autofac.Module 
{ 
    public OptionalAutowiringModule(IEnumerable<Type> optionalTypes) 
    { 
     this._optionalTypes = optionalTypes; 
    } 
    public OptionalAutowiringModule(params Type[] optionalTypes) 
    { 
     this._optionalTypes = optionalTypes; 
    } 


    private readonly IEnumerable<Type> _optionalTypes; 


    protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration) 
    { 
     base.AttachToComponentRegistration(componentRegistry, registration); 

     registration.Preparing += (sender, e) => 
     { 
      e.Parameters = e.Parameters.Concat(new Parameter[] { new OptionalAutowiringParameter(this._optionalTypes) }); 
     }; 
    } 
} 
public class OptionalAutowiringParameter : Parameter 
{ 
    public OptionalAutowiringParameter(IEnumerable<Type> optionalTypes) 
    { 
     this._optionalTypes = optionalTypes.ToList(); 
    } 


    private readonly List<Type> _optionalTypes; 


    public override Boolean CanSupplyValue(ParameterInfo pi, IComponentContext context, out Func<Object> valueProvider) 
    { 
     if (this._optionalTypes.Contains(pi.ParameterType) && !context.IsRegistered(pi.ParameterType)) 
     { 
      valueProvider =() => null; 
      return true; 
     } 
     else 
     { 
      valueProvider = null; 
      return false; 
     } 
    } 
} 

Następnie wszystko co musisz zrobić, to zarejestrować moduł z opcjonalnej zależności

builder.RegisterModule(new OptionalAutowiringModule(typeof(IPerson))); 

ale zamiast wstrzykiwania null odniesienia, który może powodować NullReferenceException. Innym rozwiązaniem byłoby stworzenie implementacji NullPerson.

builder.RegisterType<NullPerson>().As<IPerson>(); 
    builder.RegisterType<Target>(); 

Gdy masz realne wdrożenie tylko zarejestrowani go ponownie, będzie to nadpisać oryginalny realizacji.

+0

Cóż, pierwsza opcja nie jest dużo lepsza niż tylko podanie '= null' w deklaracji parametru. W rzeczywistości jest jeszcze dłuższy. "NullPerson" jest rodzajem rozwiązania, ale dość niezręcznie. –

+0

Zaktualizowałem próbkę, aby uniknąć rejestracji parametru dla każdej rejestracji przy użyciu modułu –

+0

OK, więc niestandardowe źródło rejestracji. Cóż, to chyba najlepsza droga. Dziękuję Ci! :) –

0

Jako obejście można wstrzyknąć elewację podobną do Lazy<IPerson>, która spróbuje usunąć ją z pojemnika po wywołaniu.

0

Normalnym mechanizmem deklarowania zależności opcjonalnych jest umieszczenie ich we właściwościach zamiast argumentów konstruktora.

3

Wystarczy użyć parametrów opcjonalnych, zobacz następujące próbki:

public class SomeClass 
{ 
    public SomeClass(ISomeDependency someDependency = null) 
    { 
      // someDependency will be null in case you've not registered that before, and will be filled whenever you register that. 
    } 
} 
1

Ty mógł tylko zabrać swoje uzależnienie od IPerson person = null w konstruktorze, który jest niejawna deklaracja opcjonalnym IPerson uzależnienia (por @YaserMoradi) . Jednak, że stawia się w sytuacji konieczności ugruntować to, teraz i na wieki:

”... Jestem pewien, że wszystkie elementy, które polegają na IPerson są przygotowani do podjęcia go null jego miejsce. "

Lepiej, żeby to wcale nie było pytanie.

Wzorzec "best practice" (który @CyrilDurand podaje jako sufiks w jego odpowiedzi) polega na użyciu default implementation (wynik połączenia: Autofac użyje ostatniego zarejestrowanego komponentu jako domyślnego dostawcy tej usługi). Jeśli nie masz innej implementacji pochodzącej z twojej wtyczki (zarejestrowanej po ustawieniu domyślnym), ta wartość domyślna będzie używana.

W twoim przypadku domyślnym komponentem powinno być coś w rodzaju no-op lub podstawowa implementacja usługi IPerson, gdzie każda wywołana metoda będzie miała wszystko, co stanowi domyślne zachowanie dla twojej aplikacji. Daje to również lepszą historię ponownego wykorzystania, ponieważ możesz zdefiniować domyślne zachowanie raz na zawsze.