<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.6.2">Jekyll</generator><link href="http://wolanski.info/feed.xml" rel="self" type="application/atom+xml" /><link href="http://wolanski.info/" rel="alternate" type="text/html" /><updated>2017-11-28T21:07:48+00:00</updated><id>http://wolanski.info/</id><title type="html">Marcin Wolański</title><subtitle>Notatki programisty.</subtitle><entry><title type="html">Przekierowanie adresu www na nie-www w nginx</title><link href="http://wolanski.info/2017/11/28/przekierowanie-www-na-nie-www-w-nginx.html" rel="alternate" type="text/html" title="Przekierowanie adresu www na nie-www w nginx" /><published>2017-11-28T21:00:00+00:00</published><updated>2017-11-28T21:00:00+00:00</updated><id>http://wolanski.info/2017/11/28/przekierowanie-www-na-nie-www-w-nginx</id><content type="html" xml:base="http://wolanski.info/2017/11/28/przekierowanie-www-na-nie-www-w-nginx.html">&lt;p&gt;&lt;a href=&quot;/2017/11/26/net-core-20-development-na-windows-deployment-na-linux.html&quot;&gt;Poprzednio&lt;/a&gt; skonfigurowałem aplikację stworzoną w ASP.NET Core na serwerze &lt;a href=&quot;https://nginx.org&quot;&gt;nginx&lt;/a&gt; z certyfikatem. Pięknie działają przekierowania http://www.example.com na https://example.com, ale umknął mi jeden szczegół.&lt;/p&gt;

&lt;p&gt;Mianowicie strona jest dostępna z adresów https://www.example.com oraz https://example.com. Ze względu na SEO nie jest to najlepszym pomysłem. Chciałbym zostawić adres bez &lt;em&gt;www&lt;/em&gt;. W tym celu należy w pliku &lt;em&gt;/etc/nginx/sites-available/default&lt;/em&gt; dodać następujący fragment:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-nginx&quot; data-lang=&quot;nginx&quot;&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$host&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;~&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;'^www\.')&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;301&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://example.com&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$request_uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Następnie z linii komend należy sprawdzić, czy konfiguracja &lt;a href=&quot;https://nginx.org&quot;&gt;nginx&lt;/a&gt; jest ok i zrestartować serwer:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;nginx &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl reload nginx&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;</content><author><name></name></author><summary type="html">Poprzednio skonfigurowałem aplikację stworzoną w ASP.NET Core na serwerze nginx z certyfikatem. Pięknie działają przekierowania http://www.example.com na https://example.com, ale umknął mi jeden szczegół. Mianowicie strona jest dostępna z adresów https://www.example.com oraz https://example.com. Ze względu na SEO nie jest to najlepszym pomysłem. Chciałbym zostawić adres bez www. W tym celu należy w pliku /etc/nginx/sites-available/default dodać następujący fragment: if ($host ~ '^www\.') { return 301 https://example.com$request_uri; } Następnie z linii komend należy sprawdzić, czy konfiguracja nginx jest ok i zrestartować serwer: sudo nginx -t sudo systemctl reload nginx</summary></entry><entry><title type="html">.NET Core 2.0 - development na Windows, deployment na Linux</title><link href="http://wolanski.info/2017/11/26/net-core-20-development-na-windows-deployment-na-linux.html" rel="alternate" type="text/html" title=".NET Core 2.0 - development na Windows, deployment na Linux" /><published>2017-11-25T23:00:00+00:00</published><updated>2017-11-25T23:00:00+00:00</updated><id>http://wolanski.info/2017/11/26/net-core-20-development-na-windows-deployment-na-linux</id><content type="html" xml:base="http://wolanski.info/2017/11/26/net-core-20-development-na-windows-deployment-na-linux.html">&lt;p&gt;Niniejszym wpisem chciałbym rozpocząć cykl opisujący migrację małego projektu webowego napisanego dawno temu w języku Python do .NET Core 2.0. Trudności na samym początku napotkałem wiele. Wynikają one z tego, że aplikacja powstała dawno temu, a .NET Core nie znam.&lt;/p&gt;

&lt;p&gt;Króciutko o projekcie. Aplikację stworzyłem dawno temu. Wykorzystuje ona framework &lt;a href=&quot;http://flask.pocoo.org/&quot;&gt;Flask&lt;/a&gt;, działa na bazie &lt;a href=&quot;https://www.postgresql.org/&quot;&gt;PostgreSQL&lt;/a&gt; i produkcyjnie działa na serwerze z Linuksem. Od dawna chciałem poznać .NET Core, więc zamiast odkopywać swoją znajomość języka &lt;a href=&quot;https://www.python.org/&quot;&gt;Python&lt;/a&gt;, postanowiłem przepisać aplikację na ASP.NET Core 2.0. Aplikację chciałbym rozwijać w Visual Studio Code na Windows 10, a produkcyjnie aplikacja ma działać na Linux. Założenie jest takie, że aplikacja po migracji ma mieć dokładnie taki sam zakres funkcjonalności.&lt;/p&gt;

&lt;p&gt;Na samym początku projektu lubię dopracować deployment, bo zostawianie tego na koniec nie kończy się nigdy dobrze. Aplikacja nie ma dużego ruchu, więc najtańszy plan za 5 dolarów za miesiąc w &lt;a href=&quot;https://www.digitalocean.com&quot;&gt;DigitalOcean&lt;/a&gt; powinien wystarczyć. W sieci jest sporo artykułów nt. instalacji .NET Core na serwerach tej firmy, ale niestety nie są aktualne a sama instalacja sprawiła mi trochę kłopotu. Wpis ten powstał dlatego, żeby być może komuś pomóc zaoszczędzić trochę czasu.&lt;/p&gt;

&lt;h2 id=&quot;utworzenie-serwera&quot;&gt;Utworzenie serwera&lt;/h2&gt;

&lt;p&gt;Proces założenia konta w &lt;a href=&quot;https://www.digitalocean.com&quot;&gt;DigitalOcean&lt;/a&gt; pominę, bo nic zaskakującego w nim nie ma. Jeśli je mamy, trzeba stworzyć &lt;em&gt;Droplet&lt;/em&gt;. Kiedyś serwis miał prekonfigurowany serwer z .NET Core. Obecnie trzeba samemu trochę się namęczyć. Wybiorę czysty serwer z Ubuntu 16.04.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://wolanski.info/assets/20171126_01.png&quot; alt=&quot;Stworzenie dropleta&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Po chwili na maila przyjdą namiary na nowy serwer. Z konsoli:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;ssh root@adres.ip.serwera&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Zmieniamy hasło i już jesteśmy zalogowani na serwerze. Zainstalujmy tam sdk:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;curl https://packages.microsoft.com/keys/microsoft.asc | gpg &lt;span class=&quot;nt&quot;&gt;--dearmor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; microsoft.gpg
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;sh &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'echo &quot;deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-xenial-prod xenial main&quot; &amp;gt; /etc/apt/sources.list.d/dotnetdev.list'&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get update
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get install dotnet-sdk-2.0.3&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;stworzenie-przykładowej-aplikacji&quot;&gt;Stworzenie przykładowej aplikacji&lt;/h2&gt;

&lt;p&gt;Na maszynie developerskiej przygotujmy sobie aplikację. Z natury jestem leniwy, więc na początek umieszczę na serwerze aplikację wygenerowaną z template. Z linii komend polecenie:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;dotnet new mvc &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; NazwaProjektu&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I mamy wygenerowany projekt. Zróbmy jeszcze małą modyfikację. W klasie Startup w metodzie Configure na samym końcu dodajmy:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c#&quot; data-lang=&quot;c#&quot;&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;UseForwardedHeaders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ForwardedHeadersOptions&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ForwardedHeaders&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ForwardedHeaders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;XForwardedFor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ForwardedHeaders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;XForwardedProto&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Teraz go zbudujmy, żeby mieć co umieścić na serwerze. W tym celu znów w konsoli trzeba napisać:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;NazwaProjektu
dotnet publish &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; Release&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Po chwili w katalogu &lt;em&gt;NazwaProjektu\bin\Release\netcoreapp2.0\publish&lt;/em&gt; mamy aplikację, którą wrzucimy na serwer.&lt;/p&gt;

&lt;h2 id=&quot;konfiguracja-serwera&quot;&gt;Konfiguracja serwera&lt;/h2&gt;

&lt;p&gt;Dalsze kroki dość szczegółowo opisane są &lt;a href=&quot;https://docs.microsoft.com/en-us/aspnet/core/publishing/linuxproduction?tabs=aspnetcore2x&quot;&gt;tutaj&lt;/a&gt;, więc nie będę ich tłumaczył. Najpierw należy zainstalować i uruchomić serwer &lt;em&gt;nginx&lt;/em&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get install nginx
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;service nginx start&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Po tym zabiegu w przeglądarce pod adresem &lt;em&gt;http://adres.ip.naszego.dropleta&lt;/em&gt; powinna pojawić się strona startowa serwera &lt;em&gt;nginx&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Serwer trzeba przygotować do serwowania naszej aplikacji. Zawartość pliku &lt;em&gt;/etc/nginx/sites-available/default&lt;/em&gt; zamieniamy na następującą:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-nginx&quot; data-lang=&quot;nginx&quot;&gt;&lt;span class=&quot;k&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;listen&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;server_tokens&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;off&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_pass&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http://localhost:5000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_http_version&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Upgrade&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$http_upgrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Connection&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;keep-alive&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Host&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;kn&quot;&gt;proxy_cache_bypass&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$http_upgrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Żeby serwer załapał zmiany, należy go zrestartować:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;nginx &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; reload&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Teraz serwer wszelkie żądania na porcie 80 będzie przekierowywał na 5000. Wrzućmy naszą aplikację do katalogu /var/aspnetcore/naszaaplikacja i dodajmy monitoring. W tym celu stwórzmy plik &lt;em&gt;/etc/systemd/system/kestrel-naszaaplikacja.service&lt;/em&gt; o treści:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-nginx&quot; data-lang=&quot;nginx&quot;&gt;&lt;span class=&quot;k&quot;&gt;[Unit]&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;Description=Opis&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;naszej&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;aplikacji&lt;/span&gt;

&lt;span class=&quot;s&quot;&gt;[Service]&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;WorkingDirectory=/var/aspnetcore/naszaaplikacja&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;ExecStart=/usr/bin/dotnet&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/var/aspnetcore/naszaaplikacja/naszaaplikacja.dll&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;Restart=always&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;RestartSec=10&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Restart service after 10 seconds if dotnet service crashes
&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;SyslogIdentifier=dotnet-naszaaplikacja&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;User=www-data&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;Environment=ASPNETCORE_ENVIRONMENT=Production&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false&lt;/span&gt;

&lt;span class=&quot;s&quot;&gt;[Install]&lt;/span&gt;
&lt;span class=&quot;s&quot;&gt;WantedBy=multi-user.target&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Następnie należy uruchomić stworzoną krok wcześniej usługę:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;systemctl &lt;span class=&quot;nb&quot;&gt;enable &lt;/span&gt;kestrel-naszaaplikacja.service
systemctl start kestrel-naszaaplikacja.service&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Po tym kroku pod adresem &lt;em&gt;http://adres.ip.naszego.dropleta&lt;/em&gt; powinna pojawić się nasza aplikacja.&lt;/p&gt;

&lt;h2 id=&quot;https-z-lets-encrypt&quot;&gt;HTTPS z Let’s Encrypt&lt;/h2&gt;

&lt;p&gt;Wypadałoby zabezpieczyć stronę certyfikatem. Cały proces jest opisany &lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-16-04&quot;&gt;tutaj&lt;/a&gt;, więc będę się streszczał. Zainstalujmy certbota:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;add-apt-repository ppa:certbot/certbot
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get update
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get install python-certbot-nginx&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Dodajmy domenę do serwera. W tym celu należy w pliku &lt;em&gt;/etc/nginx/sites-available/default&lt;/em&gt; dodać:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-nginx&quot; data-lang=&quot;nginx&quot;&gt;&lt;span class=&quot;k&quot;&gt;server_name&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;example.com&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;www.example.com&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;a następnie przeładować serwer:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl reload nginx&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Otwórzmy ruch HTTPS przez firewall. W tym celu w konsoli należy uruchomić następujący zestaw poleceń:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;ufw &lt;span class=&quot;nb&quot;&gt;enable
sudo &lt;/span&gt;ufw allow &lt;span class=&quot;s1&quot;&gt;'OpenSSH'&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;ufw allow &lt;span class=&quot;s1&quot;&gt;'Nginx Full'&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;ufw delete allow &lt;span class=&quot;s1&quot;&gt;'Nginx HTTP'&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Teraz wystarczy pobrać certyfikat:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;certbot &lt;span class=&quot;nt&quot;&gt;--nginx&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; example.com &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; www.example.com&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pod adresem &lt;em&gt;https://example.com&lt;/em&gt; powinniśmy zobaczyć naszą stronę.&lt;/p&gt;</content><author><name></name></author><summary type="html">Niniejszym wpisem chciałbym rozpocząć cykl opisujący migrację małego projektu webowego napisanego dawno temu w języku Python do .NET Core 2.0. Trudności na samym początku napotkałem wiele. Wynikają one z tego, że aplikacja powstała dawno temu, a .NET Core nie znam. Króciutko o projekcie. Aplikację stworzyłem dawno temu. Wykorzystuje ona framework Flask, działa na bazie PostgreSQL i produkcyjnie działa na serwerze z Linuksem. Od dawna chciałem poznać .NET Core, więc zamiast odkopywać swoją znajomość języka Python, postanowiłem przepisać aplikację na ASP.NET Core 2.0. Aplikację chciałbym rozwijać w Visual Studio Code na Windows 10, a produkcyjnie aplikacja ma działać na Linux. Założenie jest takie, że aplikacja po migracji ma mieć dokładnie taki sam zakres funkcjonalności. Na samym początku projektu lubię dopracować deployment, bo zostawianie tego na koniec nie kończy się nigdy dobrze. Aplikacja nie ma dużego ruchu, więc najtańszy plan za 5 dolarów za miesiąc w DigitalOcean powinien wystarczyć. W sieci jest sporo artykułów nt. instalacji .NET Core na serwerach tej firmy, ale niestety nie są aktualne a sama instalacja sprawiła mi trochę kłopotu. Wpis ten powstał dlatego, żeby być może komuś pomóc zaoszczędzić trochę czasu. Utworzenie serwera Proces założenia konta w DigitalOcean pominę, bo nic zaskakującego w nim nie ma. Jeśli je mamy, trzeba stworzyć Droplet. Kiedyś serwis miał prekonfigurowany serwer z .NET Core. Obecnie trzeba samemu trochę się namęczyć. Wybiorę czysty serwer z Ubuntu 16.04. Po chwili na maila przyjdą namiary na nowy serwer. Z konsoli: ssh root@adres.ip.serwera Zmieniamy hasło i już jesteśmy zalogowani na serwerze. Zainstalujmy tam sdk: curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor &amp;gt; microsoft.gpg sudo mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg sudo sh -c 'echo &quot;deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-xenial-prod xenial main&quot; &amp;gt; /etc/apt/sources.list.d/dotnetdev.list' sudo apt-get update sudo apt-get install dotnet-sdk-2.0.3 Stworzenie przykładowej aplikacji Na maszynie developerskiej przygotujmy sobie aplikację. Z natury jestem leniwy, więc na początek umieszczę na serwerze aplikację wygenerowaną z template. Z linii komend polecenie: dotnet new mvc -o NazwaProjektu I mamy wygenerowany projekt. Zróbmy jeszcze małą modyfikację. W klasie Startup w metodzie Configure na samym końcu dodajmy: app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto }); Teraz go zbudujmy, żeby mieć co umieścić na serwerze. W tym celu znów w konsoli trzeba napisać: cd NazwaProjektu dotnet publish -c Release Po chwili w katalogu NazwaProjektu\bin\Release\netcoreapp2.0\publish mamy aplikację, którą wrzucimy na serwer. Konfiguracja serwera Dalsze kroki dość szczegółowo opisane są tutaj, więc nie będę ich tłumaczył. Najpierw należy zainstalować i uruchomić serwer nginx: sudo apt-get install nginx sudo service nginx start Po tym zabiegu w przeglądarce pod adresem http://adres.ip.naszego.dropleta powinna pojawić się strona startowa serwera nginx. Serwer trzeba przygotować do serwowania naszej aplikacji. Zawartość pliku /etc/nginx/sites-available/default zamieniamy na następującą: server { listen 80; server_tokens off; location / { proxy_pass http://localhost:5000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } } Żeby serwer załapał zmiany, należy go zrestartować: sudo nginx -s reload Teraz serwer wszelkie żądania na porcie 80 będzie przekierowywał na 5000. Wrzućmy naszą aplikację do katalogu /var/aspnetcore/naszaaplikacja i dodajmy monitoring. W tym celu stwórzmy plik /etc/systemd/system/kestrel-naszaaplikacja.service o treści: [Unit] Description=Opis naszej aplikacji [Service] WorkingDirectory=/var/aspnetcore/naszaaplikacja ExecStart=/usr/bin/dotnet /var/aspnetcore/naszaaplikacja/naszaaplikacja.dll Restart=always RestartSec=10 # Restart service after 10 seconds if dotnet service crashes SyslogIdentifier=dotnet-naszaaplikacja User=www-data Environment=ASPNETCORE_ENVIRONMENT=Production Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false [Install] WantedBy=multi-user.target Następnie należy uruchomić stworzoną krok wcześniej usługę: systemctl enable kestrel-naszaaplikacja.service systemctl start kestrel-naszaaplikacja.service Po tym kroku pod adresem http://adres.ip.naszego.dropleta powinna pojawić się nasza aplikacja. HTTPS z Let’s Encrypt Wypadałoby zabezpieczyć stronę certyfikatem. Cały proces jest opisany tutaj, więc będę się streszczał. Zainstalujmy certbota: sudo add-apt-repository ppa:certbot/certbot sudo apt-get update sudo apt-get install python-certbot-nginx Dodajmy domenę do serwera. W tym celu należy w pliku /etc/nginx/sites-available/default dodać: server_name example.com www.example.com; a następnie przeładować serwer: sudo systemctl reload nginx Otwórzmy ruch HTTPS przez firewall. W tym celu w konsoli należy uruchomić następujący zestaw poleceń: sudo ufw enable sudo ufw allow 'OpenSSH' sudo ufw allow 'Nginx Full' sudo ufw delete allow 'Nginx HTTP' Teraz wystarczy pobrać certyfikat: sudo certbot --nginx -d example.com -d www.example.com Pod adresem https://example.com powinniśmy zobaczyć naszą stronę.</summary></entry><entry><title type="html">Trac na Windows</title><link href="http://wolanski.info/2010/11/15/trac-na-windows.html" rel="alternate" type="text/html" title="Trac na Windows" /><published>2010-11-14T23:00:00+00:00</published><updated>2010-11-14T23:00:00+00:00</updated><id>http://wolanski.info/2010/11/15/trac-na-windows</id><content type="html" xml:base="http://wolanski.info/2010/11/15/trac-na-windows.html">&lt;p&gt;Kiedy &lt;a href=&quot;/2010/10/10/uzytkownicy-domenowi-w-visualsvn-server.html&quot;&gt;VisualSVN Server zainstalowałem tak, jak chciałem&lt;/a&gt;, zachciało mi się systemu do zarządzania błędami. Padło na &lt;a href=&quot;http://trac.edgewall.org/&quot;&gt;Trac&lt;/a&gt;. Do instalacji postanowiłem wykorzystać Apache VisualSVN Servera. Wiem, że mógłbym wziąć sobie paczkę z &lt;a href=&quot;http://www.visualsvn.com/files/VisualSVN-Server-Trac-2.1.1.21699.zip&quot;&gt;Trac dla VisualSVN Server&lt;/a&gt;, ale nie zrobiłem tego, bo po pierwsze, nie zawiera najnowszej, stabilnej wersji Trac, a po drugie – niezależna instalacja Trac uprości aktualizację narzędzia w przyszłości.&lt;/p&gt;

&lt;h2 id=&quot;przygotowanie-środowiska&quot;&gt;Przygotowanie środowiska&lt;/h2&gt;
&lt;p&gt;Zainstalowałem po kolei używając defaultowych ścieżek:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.activestate.com/activepython/downloads&quot;&gt;ActivePython 2.5.5.7&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://pypi.python.org/packages/2.5/s/setuptools/setuptools-0.6c11.win32-py2.5.exe#md5=cb0cd7b844bed5106aeb0d4583848b1f&quot;&gt;setuptools 0.6c11 dla Python 2.5&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://ftp.edgewall.com/pub/genshi/Genshi-0.6.win32.exe&quot;&gt;Genshi 0.6&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://ftp.edgewall.com/pub/trac/Trac-0.12.win32.exe&quot;&gt;Trac 0.12&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Niestety &lt;a href=&quot;http://subversion.tigris.org/files/documents/15/47858/svn-python-1.6.6.win32-py2.5.exe&quot;&gt;najnowsze bindingi SVN dla Pythona 2.5&lt;/a&gt; nie pomogą w integracji Trac z VisualSVN Server (gdzieś znalazłem informację, że winne są zmodyfikowane biblioteki SVN w VisualSVN Server). Ale poradziłem sobie z tym pobierając wspomnianą paczkę &lt;a href=&quot;http://www.visualsvn.com/files/VisualSVN-Server-Trac-2.1.1.21699.zip&quot;&gt;Trac dla VisualSVN Server&lt;/a&gt; i kopiując z archiwum katalogi &lt;em&gt;trac\python\Lib\site-packages\libsvn&lt;/em&gt; oraz &lt;em&gt;trac\python\Lib\site-packages\svn&lt;/em&gt; do &lt;em&gt;c:\Python25\Lib\site-packages&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&quot;konfiguracja-trac&quot;&gt;Konfiguracja Trac&lt;/h2&gt;
&lt;p&gt;Z wiersza poleceń wykonujemy:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;c:&lt;span class=&quot;se&quot;&gt;\P&lt;/span&gt;ython25&lt;span class=&quot;se&quot;&gt;\S&lt;/span&gt;cripts&lt;span class=&quot;se&quot;&gt;\t&lt;/span&gt;rac-admin.exe c:&lt;span class=&quot;se&quot;&gt;\t&lt;/span&gt;rac initenv&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Na pytania instalatora odpowiadamy następująco:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;Project Name: Moja_nazwa
Database connection string &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;sqlite:db/trac.db]: &amp;lt;enter&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Do katalogu &lt;em&gt;c:\trac&lt;/em&gt; dajemy prawa do odczytu i zapisu użytkownikowi, na którym uruchomiona jest usługa.&lt;/p&gt;

&lt;p&gt;Na tym etapie można już uruchomić webserwer Traca poleceniem:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;tracd &lt;span class=&quot;nt&quot;&gt;--port&lt;/span&gt; 8000 c:&lt;span class=&quot;se&quot;&gt;\t&lt;/span&gt;rac&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;My chcemy jednak, żeby Trac serwowany był przez Apache. W związku z tym trzeba doinstalować &lt;a href=&quot;http://www.apache.org/dist/httpd/modpython/win/3.3.1/mod_python-3.3.1.win32-py2.5-Apache2.2.exe&quot;&gt;mod_python dla Apache 2.2 i Python 2.5&lt;/a&gt;. Instalator zapyta nas, gdzie jest zainstalowany Apache. Chcemy wykorzystać serwer dostarczany razem z VisualSVN Server. W związku z tym tworzymy katalog &lt;em&gt;modules&lt;/em&gt; w &lt;em&gt;C:\Program Files\VisualSVN Server\bin&lt;/em&gt; i wskazujemy instalatorowi katalog &lt;em&gt;C:\Program Files\VisualSVN Server\bin&lt;/em&gt;, a w konfiguracji Apache &lt;em&gt;C:\Program Files\VisualSVN Server\conf\httpd.conf&lt;/em&gt; dodajemy linie:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plain&quot; data-lang=&quot;plain&quot;&gt;LoadModule python_module bin/modules/mod_python.so&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Dodajemy do konfiguracji Apache katalog Trac:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Location&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;/trac&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    SetHandler mod_python
    PythonInterpreter main_interpreter
    PythonHandler trac.web.modpython_frontend 
    PythonOption TracEnv &quot;C:/trac&quot;
    PythonOption TracUriRoot /trac
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Location&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Chcemy, aby w Trac mogli się zalogować użytkownicy domenowi. W związku z tym w konfiguracji dodajemy jeszcze poniższy fragment:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;Location&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;/trac/login&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    AuthType SSPI
    AuthName &quot;Trac&quot;
    SSPIAuth On
    SSPIAuthoritative On
    SSPIDomain NAZWA_DOMENY
    SSPIOfferBasic Off
    Require valid-user
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/Location&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pod adresem &lt;em&gt;http://nazwa_hosta/trac&lt;/em&gt; mamy dostęp do Trac.&lt;/p&gt;</content><author><name></name></author><summary type="html">Kiedy VisualSVN Server zainstalowałem tak, jak chciałem, zachciało mi się systemu do zarządzania błędami. Padło na Trac. Do instalacji postanowiłem wykorzystać Apache VisualSVN Servera. Wiem, że mógłbym wziąć sobie paczkę z Trac dla VisualSVN Server, ale nie zrobiłem tego, bo po pierwsze, nie zawiera najnowszej, stabilnej wersji Trac, a po drugie – niezależna instalacja Trac uprości aktualizację narzędzia w przyszłości.</summary></entry><entry><title type="html">External Tools w Visual Studio 2010 Express</title><link href="http://wolanski.info/2010/10/25/external-tools-w-visual-studio-2010-express.html" rel="alternate" type="text/html" title="External Tools w Visual Studio 2010 Express" /><published>2010-10-24T23:00:00+00:00</published><updated>2010-10-24T23:00:00+00:00</updated><id>http://wolanski.info/2010/10/25/external-tools-w-visual-studio-2010-express</id><content type="html" xml:base="http://wolanski.info/2010/10/25/external-tools-w-visual-studio-2010-express.html">&lt;p&gt;Zainstalowałem sobie Visual Studio 2010 Express. Jest kilka narzędzi, które lubię mieć zdefiniowane jako External Tools. Wchodzę zwyczajowo w menu &lt;em&gt;Tools&lt;/em&gt; i… nie ma opcji &lt;em&gt;External Tools&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Okazało się, że autorzy nie usunęli tego wielce przydatnego narzędzia, ale je ukryli. Wystarczy włączyć opcję &lt;em&gt;Tools &amp;gt; Settings &amp;gt; Expert Settings&lt;/em&gt; i &lt;em&gt;External Tools&lt;/em&gt; wrócą na swoje miejsce.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://wolanski.info/assets/20101025.png&quot; alt=&quot;Expert Settings&quot; /&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">Zainstalowałem sobie Visual Studio 2010 Express. Jest kilka narzędzi, które lubię mieć zdefiniowane jako External Tools. Wchodzę zwyczajowo w menu Tools i… nie ma opcji External Tools.</summary></entry><entry><title type="html">Użytkownicy domenowi w VisualSVN Server</title><link href="http://wolanski.info/2010/10/10/uzytkownicy-domenowi-w-visualsvn-server.html" rel="alternate" type="text/html" title="Użytkownicy domenowi w VisualSVN Server" /><published>2010-10-09T23:00:00+00:00</published><updated>2010-10-09T23:00:00+00:00</updated><id>http://wolanski.info/2010/10/10/uzytkownicy-domenowi-w-visualsvn-server</id><content type="html" xml:base="http://wolanski.info/2010/10/10/uzytkownicy-domenowi-w-visualsvn-server.html">&lt;p&gt;Korzystałem kiedyś z &lt;a href=&quot;http://www.visualsvn.com/server&quot;&gt;VisualSVN Server&lt;/a&gt;, ale wówczas wystarczała mi autoryzacja &lt;em&gt;Basic&lt;/em&gt;, czyli każdy użytkownik ma swoją nazwę i hasło. Rozwiązanie to jest niewygodne, jeśli działamy w ramach domeny Windows. Po co nowa nazwa i hasło użytkownika specjalnie dla Subversion?&lt;/p&gt;

&lt;p&gt;VisualSVN Server w darmowej wersji Standard Edition nie udostępnia możliwości zmiany sposobu autoryzacji użytkowników z poziomu konsoli serwera. Trzeba zakupić wersję Enterprise Edition za 950 dolarów. Jako, że nie znalazłem na stronie narzędzia informacji, że modyfikacja serwera jest niezgodna z licencją, postanowiłem spróbować zmusić darmową wersję do autoryzacji użytkowników domenowych.&lt;/p&gt;

&lt;p&gt;Ściągnąłem i zainstalowałem &lt;a href=&quot;http://www.visualsvn.com/files/VisualSVN-Server-2.1.4.msi&quot;&gt;najnowszą wersję&lt;/a&gt; narzędzia (zachowałem defaultowe ustawienia instalatora).&lt;/p&gt;

&lt;p&gt;Aby móc skorzystać z logowania użytkowników domenowych w darmowej wersji VisualSVN Server należy ściągnąć moduł &lt;a href=&quot;http://sourceforge.net/projects/mod-auth-sspi/&quot;&gt;mod_auth_sspi&lt;/a&gt;. Archiwum rozpakowujemy do katalogu &lt;em&gt;C:\Program Files\VisualSVN Server\bin&lt;/em&gt;. Trzeba jeszcze zmienić konfigurację Apache znajdującą się w pliku &lt;em&gt;C:\Program Files\VisualSVN Server\conf&lt;/em&gt;. Deklarujemy załadowanie modułu:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-apacheconf&quot; data-lang=&quot;apacheconf&quot;&gt;LoadModule sspi_auth_module bin/mod_auth_sspi.so&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;W tym samym pliku linie:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-apacheconf&quot; data-lang=&quot;apacheconf&quot;&gt;AuthType Basic
AuthBasicProvider file
AuthUserFile &quot;C:/Repositories/htpasswd&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;zastępujemy następującymi:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-apacheconf&quot; data-lang=&quot;apacheconf&quot;&gt;AuthType SSPI
SSPIAuth On
SSPIAuthoritative On
SSPIDomain NAZWA_DOMENY
SSPIOfferBasic On&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Użytkownika o nazwie NAZWA_DOMENY\uzytkownik można dodać zarówno z poziomu konsoli VisualSVN Server, jak i edytując plik &lt;em&gt;C:\Repositories\authz&lt;/em&gt;.&lt;/p&gt;</content><author><name></name></author><summary type="html">Korzystałem kiedyś z VisualSVN Server, ale wówczas wystarczała mi autoryzacja Basic, czyli każdy użytkownik ma swoją nazwę i hasło. Rozwiązanie to jest niewygodne, jeśli działamy w ramach domeny Windows. Po co nowa nazwa i hasło użytkownika specjalnie dla Subversion?</summary></entry><entry><title type="html">Wykorzystanie Rake do budowy projektu .NET</title><link href="http://wolanski.info/2010/10/06/wykorzystanie-rake-do-budowy-projektu-net.html" rel="alternate" type="text/html" title="Wykorzystanie Rake do budowy projektu .NET" /><published>2010-10-05T23:00:00+00:00</published><updated>2010-10-05T23:00:00+00:00</updated><id>http://wolanski.info/2010/10/06/wykorzystanie-rake-do-budowy-projektu-net</id><content type="html" xml:base="http://wolanski.info/2010/10/06/wykorzystanie-rake-do-budowy-projektu-net.html">&lt;p&gt;Przy okazji rozpoczęcia nowego projektu chciałem w prosty sposób umożliwić budowę projektu z linii komend. Do tej pory używałem do tego NAnta, ale tym razem chciałem dać szansę narzędziu, z którego korzysta wiele projektów open-source: Rake.&lt;/p&gt;

&lt;p&gt;Wymagania miałem dwa: kompilacja projektu wykorzystującego .NET 4.0 oraz uruchomienie testów jednostkowych wykorzystujących bibliotekę &lt;a href=&quot;http://xunit.codeplex.com&quot;&gt;xUnit.net&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Rozpocząłem od ściągnięcia i instalacji środowiska &lt;a href=&quot;http://rubyforge.org/frs/download.php/72170/rubyinstaller-1.9.2-p0.exe&quot;&gt;Ruby dla Windows&lt;/a&gt;. Aby nie wynajdywać od nowa koła, zainstalowałem bibliotekę &lt;a href=&quot;http://albacorebuild.net&quot;&gt;Albacore&lt;/a&gt;, która znacznie ułatwia pracę ze skryptem wykorzystywanym do budowy projektu .NET. Aby zainstalować Albacore, należy w wierszu poleceń uruchomić komendę:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bat&quot; data-lang=&quot;bat&quot;&gt;gem install albacore&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Mój projekt ma w uproszczeniu taką strukturę:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;MyApplication\lib&lt;/li&gt;
  &lt;li&gt;MyApplication\lib\xunit-1.6.1&lt;/li&gt;
  &lt;li&gt;MyApplication\source&lt;/li&gt;
  &lt;li&gt;MyApplication\source\MySolution.sln&lt;/li&gt;
  &lt;li&gt;rakefile.rb&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aby nie musieć wskazywać w wywołaniu w wierszu poleceń narzędzia rake pliku ze zadaniami, skrypt budujący projekt należy utworzyć w pliku o nazwie rakefile, Rakefile, rakefile.rb lub Rakefile.rb. Ja wybrałem sobie nazwę &lt;em&gt;rakefile.rb&lt;/em&gt;. Dzięki temu, aby wywołać defaultowe zadanie dla Rake, będąc w katalogu projektu, w wierszu poleceń wystarczy wykonać komendę:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bat&quot; data-lang=&quot;bat&quot;&gt;rake nazwa_zadania&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Na początku pliku &lt;em&gt;rakefile.rb&lt;/em&gt; deklarujemy użycie biblioteki Albacore:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'albacore'&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Następnie dodajemy zadanie budujące solucję:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Build solution&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;msbuild&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:build&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;msb&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;msb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;path_to_command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'windir'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Microsoft.NET'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;'Framework'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;s1&quot;&gt;'v4.0.30319'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'MSBuild.exe'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;msb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;properties&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:configuration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:Debug&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;msb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;targets&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:Clean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:Build&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;msb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;solution&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;source/MySolution.sln&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Pierwsza linijka zawiera opis zadania i ma tylko znaczenie dokumentujące. W drugiej linii &lt;em&gt;msbuild&lt;/em&gt; oznacza nazwę zadania z Albacore, a &lt;em&gt;:build&lt;/em&gt; alias dla tego zadania. Dzięki temu, aby zbudować projekt, w wierszu poleceń należy wykonać komendę:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bat&quot; data-lang=&quot;bat&quot;&gt;rake build&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Jeśli pominąłbym alias, to budowę solucji mógłbym uruchomić tak:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bat&quot; data-lang=&quot;bat&quot;&gt;rake msbuild&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Trzecia linia nie wygląda najlepiej, ale jest niestety konieczna, jeśli projekt używa .NET 4.0. A to dlatego, że zadanie msbuild z Albacore domyślnie używa .NETa 3.5 i wykonanie go, bez zdefiniowania ścieżki do msbuild.exe z właściwego frameworka, zakończy się błędem.&lt;/p&gt;

&lt;p&gt;Pozostała część kodu zadania jest oczywista: chcemy zbudować solucję MySolution.sln w trybie Debug, uprzednio robiąc porządki.&lt;/p&gt;

&lt;p&gt;Zadanie uruchomienia testów jednostkowych jest krótkie:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Run unit tests&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;xunit&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:build&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xunit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;xunit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;path_to_command&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;lib/xunit-1.6.1/xunit.console.clr4.exe&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;xunit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;assembly&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;unit_tests_file.dll&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Podajemy ścieżkę do narzędzia uruchamiającego z wiersza poleceń testy jednostkowe wykorzystujące bibliotekę xUnit.net i wskazujemy bibliotekę z naszymi testami.&lt;/p&gt;

&lt;p&gt;Aby uruchomić testy jednostkowe, w wierszu poleceń należy wykonać komendę:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bat&quot; data-lang=&quot;bat&quot;&gt;rake test&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Jeśli mamy jakieś zadanie, które wykonujemy wyjątkowo często, to przed definicją zadań można dodać linię:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:default&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:nazwa_lub_alias_zadania&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Dzięki temu, aby wykonać to zadanie, w wierszu poleceń wystarczy wywołać komendę:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bat&quot; data-lang=&quot;bat&quot;&gt;rake&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nie bawiłem się zbyt długo tym rozwiązaniem, ale póki co, przynajmniej dla mnie, nie ma porównania czytelność skryptu napisanego w Ruby z plikami dla NAnta czy też msbuilda. Chcę jeszcze dodać kilka różnych zadań i zintegrować wszystko z narzędziem CI. Zobaczymy, czy się uda.&lt;/p&gt;</content><author><name></name></author><summary type="html">Przy okazji rozpoczęcia nowego projektu chciałem w prosty sposób umożliwić budowę projektu z linii komend. Do tej pory używałem do tego NAnta, ale tym razem chciałem dać szansę narzędziu, z którego korzysta wiele projektów open-source: Rake.</summary></entry><entry><title type="html">Recenzja: Debugowanie. Jak wyszukiwać i naprawiać błędy w kodzie oraz im zapobiegać</title><link href="http://wolanski.info/2010/09/30/recenzja-debugowanie-jak-wyszukiwac-i-naprawiac-bledy-w-kodzie-oraz-im-zapobiegac.html" rel="alternate" type="text/html" title="Recenzja: Debugowanie. Jak wyszukiwać i naprawiać błędy w kodzie oraz im zapobiegać" /><published>2010-09-29T23:00:00+00:00</published><updated>2010-09-29T23:00:00+00:00</updated><id>http://wolanski.info/2010/09/30/recenzja-debugowanie-jak-wyszukiwac-i-naprawiac-bledy-w-kodzie-oraz-im-zapobiegac</id><content type="html" xml:base="http://wolanski.info/2010/09/30/recenzja-debugowanie-jak-wyszukiwac-i-naprawiac-bledy-w-kodzie-oraz-im-zapobiegac.html">&lt;p&gt;Debugowanie jest procesem niezwykle złożonym. &lt;a href=&quot;http://www.amazon.com/Debugging-Applications-Microsoft-Windows-Pro-Developer/dp/0735615365&quot;&gt;Istnieją&lt;/a&gt; &lt;a href=&quot;http://www.amazon.com/Advanced-NET-Debugging-Mario-Hewardt/dp/0321578899&quot;&gt;publikacje&lt;/a&gt; opisujące techniczne aspekty wyszukiwania błędów w kodzie w ten właśnie sposób. Czasami warto jednak zapoznać się z jakimś zagadnieniem w oderwaniu się od konkretnej technologii. Taką właśnie pozycją jest &lt;a href=&quot;http://helion.pl/ksiazki/debugowanie_jak_wyszukiwac_i_naprawiac_bledy_w_kodzie_oraz_im_zapobiegac_paul_butcher,debugo.htm&quot;&gt;Debugowanie. Jak wyszukiwać i naprawiać błędy w kodzie oraz im zapobiegać&lt;/a&gt; (oryg. &lt;a href=&quot;http://www.amazon.com/Debug-Repair-Prevent-Pragmatic-Programmers/dp/193435628X&quot;&gt;Debug It!: Find, Repair, and Prevent Bugs in Your Code&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://wolanski.info/assets/20100930_01.jpg&quot; alt=&quot;Recenzowana książka&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Książka nie jest opisem jakiegoś narzędzia wspomagającego debugowanie, platformy uruchomieniowej czy też języka programowania (kodu jest bardzo niewiele). Autor skupił się raczej na mentalnym podejściu programisty do wyszukiwania błędów w aplikacji.&lt;/p&gt;

&lt;p&gt;Książka składa się z 3 części: &lt;em&gt;Istota problemu&lt;/em&gt;, &lt;em&gt;Szersza perspektywa&lt;/em&gt; oraz &lt;em&gt;Debug-Fu&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;W części pierwszej autor opisuje metodę empiryczną debugowania tj. reprodukcję błędów, diagnozowanie ich przyczyny, poprawki oraz refleksja nad tym, co zrobić, aby w przyszłości nie musieć usuwać takich samych usterek.&lt;/p&gt;

&lt;p&gt;Część druga poświęcona jest miejscu debugowania w całym procesie tworzenia aplikacji m.in. współpracy z użytkownikami aplikacji, innymi zespołami, czy błędy powinny być naprawiane od razu po ich stwierdzeniu, czy może lepiej dodać jakąś nową funkcjonalność itd.&lt;/p&gt;

&lt;p&gt;W części trzeciej autor skupił się na technicznym aspekcie procesu debugowania: stworzeniu odpowiedniego środowiska umożliwiającego skuteczne debugowanie, systemach kontroli wersji, ciągłej integracji, testach jednostkowych, użyteczności asercji, kontraktów i bibliotek ułatwiających logowanie itd.&lt;/p&gt;

&lt;h2 id=&quot;podobało-mi-się&quot;&gt;Podobało mi się&lt;/h2&gt;
&lt;p&gt;Książki podejmujące tematykę debugowania od strony technicznej nie są, przynajmniej dla mnie, lekką lekturą. Trzeba znaleźć wolną chwilę, aby wiedzę zgłębiać w skupieniu i koniecznie przy komputerze. Opisywana pozycja ma tą cechę, że takich fragmentów jest naprawdę niewiele.&lt;/p&gt;

&lt;p&gt;Dodatkowo jej gabaryty zdecydowanie nie przeszkadzają w czytaniu np. w drodze do pracy.&lt;/p&gt;

&lt;h2 id=&quot;nie-podobało-mi-się&quot;&gt;Nie podobało mi się&lt;/h2&gt;
&lt;p&gt;Choć tematyka książki sugeruje, że powinno się ją czytać lekko i przyjemnie, to tak niestety nie było w moim przypadku. Nie czytałem oryginału, ale wydaje mi się, że to kwestia przekładu.&lt;/p&gt;

&lt;p&gt;Dawno nie czytałem nic wydawanego przez &lt;a href=&quot;http://helion.pl&quot;&gt;Helion&lt;/a&gt; Zraziły mnie do tego skutecznie przede wszystkim nieudane tłumaczenia. Do zakupu opisywanej pozycji skusiła mnie cena dużo niższa niż w &lt;a href=&quot;http://www.amazon.com&quot;&gt;Amazon&lt;/a&gt;. Następnym razem się zastanowię, bo w tym przypadku skąpstwo nie popłaciło.&lt;/p&gt;

&lt;p&gt;Moim zdaniem nie powinno się tłumaczyć niektórych angielskich zwrotów. “Zaślepki testowe” (ang. test doubles), “atrapy” (ang. mocks), “namiastki” (ang. stubs) czy też mój ulubiony “profilator” (ang. profiler), to tylko niektóre kwiatki.&lt;/p&gt;

&lt;h2 id=&quot;czego-się-nauczyłem&quot;&gt;Czego się nauczyłem&lt;/h2&gt;
&lt;p&gt;Lubię od czasu do czasu przeczytać trochę mniej techniczną książkę. Lektura &lt;em&gt;Debugowania…&lt;/em&gt; zmusiła mnie do refleksji nad tym, czy w codziennej pracy właściwie podchodzę do procesu diagnozy przyczyn błędów w rozwijanych i utrzymywanych aplikacjach.&lt;/p&gt;

&lt;p&gt;Polecam, ale raczej w języku angielskim.&lt;/p&gt;</content><author><name></name></author><summary type="html">Debugowanie jest procesem niezwykle złożonym. Istnieją publikacje opisujące techniczne aspekty wyszukiwania błędów w kodzie w ten właśnie sposób. Czasami warto jednak zapoznać się z jakimś zagadnieniem w oderwaniu się od konkretnej technologii. Taką właśnie pozycją jest Debugowanie. Jak wyszukiwać i naprawiać błędy w kodzie oraz im zapobiegać (oryg. Debug It!: Find, Repair, and Prevent Bugs in Your Code).</summary></entry><entry><title type="html">Zaznaczanie błędów w Visual Studio a deinstalacja Resharpera</title><link href="http://wolanski.info/2010/09/24/zaznaczanie-bledow-w-visual-studio-a-deinstalacja-resharpera.html" rel="alternate" type="text/html" title="Zaznaczanie błędów w Visual Studio a deinstalacja Resharpera" /><published>2010-09-23T23:00:00+00:00</published><updated>2010-09-23T23:00:00+00:00</updated><id>http://wolanski.info/2010/09/24/zaznaczanie-bledow-w-visual-studio-a-deinstalacja-resharpera</id><content type="html" xml:base="http://wolanski.info/2010/09/24/zaznaczanie-bledow-w-visual-studio-a-deinstalacja-resharpera.html">&lt;p&gt;Po odinstalowaniu Resharpera zauważyłem, że w edytorze nie są zaznaczane błędy. Chwila grzebania w opcjach i udało się doprowadzić Visual Studio do stanu sprzed instalacji narzędzia.&lt;/p&gt;

&lt;p&gt;Wystarczy wybrać &lt;strong&gt;Tools&lt;/strong&gt; &amp;gt; &lt;strong&gt;Options&lt;/strong&gt; &amp;gt; &lt;strong&gt;Text Editor&lt;/strong&gt; &amp;gt; &lt;strong&gt;C#&lt;/strong&gt; &amp;gt; &lt;strong&gt;Advanced&lt;/strong&gt; i zaznaczyć w &lt;strong&gt;Editor Helper&lt;/strong&gt; opcje &lt;em&gt;Underline errors in the editor&lt;/em&gt; oraz &lt;em&gt;Show live semantic errors&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://wolanski.info/assets/20100924.png&quot; alt=&quot;Editor helper&quot; /&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">Po odinstalowaniu Resharpera zauważyłem, że w edytorze nie są zaznaczane błędy. Chwila grzebania w opcjach i udało się doprowadzić Visual Studio do stanu sprzed instalacji narzędzia.</summary></entry><entry><title type="html">NHibernate i wyjątki ADO</title><link href="http://wolanski.info/2010/03/03/nhibernate-i-wyjatki-ado.html" rel="alternate" type="text/html" title="NHibernate i wyjątki ADO" /><published>2010-03-02T23:00:00+00:00</published><updated>2010-03-02T23:00:00+00:00</updated><id>http://wolanski.info/2010/03/03/nhibernate-i-wyjatki-ado</id><content type="html" xml:base="http://wolanski.info/2010/03/03/nhibernate-i-wyjatki-ado.html">&lt;p&gt;Próba zapisania obiektu naruszającego więzy integralności przy użyciu biblioteki NHibernate spowoduje wygenerowanie wyjątku GenericADOException. Dopiero sięgając do wartości właściwości InnerException możemy przekonać się, co jest przyczyną niepowodzenia. Istnieje jednak sposób na zastąpienie standardowego wyjątku własnym.&lt;/p&gt;

&lt;p&gt;W przykładzie użyję prostej klasy:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c#&quot; data-lang=&quot;c#&quot;&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Id&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;virtual&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Age&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; 
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;i mapowania:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c#&quot; data-lang=&quot;c#&quot;&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserMap&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ClassMap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;UserMap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Unique&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Age&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; 
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Próba zapisu dwóch użytkowników o tej samej nazwie kończy się wyjątkiem GenericADOException:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c#&quot; data-lang=&quot;c#&quot;&gt;&lt;span class=&quot;na&quot;&gt;[Test, ExpectedException(typeof(NHibernate.Exceptions.GenericADOException))]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CannotAddTwoUsersWithTheSameUsername&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;janKowalski1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Jan Kowalski&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;janKowalski2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Jan Kowalski&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DataAccessFacade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;OpenSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tx&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;BeginTransaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;janKowalski1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;janKowalski2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;tx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Commit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Oczywiście można sprawdzić, co jest przyczyną błędu przy zapisie:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c#&quot; data-lang=&quot;c#&quot;&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;janKowalski1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Jan Kowalski&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;janKowalski2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Jan Kowalski&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DataAccessFacade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;OpenSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tx&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;BeginTransaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;janKowalski1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;janKowalski2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;tx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Commit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GenericADOException&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InnerException&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SQLiteException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sqLiteException&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SQLiteException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InnerException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sqLiteException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ErrorCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SQLiteErrorCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Constraint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;c1&quot;&gt;// do something with constaint exception&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Jednak ten sposób uzależniamy nasz kod od konkretnego providera. Ewentualna zmiana silnika bazy danych powoduje konieczność zmian w wielu klasach warstwy dostępu do danych. Poza tym obsługa wydawałoby się trywialnego problemu wymaga każdorazowo napisania wielu linii kodu.&lt;/p&gt;

&lt;p&gt;Czy powyższy fragment nie mógłby wyglądać tak:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c#&quot; data-lang=&quot;c#&quot;&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;janKowalski1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Jan Kowalski&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;janKowalski2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Jan Kowalski&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DataAccessFacade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;OpenSession&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tx&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;BeginTransaction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;janKowalski1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;session&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Save&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;janKowalski2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;tx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Commit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ConstraintViolationException&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// do something with constaint exception&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;O ile krócej, prawda? I nie obchodzi nas, że w danej chwili wykorzystujemy bazę danych SQLite, Oracle czy MSSQL Server.&lt;/p&gt;

&lt;p&gt;Aby osiągnąć ten efekt używając NHibernate, musimy stworzyć własną implementację interfejsu ISQLExceptionConverter:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c#&quot; data-lang=&quot;c#&quot;&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyCustomSQLExceptionConverter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ISQLExceptionConverter&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Convert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AdoExceptionContextInfo&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;adoExceptionContextInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ADOExceptionHelper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ExtractDbException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;adoExceptionContextInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SqlException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SQLiteException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ErrorCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SQLiteErrorCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Constraint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; 
                        &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;NHibernate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Exceptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ConstraintViolationException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                            &lt;span class=&quot;n&quot;&gt;adoExceptionContextInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                            &lt;span class=&quot;n&quot;&gt;ex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InnerException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                            &lt;span class=&quot;n&quot;&gt;adoExceptionContextInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                            &lt;span class=&quot;k&quot;&gt;null&lt;/span&gt;
                        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SQLStateConverter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;HandledNonSpecificException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;adoExceptionContextInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SqlException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;adoExceptionContextInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;adoExceptionContextInfo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sql&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Metoda ADOExceptionHelper.ExtractDbException wyłuskuje pierwszy wyjątek System.Data.Common.DbException z drzewa wraz z jego InnerExceptionami. Reszta jest chyba oczywista.&lt;/p&gt;

&lt;p&gt;Konfigurujemy jeszcze NHibernate w pliku konfiguracyjnym:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;property&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;sql_exception_converter&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    NameSpace.MyCustomSQLExceptionConverter, AssemblyName
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/property&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;albo za pomocą Fluent NHibernate:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c#&quot; data-lang=&quot;c#&quot;&gt;&lt;span class=&quot;n&quot;&gt;_configuration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;SetProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;NHibernate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Cfg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SqlExceptionConverter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MyCustomSQLExceptionConverter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AssemblyQualifiedName&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;</content><author><name></name></author><summary type="html">Próba zapisania obiektu naruszającego więzy integralności przy użyciu biblioteki NHibernate spowoduje wygenerowanie wyjątku GenericADOException. Dopiero sięgając do wartości właściwości InnerException możemy przekonać się, co jest przyczyną niepowodzenia. Istnieje jednak sposób na zastąpienie standardowego wyjątku własnym.</summary></entry><entry><title type="html">NHibernate.Validator: komunikaty przyjazne dla użytkownika</title><link href="http://wolanski.info/2010/01/16/nhibernate-validator-komunikaty-przyjazne-dla-uzytkownika.html" rel="alternate" type="text/html" title="NHibernate.Validator: komunikaty przyjazne dla użytkownika" /><published>2010-01-15T23:00:00+00:00</published><updated>2010-01-15T23:00:00+00:00</updated><id>http://wolanski.info/2010/01/16/nhibernate-validator-komunikaty-przyjazne-dla-uzytkownika</id><content type="html" xml:base="http://wolanski.info/2010/01/16/nhibernate-validator-komunikaty-przyjazne-dla-uzytkownika.html">&lt;p&gt;Jakiś czas temu przedstawiłem &lt;a href=&quot;/2009/10/31/nhibernate-validator-windows-forms-i-errorprovider.html&quot;&gt;sposób użycia biblioteki NHibernate.Validator w aplikacji Windows Forms&lt;/a&gt;. Miał on jedną zasadniczą wadę: komunikaty walidatora były takie, jakie stworzyli autorzy biblioteki.&lt;/p&gt;

&lt;p&gt;Przypomnę, że &lt;a href=&quot;/2009/10/20/nhibernate-validator-i-windows-forms.html&quot;&gt;poprzednio&lt;/a&gt; stworzyliśmy użytkownika z właściwościami: imię, nazwisko i wiek. Założyliśmy, że imię i nazwisko nie mogą być puste i użytkownik musi mieć przynajmniej 18 lat:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c#&quot; data-lang=&quot;c#&quot;&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FirstName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LastName&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Age&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserValidationDef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ValidationDef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;UserValidationDef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FirstName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NotNullableAndNotEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LastName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NotNullableAndNotEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Age&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GreaterThanOrEqualTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Niewypełnienie imienia i nazwiska skutkowało niskopoziomowym komunikatem: &lt;em&gt;nie może być nieokreślony i pusty&lt;/em&gt;, natomiast wpisanie wieku mniejszego niż 18: &lt;em&gt;musi być większe lub równe od 18&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Chciałbym, aby komunikaty były bardziej przyjazne dla użytkownikowa np. &lt;em&gt;Imię jest wymagane&lt;/em&gt; czy też &lt;em&gt;Wiek musi być większy lub równy 18&lt;/em&gt;. Można dostosować komunikaty dla każdej reguły:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c#&quot; data-lang=&quot;c#&quot;&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserValidationDef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ValidationDef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;UserValidationDef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FirstName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NotNullableAndNotEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Imię jest wymagane.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LastName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NotNullableAndNotEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Nazwisko jest wymagane.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Age&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GreaterThanOrEqualTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Wiek musi być większy lub równy 18.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Rozwiązanie jest zadowalające dla niewielkiej aplikacji. W bardziej skomplikowanej kosmetyczna zmiana &lt;em&gt;jest wymagane&lt;/em&gt; na &lt;em&gt;musi być wypełnione&lt;/em&gt; wymaga przejrzenia i aktualizacji kilku/kilkunastu/kilkudziesięciu klas. Autorzy przewidzieli taki przypadek: komunikaty można zdefiniować w zewnętrznym pliku zasobów.&lt;/p&gt;

&lt;p&gt;Dodajmy plik zasobów do projektu:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://wolanski.info/assets/20100116_01.png&quot; alt=&quot;Przykładowy projekt&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Definiujemy komentarze przyjazne dla użytkownika:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://wolanski.info/assets/20100116_02.png&quot; alt=&quot;Zasoby&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Obrazek chyba nie wymaga wyjaśnień, może poza &lt;em&gt;{Value}&lt;/em&gt; w komunikacie &lt;em&gt;message.user.age.GreaterThanOrEqualTo&lt;/em&gt;. Jest to właściwość publiczna atrybutu &lt;a href=&quot;https://nhcontrib.svn.sourceforge.net/svnroot/nhcontrib/trunk/src/NHibernate.Validator/src/NHibernate.Validator/Constraints/MinAttribute.cs&quot;&gt;MinAttribute&lt;/a&gt; wykorzystywanego w metodzie GreaterThanOrEqualTo. Dzięki temu argument wspomnianej metody będzie przekazywany do komunikatu. Jeśli zmieni się wartość dopuszczalnego wieku, wystarczy edycja reguły bez konieczności uaktualniania pliku z komunikatami.&lt;/p&gt;

&lt;p&gt;Zmieńmy klasę UserValidationDef tak, aby wykorzystywała komunikaty z pliku zasobów:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c#&quot; data-lang=&quot;c#&quot;&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserValidationDef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ValidationDef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;UserValidationDef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FirstName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NotNullableAndNotEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{message.user.firstname.NotNullableAndNotEmpty}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LastName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;NotNullableAndNotEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{message.user.lastname.NotNullableAndNotEmpty}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Define&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Age&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GreaterThanOrEqualTo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;18&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;WithMessage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;{message.user.age.GreaterThanOrEqualTo}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ostatni krok, to konfiguracja biblioteki tak, aby wykorzystywał plik zasobów ValidationMessages.resx:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c#&quot; data-lang=&quot;c#&quot;&gt;&lt;span class=&quot;n&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SharedEngineProvider&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;NHibernateSharedEngineProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;FluentConfiguration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;SetCustomResourceManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;NHVWinForms.Properties.ValidationMessages&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Program&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Assembly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserValidationDef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Assembly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ValidationDefinitions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;SetDefaultValidatorMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ValidatorMode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UseExternal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ve&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Environment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SharedEngineProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GetEngine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I mamy przyjazne i łatwo modyfikowalne komunikaty: &lt;em&gt;Imię jest wymagane&lt;/em&gt; zamiast &lt;em&gt;nie może być nieokreślony i pusty&lt;/em&gt; i &lt;em&gt;Wiek musi być większy lub równy 18&lt;/em&gt; zamiast &lt;em&gt;musi być większe lub równe od 18&lt;/em&gt;.&lt;/p&gt;</content><author><name></name></author><summary type="html">Jakiś czas temu przedstawiłem sposób użycia biblioteki NHibernate.Validator w aplikacji Windows Forms. Miał on jedną zasadniczą wadę: komunikaty walidatora były takie, jakie stworzyli autorzy biblioteki.</summary></entry></feed>