AutoFac - Gdzie rejestrować typy danego assembly?

1

Cześć,

Zastanawiam się jakie są dobre praktyki przy rejestracji komponentów z użyciem kontenera AutoFac - chodzi o miejsce rejestracji.

Załóżmy taką sytuację:
W solucji 2 projekty:
-WebApi, w którym znajduje się klasa Config, odpowiedzialna za odczytanie z pliku konfiguracyjnego adresu do WebSerwisów z których pobieramy dane.
-Infrastructure, gdzie mam stworzone pewne klasy które korzystają z tych webserwisów (czyli potrzebują mieć ich adres).

Oczywiście WebApi zależy od Infrastructure, nie odwrotnie.

I teraz jak się już pewnie domyślacie - zastanawiałem się czy to jest OK abym sobie zarejestrował te klasy z Infrastructure w module z projektu WebApi, aby łatwo podać im wymagane parametry. Jest to oczywiście konsekwencja tego, że klasy konfiguracyjne zaimplementowałem w projekcie WebApi (teraz już mam to w innym, takim od którego zależności mają oba te projekty), ale chodzi mi tylko o zobrazowanie problemu.

W skrócie: Czy zawsze powinniśmy rejestrować dany typ w module który znajduje się w tym samym projekcie (assembly) co dany typ?

0

ale co masz miec w WebApi ze potrzebujesz rejestrowac za pomoca autofaca? (nie mylic z konfiguracja)

0

Typy rejestrujesz w Main swojej aplikacji. Jeśli to WebAPI, to wtedy w ConfigureServices.
Infrastructure - zawiera interfejsy
Twoja aplikacja - zawiera konkretne klasy implementujące te interfejsy
Projekt testowy (Twoja druga aplikacja) - zawiera konkretne klasy implementujące interfejsy.

Kontenery IoC powstały po to, żeby programista mógł sobie w jednym miejscu zarejestrować klasy pod konkretne interfejsy. Dzięki temu łatwiejsze staje się Dependency Injection (to się robi automagicznie). Cała zasada takiego projektowania polega na tym, że w różnych aplikacjach możesz mieć różne klasy implementujące Twoje interfejsy.

Przykładowo możesz stworzyć dwie aplikacje: konsolowa windowsowa i windowsowa z okienkami. I masz np. interfejs: "IViewable" - zdefiniowany w infrastrukturze.
IViewable może wyglądać tak:

public interface IViewable
{
    void ShowView(object data);
}

To oczywiście pewne uproszczenie. Ale teraz w aplikacji konsolowej możesz zrobić coś takiego:

class View: IViewable
{
  public void ShowView(object data)
  {
      MyData d = data as MyData;
      Console.Clear();
      Console.WriteLine("Imię: " + d.FirstName);
      Console.WriteLine("Nazwisko: " + d.LastName);
  }
}

A w aplikacji okienkowej:

partial class MyView: Form, IViewable
{
  public void ShowView(object data)
  {
    MyData d = data as MyData;
    this.firstNameEdit.Text = d.FirstName;
    this.lastNameEdit.Text = d.LastName;
    this.Show();
  }
}

I teraz w aplikacji konsolowej w Main rejestrujesz klasę View, a w aplikacji GUI w Main rejestrujesz klasę MyView.
W aplikacji testującej możesz sobie zarejestrować jeszcze coś innego albo nawet zamockować.

Rozumiesz?

Poza tym, jeśli masz .NetCore 2 WebApi, to to ma własny kontener IoC i nie powinieneś używać AutoFACa.

0
Juhas napisał(a):

Infrastructure - zawiera interfejsy

Nic nie wspominałem o interfejsach, nie wiem czemu zakładasz że Infrastructure je zawiera. Mam tam Klasy które nie potrzebują się chować za interfejsem. Rejestruje je bez podawania interfejsu, nie wiem czemu w ogóle tutaj wspomniałeś cokolwiek o nich (interfejsach)

Twoja aplikacja - zawiera konkretne klasy implementujące te interfejsy
Projekt testowy (Twoja druga aplikacja) - zawiera konkretne klasy implementujące interfejsy.

Kontenery IoC powstały po to, żeby programista mógł sobie w jednym miejscu zarejestrować klasy pod konkretne interfejsy. Dzięki temu łatwiejsze staje się Dependency Injection (to się robi automagicznie). Cała zasada takiego projektowania polega na tym, że w różnych aplikacjach możesz mieć różne klasy implementujące Twoje interfejsy.

No właśnie zastanawia mnie to "w jednym miejscu". Możliwe że tak jest aczkolwiek znowu twoja argumentacja jest oparta o interfejsy i jakoś mnie nie przekonuje.

Przykładowo możesz stworzyć dwie aplikacje: konsolowa windowsowa i windowsowa z okienkami. I masz np. interfejs: "IViewable" - zdefiniowany w infrastrukturze.
IViewable może wyglądać tak:

public interface IViewable
{
    void ShowView(object data);
}

To oczywiście pewne uproszczenie. Ale teraz w aplikacji konsolowej możesz zrobić coś takiego:

class View: IViewable
{
  public void ShowView(object data)
  {
      MyData d = data as MyData;
      Console.Clear();
      Console.WriteLine("Imię: " + d.FirstName);
      Console.WriteLine("Nazwisko: " + d.LastName);
  }
}

A w aplikacji okienkowej:

partial class MyView: Form, IViewable
{
  public void ShowView(object data)
  {
    MyData d = data as MyData;
    this.firstNameEdit.Text = d.FirstName;
    this.lastNameEdit.Text = d.LastName;
    this.Show();
  }
}

I teraz w aplikacji konsolowej w Main rejestrujesz klasę View, a w aplikacji GUI w Main rejestrujesz klasę MyView.
W aplikacji testującej możesz sobie zarejestrować jeszcze coś innego albo nawet zamockować.

Rozumiesz?

yyyy, co? :D jakby rozumiem co tutaj napisałeś, tylko nie jestem pewien po co :D

Poza tym, jeśli masz .NetCore 2 WebApi, to to ma własny kontener IoC i nie powinieneś używać AutoFACa.

Niby czemu nie?

0
NiezlyByk napisał(a):

Infrastructure - zawiera interfejsy

Nic nie wspominałem o interfejsach, nie wiem czemu zakładasz że Infrastructure je zawiera. Mam tam Klasy które nie potrzebują się chować za interfejsem. Rejestruje je bez podawania interfejsu, nie wiem czemu w ogóle tutaj wspomniałeś cokolwiek o nich (interfejsach)

Więc pewnie robisz coś nie tak.

Twoja aplikacja - zawiera konkretne klasy implementujące te interfejsy
Projekt testowy (Twoja druga aplikacja) - zawiera konkretne klasy implementujące interfejsy.

Kontenery IoC powstały po to, żeby programista mógł sobie w jednym miejscu zarejestrować klasy pod konkretne interfejsy. Dzięki temu łatwiejsze staje się Dependency Injection (to się robi automagicznie). Cała zasada takiego projektowania polega na tym, że w różnych aplikacjach możesz mieć różne klasy implementujące Twoje interfejsy.

No właśnie zastanawia mnie to "w jednym miejscu". Możliwe że tak jest aczkolwiek znowu twoja argumentacja jest oparta o interfejsy i jakoś mnie nie przekonuje.

To nie jest istotne, czy masz interfejsy, czy nie. Rejestracja ma się odbywać w jednym miejscu. Jeśli chcesz rejestrować klasy w kilku miejscach, to:

  • masz burdel w kodzie
  • masz źle zaprojektowany system.

Po utworzeniu (zbudowaniu) kontenera nie powinieneś już niczego rejestrować.

I teraz w aplikacji konsolowej w Main rejestrujesz klasę View, a w aplikacji GUI w Main rejestrujesz klasę MyView.
W aplikacji testującej możesz sobie zarejestrować jeszcze coś innego albo nawet zamockować.

Rozumiesz?

yyyy, co? :D jakby rozumiem co tutaj napisałeś, tylko nie jestem pewien po co :D

Bo wydaje się, że nie za bardzo wiesz, jak powinno działać DI z IoC

Poza tym, jeśli masz .NetCore 2 WebApi, to to ma własny kontener IoC i nie powinieneś używać AutoFACa.

Niby czemu nie?

Bo masz już istniejący mechanizm, który działa z innymi mechanizmami WebAPI i podłączanie do tego kolejnego kontenera jest zupełnie bez sensu i niczego dobrego Ci nie da. I gwarantuję Ci, że będą problemy z tym.

1

Jeśli nie mam interfejsów (do akurat tych 3 klas proxy do komunikacji z web serwisami, nie twierdzę że nie mam ich w ogóle) to robię coś nie tak? W tym wypadku interfejsy nie miałyby żadnego zastosowania, jedynie generowałyby zbędny kod. Ale nie o tym mowa.

Rejestracja ma się odbywać w jednym miejscu.

Autofac ma mechanizm modułów, który w dość łatwy sposób pozwala na rejestrację klas w obrębie modułu a następnie tych modułów w projekcie głównym (jak np WebAPI).

ale co masz miec w WebApi ze potrzebujesz rejestrowac za pomoca autofaca? (nie mylic z konfiguracja)

Nie jestem pewien czy pytasz z uwagi na istniejący mechanizm wstrzykiwania zależności (czyli po co mi autofac) czy dlatego że w samym projekcie WebApi nic raczej nie powinno być do rejestrowania (jak klasy serwisowe z logiką itd).
Jeśli to pierwsze i twierdzisz tak jak użytkownik Juhas, że nie powinienem używać innego, bardziej rozbudowanego kontenera tylko dlatego że istnieje jakies tam wbudowane rozwiązanie to nie mam już więcej pytań.

Jeśli to drugie to to spieszę z wyjaśnieniami, że właśnie w tej chwili nic tam nie rejestruje, ale musiałbym jeśli odpowiadając na pytanie z mojego pierwszego posta przekonałbyś mnie że można (powinno się?) czasami rejestrować typy pochodzące z innego assembly w module leżacym gdzieś indziej. Co już stoi w opozycji do stwierdzenia :

Rejestracja ma się odbywać w jednym miejscu

1
NiezlyByk napisał(a):

I teraz jak się już pewnie domyślacie - zastanawiałem się czy to jest OK abym sobie zarejestrował te klasy z Infrastructure w module z projektu WebApi, aby łatwo podać im wymagane parametry. Jest to oczywiście konsekwencja tego, że klasy konfiguracyjne zaimplementowałem w projekcie WebApi (teraz już mam to w innym, takim od którego zależności mają oba te projekty), ale chodzi mi tylko o zobrazowanie problemu.

Moim zdaniem to nie jest OK. Klasy z Infrastructure powinny zależeć od interfejsu IConfig, który dostarczałby im wymaganych parametrów. IConfig musi być zdefiniowany w Infrastructure, a jego implementacja w WebApi i tam też zarejestrowana.

W skrócie: Czy zawsze powinniśmy rejestrować dany typ w module który znajduje się w tym samym projekcie (assembly) co dany typ?

Moim zdaniem tak, tylko należy w tym zdaniu zmienić słowo typ na klasa aby uniknąć nieporozumień.

Po utworzeniu (zbudowaniu) kontenera nie powinieneś już niczego rejestrować.

Rejestracja klasy w kontenerze, to nie jest to samo, co budowanie kontenera. Jak najbardziej można to rozdzielić, i porządne kontenery IoC to wspierają.

Bo masz już istniejący mechanizm, który działa z innymi mechanizmami WebAPI i podłączanie do tego kolejnego kontenera jest zupełnie bez sensu i niczego dobrego Ci nie da. I gwarantuję Ci, że będą problemy z tym.

Gwarantujesz, czyli zapłacisz oddszkodowanie, jeśli nie będzie problemów?
Czy ten istniejący mechanizm oferuje wszystko to, co jest potrzebne? W szczególności np. podział na moduły?
I czemu zatem inne kontenery wspierają Core, a twórcy Core tak przygotowali framework, aby używanie innych kontenerów było możliwe?

0
somekind napisał(a):

Po utworzeniu (zbudowaniu) kontenera nie powinieneś już niczego rejestrować.

Rejestracja klasy w kontenerze, to nie jest to samo, co budowanie kontenera. Jak najbardziej można to rozdzielić, i porządne kontenery IoC to wspierają.

Czy poprawne jest utworzenie kontenera (Build), a następnie "dorejestrowanie" klas?

Bo masz już istniejący mechanizm, który działa z innymi mechanizmami WebAPI i podłączanie do tego kolejnego kontenera jest zupełnie bez sensu i niczego dobrego Ci nie da. I gwarantuję Ci, że będą problemy z tym.

Gwarantujesz, czyli zapłacisz oddszkodowanie, jeśli nie będzie problemów?
Czy ten istniejący mechanizm oferuje wszystko to, co jest potrzebne? W szczególności np. podział na moduły?
I czemu zatem inne kontenery wspierają Core, a twórcy Core tak przygotowali framework, aby używanie innych kontenerów było możliwe?

Założyłem z jakiegoś powodu, że używa obu mechanizmów.

0
Juhas napisał(a):

Czy poprawne jest utworzenie kontenera (Build), a następnie "dorejestrowanie" klas?

A tak się w ogóle da?

Napisałeś wcześniej:

Jeśli chcesz rejestrować klasy w kilku miejscach, to:

  • masz burdel w kodzie
  • masz źle zaprojektowany system.

Co nie jest prawdą, bo jest właściwie wręcz przeciwnie.

0
somekind napisał(a):
Juhas napisał(a):

Czy poprawne jest utworzenie kontenera (Build), a następnie "dorejestrowanie" klas?

A tak się w ogóle da?

Zrozumiałem, że op chce tak robić.

Napisałeś wcześniej:

Jeśli chcesz rejestrować klasy w kilku miejscach, to:

  • masz burdel w kodzie
  • masz źle zaprojektowany system.

Co nie jest prawdą, bo jest właściwie wręcz przeciwnie.

No teraz chyba wiem o co chodziło. A możesz podać przykład takiego rozwiązania? W sensie fragment kodu?

7

Chodzi o np. takie coś.

W warstwie insfrastruktury:

namespace Application.Infrastructure
{
    public class AssemblyModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterType<ProductsDataProvider>().As<IProductsDataProvider>().SingleInstance();
            builder.RegisterAssemblyTypes(ThisAssembly).Where(t => t.Name.EndsWith("Dao")).AsSelf().AsImplementedInterfaces();
            builder.RegisterAssemblyTypes(ThisAssembly).Where(t => t.Name.EndsWith("Client")).AsSelf().AsImplementedInterfaces().SingleInstance();
            builder.RegisterAssemblyTypes(ThisAssembly).Where(t => t.Name.EndsWith("Cache")).AsSelf().AsImplementedInterfaces().SingleInstance();
        }
   }
}

W warstwie logiki biznesowej:

namespace Application.Business
{
    public class AssemblyModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {        
            builder.RegisterAssemblyTypes(ThisAssembly).Where(t => t.Name.EndsWith("Service")).AsSelf();
        }
   }
}

W warstwie WebApi:

namespace Application.WebApi
{
    public class AssemblyModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterType<AppConfig>().As<IConfig>();
            builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
        }
   }
}

I uruchomienie tego (też w warstwie WebApi, bo tam startuje aplikacja):

public static class AutofacConfig
{
    public static void Register(HttpConfiguration config)
    {
        var builder = new ContainerBuilder();
        var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
        builder.RegisterAssemblyModules(loadedAssemblies);
           
        var container = builder.Build();

        config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
    }
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1