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.

EmptyResult na zły sposób

Programując internety gdy wysyła się jakieś żądanie na serwer nie można założyć, że poleceni się po prostu wykona. Operacja void nie istnieje. Tzn można, ale to zła praktyka, można przecież wysłać żądanie i nie sprawdzić czy w ogóle doszło ono na serwer. Ale nie o to chodzi, mój przypadek polegał na tym, że wysyłać na serwer żądanie i chciałem tylko sprawdzić czy serwer to dostał czy nie. W moim przypadku wynik w ogóle nie był ważny. Naiwnie pomyślałem sobie, że wystarczy zwrócić (oczywiście w .net asp mvc) new EmptyResult() a na kliencie sprawdzić czy długość wyniku jest zero. Jak można się domyślić nie było by tego wpisu gdyby rzeczywistość nagięła się do mojego wyobrażenia.
Otóż pozytywny wynik operacji oraz długość wyniku była by zero gdybym wysłał request i ustawił dataType na script lub html. Ale nie w moim przypadku ja chciałem json. I co? Otóż biblioteka, która wrapowala żądania też była sprytna i czasem wysyłała żądanie json a czasem xml. I teraz operacja czasem działała gorzej a czasem jeszcze gorzej. Nie wiem czemu ubzdurałem sobie że EmptyResult coś zwróci i nie powinienem sprawdzić statusu odpowiedzi, zamiast zawartości PUSTEGO WYNIKU.

Ale jeśli jesteście ciekawi co odpowiada serwer na różne żądanie popatrzcie na ten przykładowy 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 BlogController : Controller
   2:  {
   3:      [HttpGet]
   4:      public ActionResult Index()
   5:      {
   6:          return this.View("Index");
   7:      }
   8:   
   9:      [HttpGet]
  10:      public ActionResult CallMeToGetSuccessEmptyResult()
  11:      {
  12:          return new EmptyResult();
  13:      }
  14:  }

Następnie widok:

.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:   
   2:  <script src="~/Scripts/jquery-1.10.2.js"></script>
   3:  <div class="row">
   4:      @Html.ActionLink("By xml", "CallMeToGetSuccessEmptyResult", "Blog", null, new { @class = "btn btn-default test-button", id = "demo-xml", data_type_of_data = "xml", data_anchor_id = "#demo-container-xml" })
   5:      @Html.ActionLink("By json", "CallMeToGetSuccessEmptyResult", "Blog", null, new { @class = "btn btn-default test-button", id = "demo-json", data_type_of_data = "json", data_anchor_id = "#demo-container-json" })
   6:      @Html.ActionLink("By script", "CallMeToGetSuccessEmptyResult", "Blog", null, new { @class = "btn btn-default test-button", id = "demo-script", data_type_of_data = "script", data_anchor_id = "#demo-container-script" })
   7:      @Html.ActionLink("By html", "CallMeToGetSuccessEmptyResult", "Blog", null, new { @class = "btn btn-default test-button", id = "demo-html", data_type_of_data = "html", data_anchor_id = "#demo-container-html" })
   8:  </div>
   9:   
  10:  <div class="row">
  11:   
  12:      <div class="col-md-3">XML<div id="demo-container-xml"></div></div>
  13:      <div class="col-md-3">JSON<div id="demo-container-json"></div></div>
  14:      <div class="col-md-3">SCRIPT<div id="demo-container-script"></div></div>
  15:      <div class="col-md-3">HTML<div id="demo-container-html"></div></div>
  16:  </div>
  17:   
  18:  <script type="text/javascript">
  19:   
  20:      var showJson = function(data, status, anchor) {
  21:          $(anchor).html('');
  22:          var htmlToSet = "status: " + status + "<br/>";
  23:          if (data != null) {
  24:              htmlToSet += "data lenght: " + data.length;
  25:          } else {
  26:              htmlToSet += "data is null";
  27:          }
  28:              $(anchor).html( htmlToSet );
  29:      };
  30:   
  31:      var demoResult = function(e) {
  32:          e.preventDefault();
  33:          var href = $(e.target);
  34:          var data_type = href.data("type-of-data");
  35:          var anchor = href.data("anchor-id");
  36:          var ajaxOptions = {
  37:              url: '@Url.Action("CallMeToGetSuccessEmptyResult", "Blog")',
  38:              dataType: data_type,
  39:              success: function(data, status) {
  40:                  showJson(data, status, anchor);
  41:              },
  42:              error:function(data, status) {
  43:                  showJson(data, status, anchor);
  44:              }
  45:          };
  46:   
  47:          $.ajax(ajaxOptions);
  48:      };
  49:   
  50:      $(document).ready(function() {
  51:          $(".test-button").on("click", demoResult);
  52:      });
  53:   
  54:  </script>

I teraz na obrazu wyniki działania aplikaji:

Czyli nie zawsze EmptyResult to tylko “”, czasem to także parseerror gdy oczekujemy json. Na przyszłość polecam zastanowić się co się powinno sprawdzić, gdy nie chcemy nic sprawdzać. Oraz jak chce to sprawdzić.
Jak zawsze, chętnie popełnię kolejne błędy za was w następnym odcinku.