Autofac update – dynamiczna zmiana implementacji

Wiadomo, że każdy projekcie są testy. W części z nich są testy jednostkowe, w innych są testy integracyjne, w innych testy programistyczne – programista klika i jak działa to działa, a w jeszcze innych test na produkcji u klienta razem z milionami użytkowników. Generalnie każdy jakieś test ma. Wiadomo jak jest w projektach komercyjnych, klient chce aplikację działająca, wykonaną z najnowszymi i najlepszymi technikami, najlepiej napisaną przez juniorów, bez testerów i PM – tak najczęściej planowany jest budżet na aplikacje. Programiści czasem potrafią spełnić część tych wymagań i udaje się nam stworzyć aplikację, która jest stabilna (najczęściej), wygląda przyzwoicie, została napisana tak, że można ją rozwijać – generalnie klient jest zadowolony, a przynajmniej nie pieni się i jest w stanie zaakceptować produkt.
Dzieje się tak, ponieważ Ci programiści dzielnie przeklikują swoje wytwory, piszą możliwie dobry kod, przy kiepskich wymaganiach czy ich braku. Czasem gdy trzeba wyklikać to samo tysiąc razy, człowiek robi się leniwy, albo nie uważny i czy tego chcemy czy nie nie jesteśmy w stanie wyklikać wszystkich przypadków. Najczęściej gdy projekt jest już sporych rozmiarów, posiada skomplikowaną logikę lub wiele alternatywnych do siebie ścieżek. 
W moim przypadku naszą słabą stroną były problemy w wydajnością. Na developerskich maszynach, gdzie testowaliśmy, mieliśmy po kilka kont użytkownika, kilka, kilkadziesiąt, lub trochę ponad setkę wpisów. Generowanie danych za każdym razem gdy baza się zmieni i trzeba było ją odtwarzać od zera nie należało do przyjemnych rzeczy, nie mieliśmy migracji, więc drop a potem create. Testy selenium pojawiły się później, ale też zajmowały wieczność.
Zmierzam do tego, że łatwo byłe tego uniknąć, generując sobie masę testowych danych. Jak tego dokonać? O tym właśnie dzisiaj, odrobina kodu się także pojawi a wszystko dostępne będzie na githubie. Jak zawsze minister zdrowia ostrzega, przykłady mogą być przejaskrawione, a rozwiązanie może się okazać przydatne tylko dla mnie.
Przykładowym problememe do rozwiązania jest prosta aplikacja, która umożliwia założenie konta, zalogowanie się, oraz dodanie jakiegoś prostego wpisu tekstowego, który jest widoczny dla wszystkich. Coś jak blog, czy czat czy lista ogłoszeń, to nie jest do końca ważne.
Zacznijmy od struktury aplikacji:
Dla prostoty wszystko co potrzebne umieściłem w jednym kontrolerze. A zwinięte foldery nie mają żadnego wpływu na treść edukacyjną tego posta.
Ok, to zaczynamy. Rejestracja nowego użytkownika wygląda tak:

   1:  [HttpGet]
   2:  public ActionResult Register()
   3:  {
   4:      var viewmodel = new RegisterViewModel();
   5:      return this.View("Register", viewmodel);
   6:  }
   7:   
   8:  [HttpPost]
   9:  public ActionResult Register(RegisterDto dto)
  10:  {
  11:      this.accountService.Register(dto);
  12:      return this.RedirectToAction("Index");
  13:  }
I ostatecznie prosty serwis:

   1:  public void Register(RegisterDto dto)
   2:  {
   3:      WebSecurity.CreateUserAndAccount(
   4:          dto.Email,
   5:          dto.Password);
   6:  }
Jak widać korzystam z SimpleMembershipProvider. Klasa dto do rejestracji też jest książkowo prosta:

   1:  public class RegisterDto
   2:  {
   3:      public string Email { get; set; }
   4:      public string Password { get; set; }
   5:  }
Tak więc, aby założyć konto wystarczy wpisać dwa stringi i ciach, konto dostępne. Oczywiście gdy będzie trzeba to powtórzyć wiele razy, zacznie się to robić męczące i nudne. Dlatego leniwi programiści mogą wymyślić sobie coś takiego:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  [HttpGet]
   2:  public ActionResult NewUsers()
   3:  {
   4:      this.testService.CreateNewUsers();
   5:      return this.RedirectToAction("Index");
   6:  }
Gdzie serwis został zaimplementowany w taki sposób:

   1:  public void CreateNewUsers()
   2:  {
   3:      var dto = new RegisterDto
   4:      {
   5:          Email = Faker.InternetFaker.Email() + Faker.NumberFaker.Number(),
   6:          Password = Faker.NumberFaker.Number(1000000000, 2000000000).ToString()
   7:      };
   8:      this.accountService.Register(dto);
   9:  }
Fakera opisałem w poprzednim poście. Warto zwrócić uwagę, że test serwis korzysta z account service. Dwie ważne rzeczy: każdy wie, że zależności powinny iść tylko w pionie, nigdy w poziomie – istnieje ryzyko zależności cyklicznych; jest to serwis testowy, wiec korzysta z normalnego przepływu danych, nie robi nic na skróty, nie oszukuje, także ułatwia nam tworzenie użytkowników, a jednocześnie testuje działające już serwisy.
Jak do tej pory jeszcze nie ma wielkiego szału, ale się powoli rozkręcam. Kolejny etap to wpis w aplikacji, kontroler wygląda tak:

   1:  [HttpGet]
   2:  [Authorize]
   3:  public ActionResult NewBlogPost()
   4:  {
   5:      var viewmodel = new BlogPostViewModel();
   6:      return this.View("NewBlogPost", viewmodel);
   7:  }
   8:   
   9:  [Authorize]
  10:  [HttpPost]
  11:  public ActionResult NewBlogPost(NewBlogPostDto dto)
  12:  {
  13:      this.postingService.NewBlogPost(dto);
  14:      return this.RedirectToAction("Index");
  15:  }
Kod nie powinien niczym zaskakiwać, GET i POST, oraz wymagane wcześniejsze zalogowanie się użytkownika. Warstwa implementująca:

   1:  public void NewBlogPost(NewBlogPostDto dto)
   2:  {
   3:      var userId = this.accountService.GetCurrentLoggedUserId();
   4:      var model = new BlogPost(userId, dto.Text, DateTime.Now);
   5:      this.database.BlogPosts.Add(model);
   6:      this.database.SaveChanges();
   7:  }
Dto dla nowego wpisu jest niebiańsko prosta:

   1:  public class NewBlogPostDto
   2:  {
   3:      public string Text { get; set; }
   4:  }
Ale wróćmy do serwisu, w serwisie korzystając z SMP (single membership provider) pobieramy aktualne ID użytkownika. Może nie bezpośrednio z SMP, ale prostego wrapperka nad SMP. W momencie implementacji postawiłem na opcje zaciągania danych o użytkowniku z SMP.
Sam wrapper wcześniej pokazany częściowo, teraz w calej okazałości:

   1:  class WebSecurityAccountService : IAccountService
   2:  {
   3:      public void Logout()
   4:      {
   5:          WebSecurity.Logout();
   6:      }
   7:   
   8:      public bool Login(LoginDto dto)
   9:      {
  10:          var result = WebSecurity.Login(dto.Email, dto.Password);
  11:          return result;
  12:      }
  13:   
  14:      public void Register(RegisterDto dto)
  15:      {
  16:          WebSecurity.CreateUserAndAccount(
  17:              dto.Email,
  18:              dto.Password);
  19:      }
  20:   
  21:      public int GetCurrentLoggedUserId()
  22:      {
  23:          return WebSecurity.CurrentUserId;
  24:      }   
  25:  }
No dobra, fajny pomysł, informacje o użytkowniku zawsze pobierane z serwera, wydaje się bezpieczne i w ogóle. Ale teraz dochodzimy znowu do momentu, gdy chciałbym napisać kawałek kodu który pozwoli mi jednym kliknięciem utworzyć nowy wpis. Najlepiej za każdym razem z losowym tekstem (tutaj Faker daje radę) ale jeszcze z losowego istniejącego konta. W tym momencie na chwilę zrobiło się trudno, ale skoro mamy IAccountService i z niego pobieramy informacje o użytkowniku, czemu tego nie wykorzystać, wystarczy go tylko podmienić na inny, mój, i losować z niego użytkowników:

   1:  [HttpGet]
   2:  public ActionResult NewPosts()
   3:  {
   4:      this.testService.CreateNewPosts();
   5:      return this.RedirectToAction("Index");
   6:  }
Potem:

   1:  public void CreateNewPosts()
   2:  {
   3:      var users = this.database.Users.ToList();
   4:      var random = users.ElementAt(Faker.NumberFaker.Number(users.Count - 1));
   5:      var dto = new NewBlogPostDto
   6:      {
   7:          Text = Faker.TextFaker.Sentences(3)
   8:      };
   9:   
  10:      SpecialAccountService.CurrentLoggedUserId = random.Id;
  11:   
  12:      this.postingService.NewBlogPost(dto);
  13:  }
Docieramy powoli do mięska. Z bazy danych wyciągamy wszystko użytkowników, losujemy jednego i zapamiętujemy. Następnie linijka 10 dzięki SpecialAccountService ustawia Id “aktualnie” zalogowanego użytkownika, a następnie zwróci to Id, gdy ktoś będzie pytać o aktualnie zalogowanego użytkownika. Na końcu znowu, wykorzystujemy wcześniej napisany serwis, który zachowuje się normalnie i jest dodatkowo testowany przez nasze generowanie testowych danych. Dlaczego SpecialAccountService działa tak soczyście?

   1:  public class SpecialAccountService : IAccountService
   2:  {
   3:      private readonly WebSecurityAccountService realAccountService;
   4:   
   5:      public SpecialAccountService()
   6:      {
   7:          this.realAccountService = new WebSecurityAccountService();
   8:      }
   9:   
  10:      public void Logout()
  11:      {
  12:          this.realAccountService.Logout();
  13:      }
  14:   
  15:      public bool Login(LoginDto dto)
  16:      {
  17:          return this.realAccountService.Login(dto);
  18:      }
  19:   
  20:      public void Register(RegisterDto dto)
  21:      {
  22:          this.realAccountService.Register(dto);
  23:      }
  24:   
  25:      static public int CurrentLoggedUserId { get; set; }
  26:   
  27:      public int GetCurrentLoggedUserId()
  28:      {
  29:          return CurrentLoggedUserId;
  30:      }
  31:  }
Klasa ta implementuje IAccountService i jest ona używana w zarejestrowana w autofacu. Natomiast gdy jest potrzeba prawdziwej rejestracji użytkownika, całą robotę deleguje do prawdziwego serwisu. Dzięki temu można rejestrować,logować czy wylogować użytkownika. A bajer z aktualnym użytkownikiem ułatwia nam tworzenie losowych wpisów dla losowych użytkowników.
Wiadomo, że na produkcje nie powinno się wypuszczać takiego kodu, dlatego normalnie trzeba by przygotować odpowiednią konfigurację IOC gdzie IAccountService będzie implementowany przez WebSecurityAccountService, a na potrzeby developerki wystarczy SpecialAccountService – można tak zrobić. Można też poprosić autofaca, aby zmienił swoje dotychczasowe zachowanie i nadpisać wcześniejsze reguły:

   1:  public class HijackService : IHijackService
   2:  {
   3:      public void RestoreIOC()
   4:      {
   5:          var builder = new ContainerBuilder();
   6:          builder.RegisterType<WebSecurityAccountService>().As<IAccountService>();
   7:          builder.Update(ApplicationIocContainer.Container);
   8:      }
   9:   
  10:      public void HijackIOC()
  11:      {
  12:          var builder = new ContainerBuilder();
  13:          builder.RegisterType<SpecialAccountService>().As<IAccountService>().SingleInstance();
  14:          builder.Update(ApplicationIocContainer.Container);
  15:      }
  16:   
  17:      public IOCStatus GetStatus()
  18:      {
  19:          var isHijacked = ApplicationIocContainer.Container.Resolve<IAccountService>() is SpecialAccountService;
  20:          return isHijacked ? IOCStatus.Hijacked : IOCStatus.Normal;
  21:      }
  22:  }
Od tej pory możemy dynamicznie rejestrować swoje klasy, które ułatwią nam udawanie kogoś kim nie jesteśmy, a po skończonej generacji danych przywrócić aplikację do oryginalnego stanu i zacząć właściwe testowanie. Największą gwiazdą tego postu jest metoda Update z IOC, która umożliwia aktualizację powiązać interfejsu z klasą implementującą bez potrzeby zmiany konfiguracji, bez potrzeby restartu aplikacji, żeby autofac skonfigurował się od nowa, etc.
A dzięki temu wszystkie możemy stworzyć sobie taki prosty interfejs aplikacji:
Gdzie istnieje możliwość normalnego działa z aplikacją, ale gdy zajdzie potrzeba, można na chwilę pożyczyć kontener, wygenerować sobie niezwykle szybko sporo danych, i ponownie jednym kliknięciem przywrócić normalny stan aplikacji. Tutaj tworzę tylko jednego użytkownika i jeden wpis, ale kto bogatemu zabronić tworzyć ich tysiące?
Uwagi, pomysły chętnie przyjmę.
Kod dostępny jest na bitbucket: https://bitbucket.org/jstadnicki/jstadnicki-blog
Zaczynam od jednego, z czasem gdy będzie więcej dużych wpisów, kolejne rzeczy związane z blogiem będę wrzucane tam.
Milej niedzieli!

Generator danych Faker.net

Każdy z nas jest choć trochę leniwy. Jedni troszkę mniej, inni troszkę bardziej. Ja na ten przykład czasem się rozpędzam i piszę kod, który potrzebuje. Potem o nim opowiadam, a potem ktoś pyta czemu nie skorzystałem z jakiejś tam gotowej biblioteki. Wiem, że każdy z nas należy do mensy i wie, że ten wpis nie wziął się z powietrza.
Dzisiejszy wpis dedykuje koledze Ievgenowi (blog lub @ibezuglyi)(dla ktorego specjalnie załatwiłem sobie literki, żeby wpisać jego imię do komputera), który to pokazał mi Fakera – nie palec, ale taką bibliotekę. Służy ona do generowania różnych, ale skończonych, losowych danych.
Dane podzielone są tematycznie na:

  • ArrayFaker
  • BooleanFaker
  • CompanyFaker
  • DateTimeFaker
  • EnumFaker
  • InternetFaker
  • LocationFaker
  • NameFaker
  • NumberFaker
  • PhoneFaker
  • StringFaker
  • TextFaker
W każdej znajdą się ciekawe metody, które bardzo upraszczają, ułatwiają i przyspieszają tworzenie testów, lub przynajmniej losowych wpisów.
Nowy użytkownik w naszym serwisie to taki kod:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  var user = new UserAccount
   2:  {
   3:      Email = Faker.InternetFaker.Email() + Faker.NumberFaker.Number(),
   4:      Password = Faker.NumberFaker.Number(1000000000, 2000000000).ToString()
   5:  };

Ponieważ maile mogą się powtarzać, dodajemy jeszcze więcej losowości. Hasło nie przejdzie testów na trudność, ale będzie choć trochę losowe. Czaicie prostotę i moc? Następnym razem, gdy będziecie pisać własną bibliotekę do generowania losowych danych skorzystajcie z Fakera. A jak będziecie pisać inną bibliotekę, wcześniej skorzystajcie z googla LUB (znowu od Ievgena) http://nugetmusthaves.com.
Tym razem udało się krótko. Inne biblioteki warte polecenia?

Powiedz nie new

Co mają wspólnego ze sobą te dwa obrazki?

Na pewno nie pasują tutaj, to raz. dwa nie są to najładniejsze obrazki, a trzy to obcisłe spodnie nie zawsze wyglądają fajnie, nawet na kobietach. On na szczęście nie ma skarpetek do sandałów.
Ale o czym dzisiaj, dzisiaj o obcisłości po angielsku w programowaniu. Słowo tight będzie jednym z bohaterów wpisu. A nawet tight coupling, czyli coś mocno wiążącego. Co tak mocno wiąże w programowaniu? Moim zdanie new jest temu winny. Wiąże bowiem ono ze sobą klienta, klasę którą korzysta z new aby zaspokoić swoje potrzeby, oraz dostarczyciela usługi, czyli klasę, która jest w stanie zaspokoić tę potrzebę.
Ale hola-hola, pomyślicie, jak to programować bez new? Przecież to hańba, skandal, herezje! Tak się nie da! Jasne, ale może uda się trochę ukrócić to panowanie new w kodzie?
Na pierwszy ogień idzie klasyczna zależność klas wyższych od niższych, w przypadku asp.net mvc przykładem jest tutaj kontroler, który może zależeć od serwisu, który posiada jakieś repozytorium danych, a to korzysta z bazy danych.
Czyli coś takiego

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  public class HomeController
   2:  {
   3:      private HomeService service;
   4:   
   5:      public HomeController()
   6:      {
   7:          service = new HomeService();
   8:      }
   9:  }
  10:   
  11:  public class HomeService
  12:  {
  13:      private HomeRepository repository;
  14:   
  15:      public HomeService()
  16:      {
  17:          repository = new HomeRepository();
  18:      }
  19:  }
  20:   
  21:  public class HomeRepository
  22:  {
  23:      private ApplicationDatabase database;
  24:   
  25:      public HomeRepository()
  26:      {
  27:          database = new ApplicationDatabase();
  28:      }
  29:  }
  30:   
  31:  public class ApplicationDatabase
  32:  {
  33:      //internalls
  34:  }

Application database to pewnie EF, nH, albo inna warstwa nad SQL, a może zestaw zapytań SQL zapisanych w stringach – nie jest to ważne w tym przykładzie.
Ten kod zadziała i będzie świetnie, ale te klasy będą działać tylko i wyłącznie ze sobą, nigdy nie pozwolą innej klasie, która może być szybsza i w ogóle – wykonać kodu, albowiem tylko HomeService, tylko HomeRepository, tylko ApplicationDatabase może to zrobić.Taki zapis powinien wam się kojarzyć z hardcodowanymi wartościami, które są tylko odrobinę lepsze niż magic numbers. Bo jaka jest różnica pomiędzy tym co napisałem powyżej a var daysToAdd=49; I tak gdy będę chciał zmieć ilośc dni, muszę przepisać i przekompilować.
A można prościej się, i uwaga prościej nie oznacza mniej kodu, prościej oznacza prościej, ewentualnie możne oznaczać lepiej:

   1:  public class HomeController
   2:  {
   3:      private IHomeService service;
   4:   
   5:      public HomeController(IHomeService homeService)
   6:      {
   7:          this.service = homeService;
   8:      }
   9:  }
  10:   
  11:  public class HomeService : IHomeService
  12:  {
  13:      private IHomeRepository repository;
  14:   
  15:      public HomeService(IHomeRepository homeRepository)
  16:      {
  17:          repository = homeRepository;
  18:      }
  19:  }
  20:   
  21:  public interface IHomeService
  22:  {
  23:  }
  24:   
  25:  public class HomeRepository : IHomeRepository
  26:  {
  27:      private IDatabase database;
  28:   
  29:      public HomeRepository(IDatabase applicationDatabase)
  30:      {
  31:          database = applicationDatabase;
  32:      }
  33:  }
  34:   
  35:  public interface IHomeRepository
  36:  {
  37:  }
  38:   
  39:  public class ApplicationDatabase : IDatabase
  40:  {
  41:      //internalls
  42:  }
  43:   
  44:  public interface IDatabase
  45:  {
  46:  }

Ponownie, nie wnikam w szczegóły implementacyjne..
Samo ApplicationDatabase się nie zmienia, zależy może od jakichś systemowych dll, ale dla nas będzie ona ostateczną granicą. Co się zmieniło? Otóż klasy teraz mówią coś takiego: “Spoko spoko ziomuś, będę działać, będzie pan zadowolony, ale potrzebuje tego i tego – inaczej mój kolega kompilator, albo runtime (wersja gdy dasz nulla) da Ci popalić – taka jest sytuacja”. W jasny sposób deklarują swoje wymogi pracy. Znika z naszego kodu słówko new, gacie przestały być takie obcisłe. Oczywiście trzeba mieć jakiś system który te klocki dobrze połączy ze sobą, ja korzystam z Autofaca, ale w zasadzie każdy konterer jest dobry. Co najważniejsze kontenery można tak ustawić, aby ich konfiguracja była w zapisana w zewnętrznych plikach, daje do możliwość zamiany zachowania aplikacji bez potrzeby jest przekompilowywania – wystarczy zmiana w konfiguracji i restart.
Inne miejsce gdzie pojawia się new to przejścia pomiędzy warstwami aplikacji, gdy model stają się dto, lub innymi dto, albo się agregują i projektują jakieś dane na i z, lub zupełnie inaczej. Tutaj z pomocą przychodzi Automapper. Całą magię przepisywania nazwy (name) z klasy A na pełną nazwę (fullname) w klasie B można oddelegować do osobnej części i ponownie powiedzieć, że do poprawnej pracy potrzebuje takich i takich rzeczy. Automapper domyślnie jest statyczną klasą, ale można ją owrapować i wstrzykiwać jako serwis, co może ułatwić ewentualne testowanie.
Ostatnie co przychodzi mi do głowy, to sytuacja gdy naprawdę muszę stworzyć obiekt, bo np. nie lubisz automapować go z kilku innych, albo jest to zlepek kilku wywołań jakichś metod albo jeszcze inaczej. Wiadomo, każdy ma wyjątkowy projekt i wyjątkowe sytuacja, które nigdy i nikomu się jeszcze nie powtórzyły. Na takie sytuację przychodzą mi do głowy dwa mechanizmy: konstruktor i/lub fabryki. Konstruktor powinien zapewnić ci to, że jeśli wszystko się udało i programista tworzący klasę, którą właśnie niułujesz (takie słowo), to po zwróceniu sterowania do twojego kodu, klasa ta będzie w stu procentach gotowa do działania. Używania inicjalizatora jest fajnym skrótem, albo gdy zostanie dołożone nowe pole do klasy, nie masz pewności, że w każdym miejscu zostanie ono poprawnie ustawione. Dodając pole do klasy i na listę parametrów konstruktora masz pewność, że kompilator ci tego nie zapomni wytknąć. Jest to jedno rozwiązanie, które nie zmniejsza new w kodzie, ale nie zmniejsza ciasnowatości (znowu takie słowo) spodni. Lepszym podejściem są fabryki, które wezmą tę robotę na siebie. Znowu rozwiązanie, które spowoduje ze kodu nie będzie mniej, ale umożliwi napisanie kodu który nie musi być mocno związany z typem, fabryki mogą zwracać obiekty przez interfejsy, znowu więcej kody, za luźne gacie. Dodatkowo obowiązek posiadania wiedzy o tworzeniu klas wynikowych zostanie oddelegowany do fabryk. Fabryki możecie podawać jako wasze ulubione wzorce projektowe na rozmowach o pracę
Jeśli macie inne pomysły na to jak pozbyć się new, chętnie je poznam. Jednocześnie możecie się zastanawiać czemu to robić? Po pierwsze w luźnych spodniach lepiej się chodzi, ale luźne tak bez przesady. Po drugie, moim zdaniem kod staje prostszy w utrzymaniu. Trudniej w takim kodzie o wycieki, tak w .net można zrobić wycieki pamięci. Po kolejne, stajecie się bardziej pro. Kod jest otwarty na modyfikacje i rozszerzenia, ułatwia testowanie, jest ładny i miły w dotyku. Jeśli tego nie czujecie, to nic na siłę. Ja po prostu musiałem to napisać – jeśli mogę unikam new.
ps. A miał to być krótki wpis.

O braciach QueryString i ActionParameters

W .net ASP.MVC są różne mechanizmy. Są też takie, które umożliwiają zbadanie argumentów przesłanych do akcji, jak i argumentów oczekiwanych w akcji. I właśnie o nich dzisiaj. Można je wykorzystywać na dobry i zły sposób, jak każde narzędzie. Najpierw mały pokaz a potem filozofowanie. Mamy taki kod:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  public class HomeController : Controller
   2:  {
   3:      public ActionResult Index()
   4:      {
   5:          return this.View("Index");
   6:      }
   7:   
   8:      public string MustHaveString(string text)
   9:      {
  10:          return text;
  11:      }
  12:   
  13:      public string CanHaveString(string text = "")
  14:      {
  15:          return text;
  16:      }
  17:   
  18:      public string MustHaveInt(int input)
  19:      {
  20:          return input.ToString();
  21:      }
  22:   
  23:      public string CanHaveInt(int input = 0)
  24:      {
  25:          return input.ToString();
  26:      }
  27:   
  28:      public string MustHaveBoth(string text, int input)
  29:      {
  30:          string r = string.Empty;
  31:          for (int i = 0; i < input; i++)
  32:          {
  33:              r += text;
  34:          }
  35:          return r;
  36:      }
  37:   
  38:      public string CanHaveBoth(string text = "just a text", int input = 7)
  39:      {
  40:          string r = string.Empty;
  41:          for (int i = 0; i < input; i++)
  42:          {
  43:              r += text;
  44:          }
  45:          return r;
  46:      }
  47:   
  48:      public string Greed(string text, int input, int wantSoMuch, int stillNotUsing, int itAll)
  49:      {
  50:          return text + input;
  51:      }
  52:  }

Idąc od góry oczekujemy stringa, następnie oczekujemy stringa, ale mamy wartość domyślną na wypadek nie przesłania do przez użytkownika. Dalej to samo z intem, obowiązkowo oraz opcjonalnie. Potem wariacja z dwoma parametrami – obowiązkowo i opcjonalnie. I na koniec strasznie poznańska metoda, która potrzebuje wielu paramentrów, ale wykorzystuje tylko kilka z nich. Jeszcze jeszcze index, którego wyświetlenie wygląda tak:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  <div class="container">
   2:      <div class="row">
   3:          <div class="col-md-6">@Html.ActionLink("Must have a string with string set", "MustHaveString", "Home", new { text = "lorem ipsum" }, new { @class = "btn btn-default" })</div>
   4:      </div>
   5:      <div class="row">
   6:          <div class="col-md-6">@Html.ActionLink("Must have a string with string not", "MustHaveString", "Home", new {}, new { @class = "btn btn-default" })</div>
   7:      </div>
   8:      <div class="row">
   9:          <div class="col-md-6">@Html.ActionLink("Can have a string with string set", "CanHaveString", "Home", new { }, new { @class = "btn btn-default" })</div>
  10:      </div>
  11:      <div class="row">
  12:          <div class="col-md-6">@Html.ActionLink("Can have a string with string not set", "CanHaveString", "Home", new {text="litwo ojczyzno moja" }, new { @class = "btn btn-default" })</div>
  13:      </div>
  14:      <hr/>
  15:      <div class="row">
  16:          <div class="col-md-6">@Html.ActionLink("Must have a int with int set", "MustHaveInt", "Home", new { input = 42 }, new { @class = "btn btn-default" })</div>
  17:      </div>
  18:      <div class="row">
  19:          <div class="col-md-6">@Html.ActionLink("Must have a int with int not", "MustHaveInt", "Home", new { }, new { @class = "btn btn-default" })</div>
  20:      </div>
  21:      <div class="row">
  22:          <div class="col-md-6">@Html.ActionLink("Can have a int with int set", "CanHaveInt", "Home", new { input=667 }, new { @class = "btn btn-default" })</div>
  23:      </div>
  24:      <div class="row">
  25:          <div class="col-md-6">@Html.ActionLink("Can have a int with int not set", "CanHaveInt", "Home", new { }, new { @class = "btn btn-default" })</div>
  26:      </div>
  27:      <hr/>
  28:      <div class="row">
  29:          <div class="col-md-6">@Html.ActionLink("Must have both set", "MustHaveBoth", "Home", new { text="plonie ogniosko", input=3}, new { @class = "btn btn-default" })</div>
  30:      </div>
  31:      <div class="row">
  32:          <div class="col-md-6">@Html.ActionLink("Must have both not", "MustHaveBoth", "Home", new { }, new { @class = "btn btn-default" })</div>
  33:      </div>
  34:      <div class="row">
  35:          <div class="col-md-6">@Html.ActionLink("Must have both set too much", "MustHaveBoth", "Home", new { text="a",input="123", x="co ja tu"}, new { @class = "btn btn-default" })</div>
  36:      </div>
  37:      <hr/>
  38:      <div class="row">
  39:          <div class="col-md-6">@Html.ActionLink("Greed only the using ones", "Greed", "Home", new { text = "a", input = "123" }, new { @class = "btn btn-default" })</div>
  40:      </div>
  41:      <div class="row">
  42:          <div class="col-md-6">@Html.ActionLink("Greed all the things", "Greed", "Home", new { text = "a", input = "123", wantSoMuch = -1, stillNotUsing = -1, itAll =-1}, new { @class = "btn btn-default" })</div>
  43:      </div>
  44:  </div>

Czyli razor w linkami do poszczególnych akcji, czasem w linku są wymagane argumenty czasem ich brak. Wygląda to tak:

Mała niespodzianka, która wyszła podczas pisania tego posta. Okazuje się, że gdy na liście argumentów jest string, ale nie zostanie przesłany i nawet jeśli nie posiada on domyślnej wartości to i tak pewne mechanizmy asp zadziałają i zostanie wywołana akcja zdefiniowana w kontrolerze. Ta sama sztuczka z int’em już nie zadziała. Zostanie wyświetlmy zółty ekran błędu, informujący o tym, że nie udało się połączyć wszystkich kropek i jest klops. Ale to nie o tym wpis – ot taka ciekawostka.

W kontrolerze możemy nadpisać wywołanie metody, np. takiej metody w taki sposób:

   1:  protected override void OnActionExecuting(ActionExecutingContext filterContext)
   2:  {
   3:      Debug.WriteLine(
   4:          string.Format("***** New Action *****n***** {0} *****", filterContext.ActionDescriptor.ActionName));
   5:      Debug.WriteLine("*** Action parameteres {0}***", filterContext.ActionParameters.Count);
   6:      filterContext.ActionParameters.ForEach(
   7:          ap => Debug.WriteLine(string.Format("Key: {0}tValue: {1}", ap.Key, ap.Value)));
   8:   
   9:      var queryString = filterContext.RequestContext.HttpContext.Request.QueryString;
  10:      Debug.WriteLine("*** Query string {0}***", queryString.AllKeys.Count());
  11:      queryString.AllKeys.ForEach(k =>
  12:          Debug.WriteLine(string.Format("Key: {0}tValue: {1}", k, queryString[k])));
  13:  }

I teraz docieramy do sedna wpisu, będziemy oglądać QueryString i ActionParameters. Dla starych wyjadaczy różnica pewnie jasna i oczywista, ja się poznałem z nimi dopiero ostatnio, oryginalni autorzy świetnie się spisali przeinaczając (jest takie słowo) ich cel: otóż, wymyślił sobie ktoś że skoro w projekcie jest bardzo dużo kontrolerów, to pewnie znajdzie się część wspólna, którą warto umieścić w jakimś bazowym dla projektu kontrolerze. Sanity check – OK. Wszystko mogło być dobrze, do momenty gdy ktoś inny, nie robiłem git blame, nie wpadł na pomysł aby sprawdzać czy argumenty się zgadzają i sprawdzać, czy action paramentes zawiera np projectId, który ma zostać wysłany od użytkownika. Sanity check – NOK. Każdy kontroler dziedziczący ten wspólny, musi mieć na liście argumentów każdej ze swojej akcji parametr “int projectId”. Co przeciwnym wypadku? Oczywiście że przekierowanie na InvalidUrl i uwaga, strona ta w żaden sposób nie informuje o tym, czego oczekiwała, ani nic. Po prostu invalid url. Sanity check – Reject!
Dlaczego mnie to tak drażni? Bo jest to ukrywanie zależności danej metody, tworzę albo pracuje z taki kodem i gdy na liście argumentów mam nie używane parametry, to je usuwam i oczekuje że kod nadal będzie działać.
Zobaczcie jak wygląda wywołanie poszczególnych linków:
***** New Action *****
***** MustHaveString *****
*** Action parameteres 1***
Key: text    Value: lorem ipsum
*** Query string 1***
Key: text    Value: lorem ipsum

***** New Action *****
***** MustHaveString *****
*** Action parameteres 1***
Key: text    Value:
*** Query string 0***

***** New Action *****
***** CanHaveString *****
*** Action parameteres 1***
Key: text    Value:
*** Query string 0***

***** New Action *****
***** CanHaveString *****
*** Action parameteres 1***
Key: text    Value: litwo ojczyzno moja
*** Query string 1***
Key: text    Value: litwo ojczyzno moja

***** New Action *****
***** MustHaveInt *****
*** Action parameteres 1***
Key: input    Value: 42
*** Query string 1***
Key: input    Value: 42

***** New Action *****
***** MustHaveInt *****
*** Action parameteres 1***
Key: input    Value:
*** Query string 0***

***** New Action *****
***** CanHaveInt *****
*** Action parameteres 1***
Key: input    Value: 667
*** Query string 1***
Key: input    Value: 667

***** New Action *****
***** CanHaveInt *****
*** Action parameteres 1***
Key: input    Value: 0
*** Query string 0***

***** New Action *****
***** MustHaveBoth *****
*** Action parameteres 2***
Key: text    Value: plonie ogniosko
Key: input    Value: 3
*** Query string 2***
Key: text    Value: plonie ogniosko
Key: input    Value: 3

***** New Action *****
***** MustHaveBoth *****
*** Action parameteres 2***
Key: text    Value:
Key: input    Value:
*** Query string 0***

***** New Action *****
***** MustHaveBoth *****
*** Action parameteres 2***
Key: text    Value: a
Key: input    Value: 123
*** Query string 3***
Key: text    Value: a
Key: input    Value: 123
Key: x    Value: co ja tu

***** New Action *****
***** Greed *****
*** Action parameteres 5***
Key: text    Value: a
Key: input    Value: 123
Key: wantSoMuch    Value:
Key: stillNotUsing    Value:
Key: itAll    Value:
*** Query string 2***
Key: text    Value: a
Key: input    Value: 123

***** New Action *****
***** Greed *****
*** Action parameteres 5***
Key: text    Value: a
Key: input    Value: 123
Key: wantSoMuch    Value: -1
Key: stillNotUsing    Value: -1
Key: itAll    Value: -1
*** Query string 5***
Key: text    Value: a
Key: input    Value: 123
Key: wantSoMuch    Value: -1
Key: stillNotUsing    Value: -1
Key: itAll    Value: -1

O ile pierwsze z nich są w porządku, bo korzystają z tego czego wymagają, o tyle mały smród na końcu, niegodziwy zachłanny gostek chce aż pięciu parametrów, podczas gdy wykorzystuje tylko dwa z pięciu. Na szczęcie HomeController nie został jeszcze dotknięty ideą sprawdzania action parameters, ale jeśli ktoś wpadnie na pomysł napisania takiego kodu:

   1:  protected override void OnActionExecuting(ActionExecutingContext filterContext)
   2:  {
   3:      if (!filterContext.ActionParameters.Keys.Contains("itAll"))
   4:      {
   5:          Response.Redirect("http://jstadnicki.blogspot.com/");
   6:      }
   7:  }

To wtedy musicie dostarczyć mieć na liście argumentów metody, uwaga napiszę to jeszcze raz: sygnatura akcji musi mieć “itAll” na liście argumentów aby w ogóle się do niej dostać. Czyli wywołanie localhost/home/index?itAll=”dzialaj” nie zadziała, ponieważ index nie ma itAll na liście parametrów, a to że został on przesłany w query string nie pomoże.
Kończąc mój długi wpis, który miał być krótki. Nie ukrywajcie zależności w kodzie, zgłaszając błąd czy to użytkownikowi czy innemu programiście, podajcie wystarczająco dużo informacji, aby od razu jasne było dlaczego coś się nie udało. I dodając kod do klasy bazowej pamiętajcie o tym, jak wielki wpływ na inne części aplikacji będzie miała wasza zmiana. Dziedziczenie nie jest rozwiązaniem. I ostatnie jeszcze ActionParameters!!!!!!!!!!!!!!!=QueryString

Automapper NullSubstitue lubi cache

Ot taka ciekawostko z automappera, kto zna to niech czyta dalej, kto nie zna może zapoznać sie z poprzednim wpisem na tym właśnie blogu: http://jstadnickiag.nazwa.pl/jaroslawstadnickimainwebsite/?p=31.

Właściwa ciekawostka, mając taki kod:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  Mapper.CreateMap<UserAccount, ProfilePrivateViewModel>()
   2:    .ForMember(d => d.Recruiter, o => o.NullSubstitute(new ProfilePrivateRecruiterViewModel()))
   3:    .ForMember(d => d.Developer, o => o.NullSubstitute(new ProfilePrivateDeveloperViewModel()));

Czyli jeśli rekruter lub developer będzie nullem, to chcemy aby zostało to zastąpione nowo tworzoną klasą. Wygląda prosto? Ci z was, którzy myślą tak jak ja, czyli przy każdym mapowaniu z UserAccount na ProfilePrivateViewModel będzie tworzony nowy obiekt rekrutera lub developera, jeśli w UserAccountnullem – są w błędzie. Otóż okazuje się, że automapper podczas inicjalizacji zapisuje sobie ten obiekt, a następnie przy każdym mapowaniu, gdy ma z niego skorzystać zwraca zawsze tą samą instancję obiektu. Jest to bardzo ważne, jeśli wy (ja też) modyfikujemy taką instancję. Czujecie już problem? Ponieważ będzie ona współdzielona pomiędzy inne nulle, gdy taki się pojawi i dostanie ten substytut, będzie on już zmodyfikowany.

Gdyby mieć jeszcze taki kod:

   1:  AfterMap((s, d) => d.Skills.Add(new DeveloperSkillViewModel())

Wyrwany trochę z kontekstu, ale rozumiecie idee. Okaże się, że po każdym mapowaniu z null na “nowy” viewmodel programisty, jego lista umiejętności rośnie o jeden. Dziesiąte mapowanie stworzy nowego developera, ale z dziesięcioma umiejętnościami.

Możecie się domyślić jak ja się dowiedziałem 😉

Pomimo tego ficzera, automapper dla mnie nadal dobrą biblioteką jest.