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.

Czemu bugi są jak wino, im starsze tym droższe.

Każdy z nas/was słyszał o tym, że naprawienie buga z czasem staje coraz droższe, a jak kod pójdzie na produkcje to już w ogóle wszystko drożej miliard razy. Każdy słyszał, ale czy ktoś zdaje sobie sprawę czemu tak się dzieje?
Mi wydaje mi się że wiem i chce się tą wiedzą podzielić, także posłuchajcie pewnej bajki o remoncie mieszkania, a może znajdziecie analogie do IT, a przy okazji może podpowiecie jak dobrze zainstalować progi w mieszkaniu.
Pewnego razu, pewien programista zapragnął mieszkania, takiego pięknego mieszkania, a w nim pomalowanych ścian i sufitów, kafelek i paneli. I chciał (on – programista) aby to ładnie było w nim (tym – mieszkaniu). Słuchy chodziły, że inni koledzy z fachu tego samego już maja takie cuda i że ładnie u nich. Poradził się więc do kogo pójść, aby piękna takie i w jego domu zagościły.
Gdy wszystko już się ustaliło i zrobiło, złoto przeszło z ręki do ręki, wprowadził się on ze swoją białogłową i ich kwiatem miłości do nowego pałacu.
Ich zdziwieniom i radości z posiadania własnego miejsca nie było granic. A w zasadzie były i dosyć szybko się skończyła piękna radość. Pomijając większe i mniejsze odstępstwa od umowy/jakości. Skupię się w tej bajce na rzeczy, która może się wydać wręcz śmieszna, a taka nie jest.
Otóż oto przedmiot wywodu mojego:
Próg

Śmiechy i chichy teraz cichną i lecimy dalej. Otóż piękne plany, które snuliśmy to ograniczenie progów do minimum. Tutaj nastąpił kontakt z rzeczywistością:
Jak to bez panel bez progów, muszą być!
Nieświadomi wiedzy pokiwaliśmy głowami, jak muszą to muszą – fachowiec wie. 0:1 dla niego, bo tak naprawdę to nie muszą.
Gdy w mieszkaniu byliśmy i mieszkaliśmy, naszła nas taka fanaberia, żeby drzwi posiadać, bo czasem warto się zamknąć w jednym z nich i spokój mieć. Progi jednak spokoju nam dać nie chciały. Trzeba było je poprawić. Pewnego weekendu, postawiłem się zebrać i poprawić to co nas boli. Zerwałem progi, tu i tam razem z panelu kawałkiem, ale to nie problem, da się to zakryć nowymi, bardziej płaskimi ale też trochę szerszymi. Aż tak to nie boli. Przy pierwszej próbie założeniu nowego progu okazało się, że nie pasuje i trzeba trochę poprawić

Gdy już przy pomocy próbnika i pilnika dopasowałem szczelinę, tak aby nowy, właściwy próg pasował, wiedziałem że poprawką dwóch następnych na pewno nie zajmie im chwileczki.
Drap drap, szlif szlif udało się zrobić tak, że próg 2.0 pasował do szczeliny 1.0, co mniej więcej wyglądało tak:

Na internetach znana jest taka oto przyśpiewka:

99 little bugs in the code

99 little bugs in the code
Take one down, patch it around
117 little bugs in the code

A wiadomo, tradycja rzecz święta, także tego:

Gdy i tutaj udało mi się dopasować szczelinę do próbnika, okazało się że próg 2.0 nie jest aż tak kompatybilny ze szczeliną i należy wprowadzić drobny tunning sprzętowy:

Wreszcie, gdy wszystkie szczeliny 1.0 zostały spasowane z progiem 2.0, mógł nastać czas relesu. Ale! Przecież jeszcze drzwi. Ktoś opowiadał, że progi po drzwiach, hmmm. Tutaj uratowało nas szczęście, postanowiliśmy zaczekać na monterów ościeżnic i drzwi, co okazało się dobrym posunięciem, bo “progi po drzwiach” – tak rzekł pan montujący.

Ja spędziłem cały dzień, zrywając starą wersję progu, dopasowując szczelinkę i dopasowując nowe progi, Nie mogłem zdjąć podłogi i normalnymi narzędziami ściąć wystających elementów. Nie posiadając także narzędzi czy jakieś specjalistycznej wiedzy (w zasadzie nic szczególnego wiedzieć nie trzeba, ale zawsze) musiałem wprowadzić zmiany już w istniejącym systemie. Ok dałem rade, będzie działać, nauczyłem się pewnie paru rzeczy, ale zajęło mi to dzień – a muszę jeszcze dopasować progi do ościeżnic.
Gdyby od razu zrobić dobrze, tak że nie trzeba by w ogóle poprawiać, mając możliwości do manewrowania panelami, mając narzędzia to zamiast całego dnia, moją robotę można zrobić w pięć minut. W dodatku nie doszło by do uszkodzenia materiału, czy kupowania czegoś co nie pasuje i trzeba to wyrzucić.

Wszystkim czym mogę i jak mogę podpisuje się pod tym obrazkiem.
PS.
Jestem po releasie na produkcje, ale syf. Jestem strasznie nie pocieszony tym, jak mi poszło. Nie wiem czy nie będzie trzeba robić, kolejnego fixa i znowu robić releasa na produkcje, z programi 3.0.
Czas pokaże. Mam nadzieję, że teraz już będziecie wiedzieć, czemu im później tym drożej.

ToBeImplemented reboot

Projekty które robi się dla siebie mają pewna cechę, która jednocześnie jest czymś dobrym i złym. Taki pet-project można rozpoczynać milion razy i nikt nie robi z tego powodu afery – to plus, po milionowym rozpoczęciu szanse na zakończenie są małe – to minus.
Ja właśnie zwiększyłem cyferkę do 3, na szczęście nadal chce mi się to pisać. Rozpocząłem od nowa, bo strasznie pokomplikowałem sobie aplikację, to raz, i przy próbach prostowania doszedłem do wniosku, że chyba szybciej będzie napisać to jeszcze raz, bez takiej ilości magicznego pyłku, który użyłem wcześniej. A dwa, że nauczyłem się paru nowych rzeczy, które chciałem tutaj wykorzystać.
Tym sposobem zmierzam do warstw. O ile wcześniej dzieliłem projekt na proste części:


Jak widać, szaleństwa nie ma. Aplikacja (ASP MVC) korzysta z serwisów, serwisy z bazy oraz czasem z samych siebie (oczywiście przez interfejsy).
Ok, w zasadzie nie ma się czego czepiać, tak/nie? Mogło być lepiej, mogło być gorzej. Zamiast sześciu żółtych prostokątów, mógł być jeden. Zawsze powtarzam, że nie wolno porównywać się gorszym, pozostaje więc tylko czepiać się i szukać miejsca na poprawki.
Co skusiło mnie to tego, żeby to zmienić? W serwisach było w zasadzie wszystko co nie jest związane z bazą danych i z samym mvc, czyli: logika, mapowanie z model na view model, maile, hash, konta użytkowników, blablablabla, wszystko.
Czy to dobre? It depends. Jeśli robi się mały prosty projekt (taki książkowy), to może będzie to dobre rozwiązanie, jeśli robi się nowego fejsbuka to raczej nie, a przypominam, że już nie długo moja aplikacja zajmie jego miejsce – widzę i słyszę wasz śmiech, zobaczymy kto będzie się śmiać ostatni!

Co takiego pozmieniałem w swoim rozwiązaniu? Warstwy, więcej warstw, znacie to przysłowie, że każdy problem da się rozwiązań dodając kolejną warstwę abstrakcji?
Razem z tym zmieniłem także troszkę podejście do rozwiązywania zależności, które tworzę, wygląda mniej więcej tak:

Największe zmiany to wiele aplikacji które są wspierane, robię to żeby sprawdzić jak długo można utrzymywać wspólny kod, na razie bez cross-platform.
Idealnie jest aby aplikacje były maksymalnie lekkie, cała logika jest przeniesiona do części Bussines, która wykonuje właściwą pracę. W przypadku aplikacji WPF są jej dwa warianty, jeden oparty o Application.REST, drugi który referuje bezpośrednio do Bussines i działa “natywnie”.
Dodatkowo ważny jest brak zależności w poziomie, tzn. serwisy nie mogą zależeć od serwisów, infrastruktura od infrastruktury, czy biznes od biznesu. Poszczególne klasy w danej warstwach muszą być samodzielne, jeśli ma się pojawiać jakaś zależność, oznacza to że klasa chce wziąć na klatę zbyt dużą odpowiedzialność i że jest źle.
Prawdę mówiąc, jestem dopiero na początku tej drogi i wiem, że pojawią się bloczki (już są), które będą specyficzne dla poszczególnych aplikacji, ale jestem na to gotowy i na aktualnym stopniu mojej znajomości naszej programistycznej domeny jestem w stanie to zaakceptować.

TL;DR:
W projektach gdzie zależności są poziome, np. serwisA zależy od serwisuB są, złe. Dwa powody: może to prowadzić to zależności cyklicznych, oraz oznaczać to może, że jeden z nich ma za dużą odpowiedzialność, ponieważ część pracy przekazuje do sąsiada o którym nie powinien wiedzieć.
Słowo motywujące na dziś: nie bój się eksperymentować!

Ciekawy moich postępów czy rozwiązań, aktualny projekt możesz znaleźć tutaj:
https://jstadnicki.visualstudio.com/DefaultCollection/_git/ToBeImplemented
https://github.com/jstadnicki/tbi

Autofacu czytaj szablony z configa – please.

Czyżbym wrócił do formy z blogiem?

Niektórzy może pamiętają, a inni nie, ale walczę z takim tam sobie prywatnym projekcikiem. Otóż nastał tam etap refaktoringu i właśnie wtedy postanowiłem, że trzeba wreszcie przenieść rejestrację komponentów w IOC z kodu do configa. Używam Autofac, więc zerknełem w dokumentację i dociągnełem ‘Autofac.Configuration’ z NuGeta, jest to potrzebne aby móc grzebać z configu. Zaraz potem wziełem się do roboty,wycinek wygląda tak:
Trzeba dodać informację o nowej sekcji:

   1:  <section name="autofac" type="Autofac.Configuration.SectionHandler, Autofac.Configuration" />

A potem w sekcji autofac lecimy w taki sposób:

.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:  <autofac>
   2:      <components>
   3:          <component type="ToBeImplemented.Services.AboutService, ToBeImplemented.Services" 
   4:                     service="ToBeImplemented.Contracts.IAboutService, ToBeImplemented" />
   5:      </components>
   6:  </autofac>

type: określa jaka klasę z pełnym namespace, oraz podaje nazwę assembly
service: określa interfejs z pełnym namespace, oraz podaje nazwę assembly.
To oznacza w kodzie:

   1:  /*** ToBeImplemented project ***/
   2:  namespace ToBeImplemented.Contracts
   3:  {
   4:      public interface IAboutService
   5:      {
   6:          /* definition goes here */
   7:      }
   8:  }
   9:   
  10:  /*** ToBeImplemented.Services project ***/
  11:  namespace ToBeImplemented.Services
  12:  {
  13:      public class AboutService : IAboutService
  14:      {
  15:          /* implemenetation goes here*/
  16:      }
  17:  }

Wygląda to prosto? Mam nadzieję że tak, bo to jest proste i prosta sytuacja. Życie oczywiście nie jest takie, dlatego natrafiłem na taki problem

   1:  namespace ToBeImplemented.Services
   2:  {
   3:      using System.Collections.Generic;
   4:   
   5:      using ToBeImplemented.ViewModels;
   6:   
   7:      public class TagViewModelEqualityComparer : IEqualityComparer<TagViewModel>
   8:      {
   9:         /* code goes here */
  10:      }
  11:  }

Szablony, jak ich nie kochać? Idea jest piękna, jeden kod, wiele zastosowań. A teraz wracając do do autofaca i w zasadzie podstaw w .net i programowaniu i idei tego wpisu na blogu.
Jakiego typu jest IEqualityComparer<TagViewModel>? Hę? Bo ja się pomyliłem, zapomniałem o tym, czym naprawdę są szablony. Może sprawdzimy na takim przykładzie

   1:  using System;
   2:  using System.Collections.Generic;
   3:   
   4:  namespace ConsoleApplication1
   5:  {
   6:      class Program
   7:      {
   8:          static void Main(string[] args)
   9:          {
  10:              Console.WriteLine(typeof(IEqualityComparer<int>));
  11:              Console.WriteLine(typeof(TagViewModelEqualityComparer));
  12:          }
  13:      }
  14:   
  15:      public class TagViewModel
  16:      {
  17:          /* code goes here */
  18:      }
  19:   
  20:      public class TagViewModelEqualityComparer : IEqualityComparer<TagViewModel>
  21:      {
  22:          /* implementation goes here */
  23:      }
  24:  }

Co będzie na ekranie?

System.Collections.Generic.IEqualityComparer`1[ConsoleApplication1.TagViewModel]
ConsoleApplication1.TagViewModelEqualityComparer

Draniu! I teraz do mnie: jak mogłem o tym zapomnieć! Czyli, gdy w config kiedyś zechce zarejestrować klasę korzystającą z templejtów lub będącą templejtem to muszę pamiętać, że nie implementuje templejta, tylko jego konkretną implementacje, w moim przypadku wygląda to tak:

   1:  <component type="ToBeImplemented.Services.TagEqualityComparer, ToBeImplemented.Services"
   2:             service="System.Collections.Generic.IEqualityComparer`1[[ToBeImplemented.Model.Tag, ToBeImplemented.Model]], mscorlib" />

Od teraz będzie działać dobrze. I żeby nie było, chciałem tylko podzielić się wrażeniami z templejtów. Oczywiście autofac to dużo więcej możliwości i problemów, ale mnie ostatnio spotkał tylko ten jeden (kłamczuch ze mnie).
Po dokładne szczegóły dotyczące użytkownia autofaca zapraszam na ich strone projektu, na SO albo może ktoś mnie zapytać jeśli się wstydzi czytać i pisać po angielsku.
I to tyle. Mam nadzieję, że zaoszczędziłem dzięki temu komuś trochę czasu.