Wprowadzenie
Kilka tygodni temu pojawił się pierwszy artykuł o frameworku Blazor na blogu. Od tamtego czasu zmienił się status frameworka. Wyszedł z fazy eksperymentalnej i aktualnie jest w wersji Preview. Część Server-side zostanie wydana wraz z .NET Core 3.0 we wrześniu 2019. Na stabilną część Client-side działającą na WebAssembly przyjdzie nam poczekać dodatkowy rok, do momentu wydania .NET 5. Ale i tak myślę, że warto już teraz interesować się tym frameworkiem.
W dzisiejszym wpisie będę chciał Ci pokazać, w jaki sposób pobrać dane z Web API. Utworzymy proste rozwiązanie, które z jednej strony będzie udostępniać dane z wykorzystaniem Web API w .NET Core 3.0, a z drugiej strony aplikację w Blazor, która wyśle żądanie o te dane i wyświetli je w widoku.
Utworzenie nowego projektu
W poprzednim wpisie o frameworku Blazor wspomniałem, że w Visual Studio po zainstalowaniu dodatku do Blazora (https://marketplace.visualstudio.com/items?itemName=aspnet.blazor) mamy do dyspozycji dwa szablony do tworzenia projektu. Pierwszy tworzy tylko projekt Blazora, który działa w przeglądarce – i z niego skorzystaliśmy w poprzednim wpisie. Dzisiaj natomiast skorzystamy z tego drugiego szablonu (Blazor ASP.NET Core hosted), który w solution tworzy trzy projekty:
Pierwszy projekt (Client) to aplikacja Blazor działająca w przeglądarce. Jest to ten sam projekt, który opisywałem w poprzednim wpisie. Drugi projekt (Server) to projekt Web API w .NET Core, który oczywiście działa po stronie serwera. Ostatni (Shared) to projekt, w którym znajdują się klasy i inne elementy współdzielone między obydwoma projektami.
Ten ostatni projekt, Shared, jest bardzo przydatny. Dzięki temu, że jest współdzielony między frontend oraz backend, możemy zmniejszyć sobie ilość pracy i wrzucać do niego wszystkie obiekty DTO, które zwracają kontrolery w akcjach i do których dane po stronie frontend będą parsowane.
W aktualnych rozwiązaniach, w których na przykład po stronie backendu mamy .NET Core, a po stronie frontendu Angulara (lub inny framework), musimy zawsze wykonywać dodatkową pracę, w ramach której synchronizujemy definicje obiektów DTO. Dodanie nowej właściwości do klasy zwracanej przez kontroler powoduje, że po stronie frontend musimy też dodać tę nową właściwość. Oczywiście takie rozwiązania jak Swagger ułatwiają nam ten proces, ale w przypadku Blazora oraz projektu Shared tak naprawdę tego problemu nie mamy. Co bardzo ułatwia pracę i mnie osobiście się bardzo podoba. 🙂
Web API
Omawianie przykładu zaczniemy od części serwerowej. Tak jak wspomniałem wcześniej, w projekcie Shared znajduje się klasa, która reprezentuje dane przesyłane między backend a frontend. W przykładowym projekcie wygląda tak:
public class WeatherForecast | |
{ | |
public DateTime Date { get; set; } | |
public int TemperatureC { get; set; } | |
public string Summary { get; set; } | |
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); | |
} |
W klasie znajdują się cztery właściwości, z czego trzy przechowują dane, natomiast ostatnia przelicza temperaturę z stopni Celsjusza, na Fahrenheity.
Po stronie Web API znajduje się prosty kontroler, który zwraca testowe dane:
[Route("api/[controller]")] | |
public class SampleDataController : Controller | |
{ | |
private static string[] Summaries = new[] | |
{ | |
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" | |
}; | |
[HttpGet("[action]")] | |
public IEnumerable<WeatherForecast> WeatherForecasts() | |
{ | |
var rng = new Random(); | |
return Enumerable.Range(1, 5).Select(index => new WeatherForecast | |
{ | |
Date = DateTime.Now.AddDays(index), | |
TemperatureC = rng.Next(-20, 55), | |
Summary = Summaries[rng.Next(Summaries.Length)] | |
}); | |
} | |
} |
W tych dwóch projektach nic nie zmieniałem i zostawiłem to, co zostało wygenerowane przez Visual Studio podczas ich tworzenia.
Blazor Web API
Po stronie aplikacji frontend rozbudowałem trochę kod w stosunku do tego, co wygenerowało Visual Studio. Wcześniej cały kod komunikacji z Web API znajdował się w widoku FetchData.razor (tutaj przy okazji widać, że od poprzedniego wpisu zmieniły się rozszerzenia widoków z cshtml na razor). Natomiast w moim projekcie dodałem dodatkową warstwę usługową, która będzie odpowiedzialna za komunikację z Web API. Sam widok będzie korzystał tylko z tej dodatkowej klasy i będzie wyświetlał zwrócone dane.
Do projektu dodałem folder Services i w nim znajduje się klasa WeatherForecastService, która wyśle żądanie do kontrolera i zwróci zserializowane dane do widoku:
public class WeatherForecastService : IWeatherForecastService | |
{ | |
private readonly HttpClient _client; | |
public WeatherForecastService(HttpClient client) | |
{ | |
_client = client; | |
} | |
public async Task<WeatherForecast[]> GetAsync() | |
{ | |
return await _client.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts"); | |
} | |
} | |
public interface IWeatherForecastService | |
{ | |
Task<WeatherForecast[]> GetAsync(); | |
} |
W samej usłudze nie dzieje się nic wielkiego. Wstrzykujemy obiekt klasy HttpClient, a następnie w metodzie GetAsync wysyłamy żądanie do kontrolera i serializujemy dane. Wszystko w sposób asynchroniczny z wykorzystaniem klasy Task oraz słów kluczowych async/await.
Blazor, podobnie jak Web Api w .NET Core, ma wbudowany prosty kontener Dependency Injection. Dlatego po dodaniu klasy usługi i jej interfejsu należy ją zarejestrować w klasie Startup:
public class Startup | |
{ | |
public void ConfigureServices(IServiceCollection services) | |
{ | |
services.AddScoped<IWeatherForecastService, WeatherForecastService>(); | |
} | |
public void Configure(IComponentsApplicationBuilder app) | |
{ | |
app.AddComponent<App>("app"); | |
} | |
} |
Zmiany w widoku
Zanim przejdziemy do użycia w widoku powyższej usługi, warto zrobić jeszcze jedną rzecz. W plikach widoków, podobnie jak w zwykłym kodzie C#, aby skorzystać z jakieś klasy, musimy najpierw ją zaimportować. Na ogół robimy to poprzez dodanie usinga do przestrzeni nazw, w której znajduje się klasa.
W widokach w Blazorze możemy to zrobić na dwa sposoby. Możemy dodać using w pliku widoku lub w pliku _Imports.razor. Ta druga opcja jest o tyle ciekawa, że dodanie tam usinga spowoduje, że wszystkie widoki (np. z folder Pages) będą miały automatycznie dodane te usingi i nie będzie trzeba ich dodawać za każdym razem w pliku widoku. Do pliku _Imports.razor w folderze Pages dodałem usingi do folderu z usługami oraz projektu Shared:
@layout MainLayout | |
@using ApiDemo.Shared | |
@using ApiDemo.Client.Services |
Teraz mogę zmodyfikować widok FetchData.razor, który wyświetla dane pobranie z Web Api i wyświetla w tabelce:
@page "/fetchdata" | |
@inject IWeatherForecastService Service | |
<h1>Weather forecast</h1> | |
<p>This component demonstrates fetching data from the server.</p> | |
@if (forecasts == null) | |
{ | |
<p><em>Loading...</em></p> | |
} | |
else | |
{ | |
<table class="table"> | |
<thead> | |
<tr> | |
<th>Date</th> | |
<th>Temp. (C)</th> | |
<th>Temp. (F)</th> | |
<th>Summary</th> | |
</tr> | |
</thead> | |
<tbody> | |
@foreach (var forecast in forecasts) | |
{ | |
<tr> | |
<td>@forecast.Date.ToShortDateString()</td> | |
<td>@forecast.TemperatureC</td> | |
<td>@forecast.TemperatureF</td> | |
<td>@forecast.Summary</td> | |
</tr> | |
} | |
</tbody> | |
</table> | |
} | |
@functions { | |
WeatherForecast[] forecasts; | |
protected override async Task OnInitAsync() | |
{ | |
forecasts = await Service.GetAsync(); | |
} | |
} |
W pliku warto zwrócić uwagę na kilka rzeczy. W pierwszej linii znajduje się dyrektywa page z adresem, pod którym będzie dostępny widok (/fetchdata), w drugiej natomiast znajduje się wstrzyknięcie usługi pobierającej dane.
Na samym końcu znajduje się blok @functions, w którym są dwie rzeczy: pole forecasts dla danych pobranych z usługi oraz metoda OnInitAsync. Metoda ta odpalana jest po załadowaniu widoku i w niej właśnie wywołujemy metodę GetAsync z usługi.
W samym kodzie html mamy ifa, która sprawdza wartość pola forecasts. Gdy na początku wyświetlenia widoku jest nullem, to aplikacja wyświetla informacje o ładowaniu danych. Po ich pobraniu nastąpi wyświetlenie tabelki z wykorzystaniem pętli foreach.
Jak widać, pobranie danych w Blazor z Web Api nie jest niczym skomplikowanym. A dzięki współdzieleniu części kodu jest wręcz przyjemne. 🙂
Przykład
Tradycyjnie na githubie (https://github.com/danielplawgo/BlazorWithApi) znajduje się przykład. Przygotowywałem go w Visual Studio 2019 z zainstalowanym dodatkiem dla Blazora (https://marketplace.visualstudio.com/items?itemName=aspnet.blazor). Po jego pobraniu nie trzeba robić nic specjalnego, wystarczy tylko uruchomić aplikację.
Podsumowanie
Bardzo ucieszyła mnie informacja o tym, że Blazor wyszedł z fazy eksperymentalnej i jest w wersji preview. Im więcej z niego korzystam, tym bardziej podoba mi się idea korzystania z tych samych narzędzi po stronie backend oraz frontend. Do tego możliwość współdzielenia kodu, w szczególności obiektów DTO przesyłanych między obiema aplikacjami, bardzo wszystko ułatwia.
Już nie mogę się doczekać wersji finalnej frameworka. Ale przed Microsoftem jeszcze dużo pracy nad nim.
Przygotowuję kolejne wpisy na temat Blazor. Daj znać, czy ten temat Cię interesuje. 🙂
Zainteresowany Blazorem? Jak tak to zapraszam na stronę prezentacji Front-end in C#? Yes – Blazor, gdzie znajdziesz więcej materiałów.
Tak, temat bardzo interesujący! Proszę o więęecej! (Zdecydowanie więcej)
W najbliższym czasie będzie tego więcej 🙂 Właśnie kończę pracę nad kolejnym wpisem o Blazor, który pojawi się za tydzień 🙂
Super. No i może jakiś kursik??
Taki jest plan, ale chce jeszcze poczekać do wersji stabilnej działającej na WebAssembly.
Ojjj to jeszcze poczekamy. To może teraz w oparciu o Server Side Blazor? (Będzie zawarte w .Net Core 3.0)
Pomyślę, może faktycznie warto byłoby zacząć od Server Side i później rozszerzyć to o Client Side.
Było by super! Tylko proszę językiem dla początkujących leszczy :). Temat jest arcyciekawy. Jeśli mógłbym jako początkujący w czymś pomóc, to służę.
ok będę pamiętał 🙂
Hej.
Dzieki za kolejny ciekawy artykul.
NIESTETY, mi nie wyswietlaja sie fragmenty artykulu zawierajace kod 🙁
Jakies drobne niedopatrzenie czy to ja mam problem z przegladarka?
Faktycznie był jakiś problem z gist, już teraz powinno być ok.