ASP.NET — C# z JavaScript'em w pokojowym współistnieniu
18.05.2017 02:49
Tło
Pierwszy raz z problemem wywołania funkcji bądź przekazania zmiennych z kodu pisanego w C# do kody JavaScript’owego spotkałem się przy okazji generowania wykresów z użyciem biblioteki Google Charts. Moja aplikacja napisana w ASP.NET MVC korzystała z bazy danych stworzonej przy użyciu Entity Framework. Model przekazywany do widoku z kontrolera nie posiadał w sobie żadnych danych. Były one pobierane z bazy dopiero w momencie wywołania odpowiednich funkcji. I tu pojawił się problem. W jaki sposób dane uzyskane w ten sposób „wstrzyknąć” do tablicy w JS, na podstawie której tworzony był wykres?
Struktura
Dla ułatwienia przybliżę tylko tę część struktury projektu, która jest istotna:
Models
- /BrandC.cs – klasa modelu
Views/Brand
- /Index.cshtml – widok
- /ChartsView.cshtml – widok częściowy renderowany wewnątrz index.cshtml
Views/Shared
- /_Layout.cshtml – layout strony, zawiera sekcję head oraz body, wewnątrz body w renderowane są widoki.
Problem 1. – dodawanie kodu do sekcji head z poziomu widoków
Ideą było zadeklarowanie wszystkich opcji wykresów z poziomu sekcji head strony. Jako, że dane zależne były od modelu przekazanego do widoku wypełnienie tablicy z danymi musiało nastąpić w pliku Index.cshtml. Całkiem eleganckim rozwiązaniem okazało się dodanie do pliku _Layout następującego kodu:
[code=C#] @if (IsSectionDefined("AddToHead")) { @RenderSection("AddToHead", required: false) } [/code]
A następnie w samym Indexie wstawiona została sekcja z kodem JS:
[code=C#] @section AddToHead{
Po tych operacjach w widoku ChartsView można było już wstawić znacznik
Problem 2. – przekazanie danych z listy C# do listy w JavaScript
Funkcja przygotowująca dane do wykresu zwracała je w postaci List<int>. Najbardziej naturalnym sposobem przekazania ich do zmiennej z danymi do wykresu wydał się być JSON. I słusznie. Serializację danych w ASP.NET możemy przeprowadzić na dwa sposoby – za pomocą wbudowanej klasy System.Web.Helpers.Json lub frameworka Json.NET. Twórcy frameworka chwalą się, że jest to rozwiązanie szybsze od serializacji wbudowanej. Jak jest naprawdę postanowiłem przekonać się osobiście i test ten znajdzie się w kolejnym wpisie. Samo przygotowanie danych odbywa się bardzo podobnie, a można zrobić je w dwóch miejscach. W przypadku rozwiązania systemowego funkcja wciąż zwracała List<int>. W kodzie JS pojawił się następujący zapis:
[code=JS / C#]var inData = @Html.Raw(Json.Encode(Model.prepareChartDataV2()));[/code]
Kolejno: Html.Raw odpowiada za prawidłowe kodowanie – bez tego zapisu zamiast znaku „ pojawia się "e. Json.Encode jest wywołaniem metody serializującej z systemowej klasy Json. Jako argument przyjmuje obiekt, zwraca rzecz jasna string Model.prepareChartDataV2() jest funkcją zwracającą wspomnianą wcześniej tablicę int’ów.
Korzystając z frameworka w kodzie funkcji zmieniłem typ zwracanych danych na string, a funkcję serializującą wywołałem w momencie zwracania wyniku:
[code=C#] return JsonConvert.SerializeObject(listaInt); [/code]
Sam kod w pliku index uprościł się rzecz jasna o wywołanie metody serializującej: [code=JS/C#] var inData = @Html.Raw(Model.prepareChartDataV2()); [/code]
Serializacja obiektów
Nie tylko listy podlegają serializacji. Równie dobrze można serializować całe obiekty pobrane z bazy danych nawet, jeżeli są typu anonimowego. Załóżmy proste tabele bazy danych:
Wpis: WpisID|Autor|TagID 12|Anon|2 Tag: TagID|Tagi| 2|Personal
W wypadku pobrania tego zestawu danych za pomocą najprostszego zapytanie LINQ do zmiennej var obj, po wykonaniu serializacji na obiekcie obj otrzymamy następującego json'a:
[code=LINQ] using (var db = new _base()) { var pz = (from p in db.Wpis select new { p }).ToList(); return JsonConvert.SerializeObject(pz); } [/code]
[code=JSON] { „WpisID” : „12”, „Autor” : „Anon”, „Tag” : { „TagID” : „2”, „Tagi” : „Personal” } } [/code]
Adnotacja
Powyższy wpis to mój debiut blogerski. Motywacją do napisania tego tekstu był czas poświęcony na poszukiwanie rozwiązań omówionych tu zagadnień. Jako osoba nie będąca ekspertem w .NET uznałem, że warto podzielić się tym rozwiązaniem z innymi stającymi przed koniecznością rozwiązania podobnego problemu.