Polskie znaki w MVC

Tworzy się wszystko po angielsku, a przez to nie ma problemów ze znakami „zażółć gęślą jaźń”. Ja popełniłem ostatnio małą aplikację, gdzie postanowiłem że cały UI będzie po polsku, ponieważ do takich odbiorców kieruje swój projekt. Skoro jedno języczne to będzie to proste. HTML i opisy po polsku, atrybuty i informacje po polsku.

Nic nie zapowiadało małej katastrofy, ale ta nadeszła całkiem szybko:

 

Plik zapisany w UTF-8, przynajmniej VS2015 tak twierdzi (słaba ta wersja). Html meta też mówi, że to UTF-8. Ciekawie zaczyna się dopiero gdy w spojrzy się do source tree (aktualnie tego używam):

 

Nie zastanawiając się za długo, pomyślałem sobie walić to, kiedyś jak (nie jeśli, tylko kiedy!) będzie więcej użytkowników trzeba będzie to wyświetlić w innym języku. Zasoby! Pomyślałem i stop, bo nie pamiętam jak to się dokładnie robiło i właśnie dlatego wpis.
Prawy na projekcie, dodaj nowy resource. Z automatu dodaj się domyślny język, u mnie domyślny jest taki który nie będzie posiadać tłumaczenia, po to, żeby znaleźć braki w tłumaczeniu. Chciałem przez chwile, mieć „———-” zamiast tekstu, ale potem pomyślałem że będzie trudniej znaleźć klucz dla wartości „———„, jeśli taka będzie wszędzie. Przyjąłem na razie konwencja, że wartość odpowiada kluczowi, czyli dla klucza ViewModel_LoginViewModel_Password domyślnym tłumaczeniem jest także ViewModel_LoginViewModel_Password. Bo: nie wygląda to strasznie źle w przypadku fakapa, łatwo daje się znaleźć w wielkim pliku z zasobami, a jednocześnie szybko daje się odnaleźć w kodzie. ViewModele, klasa LoginViewModel, właściwość Password. Nie które z właściwości posiadają także błędy, wtedy dodaje do klucza informacje o błędzie np. required. Poniżej inna klasa, bo LoginViewModel okazał się nie przetłumaczony do końca – ot uroki pracy samemu i braku reviewu.

Przykład z właściwością która posiada więcej niż jeden atrybut z tłumaczeniem i klucz użyty:
ViewModel_VerifyPhoneNumberViewModel_Code_Required
ViewModel_VerifyPhoneNumberViewModel_Code
Itd, itp, etc i wiecie – rozumiecie.
Tak tylko dla wyjaśnienia: Translations to klasa, która zawiera tłumaczenia.

Co jeszcze jest ważne? Dodatkowe wersje językowe (inne niż domyśla) powinny zawierać język, które mają wspierać – u mnie .pl. I ostatnia ważna ważność: plik *.Designer.cs dla wersji językowej innej niż domyślna będzie pusty – tak ma być. Tylko domyślny designer posiada wygenerowany kod.
Korzystanie z zasobów jest proste, w przypadku atrybutów kod powyżej pokazal jak korzystać – uwaga na ErrorMessageResourceType i ResourceType. Natomiast w przypadku razora, z którego ja aktualnie korzystam wygląda to tak:

W przypadku l> pamiętamy o wcześniejszym using. I tyle, pozostaje tylko dodać tłumaczenie wszędzie i będzie po krzyku.

EmptyResult na zły sposób

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

Ale jeśli jesteście ciekawi co odpowiada serwer na różne żądanie popatrzcie na ten przykładowy kod:

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

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

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

.csharpcode .str { color: #a31515; }

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

.csharpcode .preproc { color: #cc6633; }

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

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

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

.csharpcode .lnum { color: #606060; }

   1:  public class BlogController : Controller
   2:  {
   3:      [HttpGet]
   4:      public ActionResult Index()
   5:      {
   6:          return this.View("Index");
   7:      }
   8:   
   9:      [HttpGet]
  10:      public ActionResult CallMeToGetSuccessEmptyResult()
  11:      {
  12:          return new EmptyResult();
  13:      }
  14:  }

Następnie widok:

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

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

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

.csharpcode .str { color: #a31515; }

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

.csharpcode .preproc { color: #cc6633; }

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

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

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

.csharpcode .lnum { color: #606060; }

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

I teraz na obrazu wyniki działania aplikaji:

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

Redirect to anchor w asp mvc

Od jakiegoś czasu pracuje za prawdziwe złoto jako sieciowy programista, dawno nikt nie wymagał aby po przekierowaniu wrócić do jakiegoś specyficznego kawałka strony. Zawsze kończyło się przekierowaniem do pełnej. Zapomniałem już o takiej funkcjonalność, no prawie zapomniałem. Otóż klepiąc sobie coś tam w domu, chciałem po zrobieniu POSTa wrócić gdzieś na dół strony, akcja nie korzysta ze zdobyczy technologi jaką jest AJAX, więc strona się przeładowywuje. Pozostało mi tylko skorzystanie z elementu html, który posiada znacznik id oraz nawigacja do strony z podaniem tego znacznika w url. Znacznik podaje się po znaczku ‚#’. Kompilator nie chciał tego przyjąć do wiadomości, gdy próbowałem zrobić this.RedirectToAction(„Index”,”Home”,new{#=”comments”}); nie działało.
Na szczęście internety dają radę także i tym razem super hero w postaci stack overflow ułatwił życie.
Uwaga podaję odpowiedź:
this.Redirect(Url.RouteUrl(new { controller = „Home”, action = „Index”}) + „#comments”);
To tyle w tym krótkim odcinku. Jutro znowu do pracy.