.NET 5 – jak zacząć

Chciałbyś nauczyć się programować w .NET i dowiedzieć się co potrafi platforma od Microsoft? Jakich narzędzi użyć i od czego zacząć? Dobrze trafiłeś! Wyjaśnię Ci wszystko krok po kroku.

Co to jest .NET?

.NET jest to platforma programistyczna stworzona przez Microsoft. A oto najważniejsze jej cechy:

  • można pisać w wielu językach: C#, F# oraz VB.NET
  • biblioteki napisane w różnych językach w .NET mogą ze sobą współpracować, ponieważ są kompilowane do kodu pośredniego IL
  • .NET 5 i technologie mu towarzyszące są otwarte, a ich źródła są dostępne na platformie GitHub 
  • w .NET 5 można budować aplikacje konsolowe, strony internetowe, API, gry, aplikacje na komórkę oraz na komputery stacjonarne
  • .NET jest ogromnie popularny, dlatego posiada wiele gotowych integracji z technologiami Amazon, czy Google, jednak najłatwiej będzie się nam pracowało z produktami Microsoft oraz chmurą Azure
  • program napisany w .NET 5 możemy bez problemu uruchomić na Windows, Linux oraz MacOS

Oczywiście te kilka punktów w żadnym stopniu nie wyczerpuje tematu, bo można by spokojnie napisać kilka książek do czego zdolny jest .NET, jednak na sam początek to szybkie podsumowanie wystarczy.

Co muszę zainstalować na początek?

Aby budować aplikacje musisz zainstalować pakiet narzędzi programistycznych, czyli SDK. Aby je uruchomić – pakiet uruchomieniowy.

Oba znajdziesz tutaj: https://dotnet.microsoft.com/download/dotnet/5.0

Jeżeli chodzi o Runtime, to wybierz to co potrzebujesz:

  • ASP.NET Core – jeżeli chcesz buować aplikacje webowe
  • Desktop – dla aplikacji desktopowych pod Windows
  • .NET Runtime – dla aplikacji konsolowych

Jeden z powyższych na początek zdecydowanie wystarczy. Ja polecałbym ASP.NET Core.

W czym pisać programy?

Podobno dobry programista i w notatniku sobie poradzi, ale myślę, że ta era już dawno się skończyła. Tutaj są tak naprawdę dwie opcje do wyboru.

Visual Studio

Ogromny i bardzo popularny edytor kodu. Jest to prawdziwy kombajn, jest świetnie zintegrowany ze wszystkimi technologiami Microsoft, zwłaszcza z tymi starszymi. Używam go od wielu lat i nie wyobrażam sobie pracy bez niego.

Najważniejsze cechy:

  • wygodny edytor, gdzie wiele rzeczy możemy wyklikać
  • wsparcie dla starszych technologii Microsoft
  • dostępny na Windows oraz MacOS
  • płatny, jednak istnieje wersja darmowa, okrojona – Community

Visual Studio Code

Prosty, wieloplatformowy edytor kodu, rozwijany open-source. Nie posiada tak wielu integracji jak Visual Studio, jednak jest lekki i dzięki darmowym rozszerzeniom, można go łatwo dostosować pod swoje potrzeby. Z pewnością jednak pracuje się na nim wygodnie z mniejszymi projektami oraz ze stronami internetowymi. Używam go w momencie kiedy pracuję np. z React.js

Najważniejsze cechy:

  • jest szybki i lekki
  • jest darmowy i łatwo można go dostosować
  • działa pod Windows, Linux oraz MacOS
  • integracja ze starszymi technologiami Microsoft nie jest najlepsza, jednak z .NET Core i .NET 5 działa świetnie

Pierwszy program

Kreator w Visual Studio

Najprostszym sposobem, aby stworzyć swój pierwszy program w .NET 5 jest użycie jednego z gotowców w Visual Studio. To środowisko oferuje nam wiele opcji do wyboru:

Najprostszy projekt, który możesz wybrać na początek to aplikacja konsolowa w języku C#. Jeżeli natomiast wolisz zacząć od projektu webowego, wybierz ASP.NET Core application.

 

Aby upewnić się, że używasz .NET 5, edytuj plik projektu i sprawdź jaka wartość jest wpisana w TargetFramework, powinna mieć wartość net5.0.

 

Aby uruchomić projekt, naciśnij po prostu F5. W tym momencie aplikacja konsolowa zostanie uruchomiona i pojawi się czarne okienko z napisem Hello World!.

Nowy projekt w Visual Studio Code

.NET Core jak i .NET 5 jest wydawany razem z .NET CLI, czyli wieloplatformowym zbiorem poleceń, który pozwala na tworzenie, budowanie i publikowanie projektów .NET. Ważnym słowem jest tutaj wieloplatformowość – to właśnie dzięki CLI możemy budować i uruchamiać programy w .NET 5 nie tylko na Windows, ale także na Linux i MacOS.

Stwórzmy zatem nowy projekt. Kiedy otworzymy Visual Studio Code, należy otworzyć nowy terminal i wpisać polecenie dotnet new --list, dzięki któremu zobaczymy jakie są obecnie dostępne gotowce projektów.

 Aby stworzyć aplikację konsolową o nazwie ConsoleApp2 należy wpisać polecenie dotnet new console -n ConsoleApp2

Świetnie, mamy już aplikację konsolową z pojedynczym plikiem w języku C# o nazwie Program.cs. Potrzebna nam będzie znajomość jeszcze dwóch komend:

  • dotnet build–  aby zbudować projekt
  • dotnet run – aby go uruchomić

Po kilku sekundach Twoim oczom ukaże się czarne okienko z programem.

Podsumowanie

Programowanie w .NET 5 można zacząć bardzo szybko i już po chwili pisać własne programy. Dodatkowo zadziałają one nie tylko na Windows, ale także na Linux i MacOS. Mogą także działać w chmurze, np. w kontenerach. Możliwości są naprawdę ogromne.

Jeżeli chciałbyś dowiedzieć się czegoś więcej o programowaniu w konsoli, zerknij na serię moich artykułów: https://www.michalbialecki.com/2018/05/25/how-to-make-you-console-app-look-cool/

Jeżeli zaczynasz swoją przygodę z .NET, to dobrze trafiłeś. Zasubskrybuj mój blog i dostawaj informacje o nowych postach – na pewno dowiesz się z nich czegoś ciekawego.

Pozdrawiam i powodzenia 🙂

ASP.NET Core in .NET 5 – wysyłanie żądania

Wysłanie żądania w programie ASP.NET Core w .NET 5 jest standardową operacją, którą można dość łatwo wykonać. Jednak szczegóły mają znaczenie w tym przypadku i pokażę Wam najlepsze praktyki. Przyjrzymy się również niektórym zaawansowanym funkcjom, aby uzyskać pełny ogląd sytuacji.

Żądania do prawdziwego API

W tym artykule będę korzystać z bezpłatnej usługi dostępnej w Internecie do pobierania prognoz pogody – http://weatherstack.com. Aby móc z niego korzystać, po prostu zarejestruj się na stronie internetowej i możesz go również użyć. W ciągu miesiąca dostępnych jest 1000 żądań dla darmowego konta, które powinno wystarczyć do zaspokojenia naszych potrzeb.

Najpierw rzućmy okiem na żądania, które zamierzamy wysyłać. Aby przetestować API, używam aplikacji Postman, która jest bardzo wydajna, ale intuicyjna. Gorąco zachęcam do przeczytania mojego artykułu na ten temat tutaj: Postman the right way

Oto jak wygląda żądanie pobrania aktualnej pogody w Poznaniu:

Jest to żądanie typu GET wysłane na adres http://api.weatherstack.com/current z dwoma parametrami:

  • access_key który otrzymasz kiedy zarejestrujesz konto
  • query które jest nazwą miasta

Odpowiedź, którą otrzymaliśmy to 200 OK z zawartości w formacie JSON.

 Żeby wykonać to żądanie w kodzie, zaimplementuję klasę WeatherStackClient.

using Microsoft.Extensions.Logging;
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

namespace PrimeHotel.Web.Clients
{
    public class WeatherStackClient : IWeatherStackClient
    {
        private const string AccessKey = "3a1223ae4a4e14277e657f6729cfbdef";
        private const string WeatherStackUrl = "http://api.weatherstack.com/current";

        private HttpClient _client;
        private readonly ILogger<WeatherStackClient> _logger;

        public WeatherStackClient(HttpClient client, ILogger<WeatherStackClient> logger)
        {
            _client = client;
            _logger = logger;
        }
    }
}

Kilka rzeczy w tym kodzie warto wyjaśnić:

  • AccessKey jest na razie wpisany na sztywno, ale w prawdziwym produkcyjnym API powinien być pobrany z pliku konfiguracyjnego
  • IWeatherStackClient interfejs, który został dodany, aby wykorzystać go przy wstrzykiwaniu zależności (Dependency Injection)
  • HttpClient jest przekazywany przez konstruktor. Będzie on automatycznie stworzony i zarządzany przez framework

A teraz utwórzmy kod odpowiedzialny za logikę:

    public async Task<WeatherStackResponse> GetCurrentWeather(string city)
    {
        try
        {
            using var responseStream = await _client.GetStreamAsync(GetWeatherStackUrl(city));
            var currentForecast = await JsonSerializer.DeserializeAsync<WeatherStackResponse>(responseStream);
            
            return currentForecast;
        }
        catch (Exception e)
        {
            _logger.LogError(e, $"Something went wrong when calling WeatherStack.com");
            return null;
        }
    }

    private string GetWeatherStackUrl(string city)
    {
        return WeatherStackUrl + "?"
                + "access_key=" + AccessKey
                + "&query=" + city;
    }

Przeanalizujmy ten kod po kolei i zobaczmy co się w nim dzieje:

  • _client.GetStreamAsync jest metodą asynchroniczną, której przekazujemy URL i zwraca treść odpowiedzi w postaci strumienia. Jest więcej metod do wyboru: GetAsync, PostAsync, PutAsync, PatchAsync, DeleteAsync aby wykonać wszystkie operacje CRUD. Jest także metoda GetStringAsync które działa podobnie do GetStreamAsync
  • GetWeatherStackUrl tworzy URL poprzez dodanie do siebie URL serwisu oraz parametrów
  • JsonSerializer.DeserializeAsync<WeatherStackResponse>(responseStream) deserializuje strumień danych do obiektu typu WeatherStackResponse

A klasa WeatherStackResponse wygląda następująco:

using System.Text.Json.Serialization;

namespace PrimeHotel.Web.Clients
{
    public class WeatherStackResponse
    {
        [JsonPropertyName("current")]
        public Current CurrentWeather { get; set; }

        public class Current
        {
            [JsonPropertyName("temperature")]
            public int Temperature { get; set; }

            [JsonPropertyName("weather_descriptions")]
            public string[] WeatherDescriptions { get; set; }

            [JsonPropertyName("wind_speed")]
            public int WindSpeed { get; set; }

            [JsonPropertyName("pressure")]
            public int Pressure { get; set; }

            [JsonPropertyName("humidity")]
            public int Humidity { get; set; }

            [JsonPropertyName("feelslike")]
            public int FeelsLike { get; set; }
        }
    }
}

Zauważ, że użyłem atrybutu JsonPropertyName, aby określić które elementy w JSON odpowiadają właściwościom w mojej klasie. A oto struktura, którą chcę zmapować.

Zauważ, że nie muszę mapować wszystkich właściwości, a jedynie te, których potrzebuję. Ostatnia rzeczy, czyli zarejestrowanie klasy WeatherStackClient w kontenerze IoC. Aby tego dokonać, muszę przejść do klasy Startup i w metodzie ConfigureServices dodać następującą linię.

services.AddHttpClient<IWeatherStackClient, WeatherStackClient>();

Używamy dedykowanej metody rejestracji klas używającycj HttpClient. Pod spodem korzystamy z IHttpClientFactory, który pomaga w utrzymaniu puli i żywotności klientów. Istnieje ograniczona liczba połączeń HTTP, która może być utrzymywana w jednym momencie, a jeśli utworzysz zbyt wielu klientów, którzy blokują połączenie, niektóre z twoich żądań nie zostaną zrealizowane. IHttpClientFactory dodaje także konfigurowalne środowisko logowanie (za pośrednictwem ILogger) dla wszystkich wysyłanych żądań. Podsumowując, wspomniany mechanizm ułatwia życie programiście i działa przy tym w sposób inteligentny.

Czy to działa? Tak! Odpowiedź została poprawnie odwzorowana na moją klasę i mogę ją zwrócić.

Zastanawiasz się może co zostało zalogowane podczas procesowania tego żądania? Sprawdźmy.

Jak wspomniałem wcześniej, IHttpClientFactory zapewnia również mechanizm logowania, dzięki czemu każde żądanie jest logowane. Tutaj widać, że zalogowano nie tylko adres, ale także metodę HTTP i czas wykonania. Może to być bardzo przydatne podczas debugowania.

Dodanie mechanizmu ponawiania próby

W świecie mikro-serwisów, każdy serwis może czasami mieć gorszy dzień. Dlatego musimy mieć mechanizm ponawiania prób dla usług, do których wysyłamy żądania, i wiemy, że od czasu do czasu zawodzą. W ASP.NET Core dla .NET 5 istnieje biblioteka zewnętrzna, ale zintegrowana we framework, właśnie w tym celu – to Polly. Polly to wszechstronna biblioteka do obsługi błędów dla platformy .NET. Pozwala programistom na płynne i bezpieczne tworzenie zasad, jak błędy mają być obsługiwane. Chodzi to o scenariusze takie jak: ponawianie próby, Circuit Breaker, Timeout, Bulkhead Isolation, oraz Fallback.

W naszym przypadku dodamy mechanizm ponownej próby, która wywoła usługę WeatherStack i ponowi próbę 3 razy po początkowej awarii. Dzięki Polly można łatwo ustawić wiele prób i opóźnień między nimi. Rzućmy okiem na przykład – jest to metoda w klasie Startup, w której konfigurujemy kontener DI.

    services.AddHttpClient<IWeatherStackClient, WeatherStackClient>()
        .AddTransientHttpErrorPolicy(
            p => p.WaitAndRetryAsync(new[]
            {
                TimeSpan.FromSeconds(1),
                TimeSpan.FromSeconds(5),
                TimeSpan.FromSeconds(10)
            }));

Za pomocą tego kodu ponowimy to samo żądanie z opóźnieniem 1, 5 i 10 sekund. Nie jest wymagany dodatkowy kod. Polly zrobi to wszystko za nas. Zobaczymy zalogowany błąd, że coś się nie powiedzie, ale dopiero gdy wszystkie próby zakończą się niepowodzeniem, otrzymamy wyjątek.

Dodanie cancellation token

Cancellation token to mechanizm, który może zatrzymać wykonywanie połączenia asynchronicznego. Powiedzmy, że nasze żądanie nie powinno zająć więcej niż 3 sekundy, ponieważ jeśli tak jest, to wiemy, że coś jest nie tak i nie ma sensu czekać.

Aby zaimplementować taki mechanizm, musimy utworzyć cancellation token i podać go podczas wysyłanie żądania w kliencie HTTP.

    public async Task<WeatherStackResponse> GetCurrentWeatherWithAuth(string city)
    {
        try
        {
            using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(3));

            using var responseStream = await _client.GetStreamAsync(GetWeatherStackUrl(city), cancellationTokenSource.Token);
            var currentForecast = await JsonSerializer.DeserializeAsync<WeatherStackResponse>(responseStream);

            return currentForecast;
        }
        catch (TaskCanceledException ec)
        {
            _logger.LogError(ec, $"Call to WeatherStack.com took longer then 3 seconds and had timed out ");
            return null;
        }
        catch (Exception e)
        {
            _logger.LogError(e, $"Something went wrong when calling WeatherStack.com");
            return null;
        }
    }

Jeśli żądanie potrwa zbyt długo, otrzymamy wyjątek TaskCancelledException, który możemy wychwycić i zareagować na niego inaczej, niż w przypadku nieoczekiwanego wyjątku.

Dodanie autoryzacji

Podstawowa autoryzacja jest zdecydowanie najprostsza i jest jedną z najpopularniejszych. Chodzi o to, że każde żądanie do określonej usługi musi być autoryzowane, więc razem z naszą zawartością, musimy wysyłać także informacje autoryzacyjne. Przy podstawowej autoryzacji musimy przekazać użytkownika i hasło zakodowane jako ciąg znaków w formacie base64 i umieścić go w nagłówku żądania. Zobaczmy, jak można to osiągnąć.

    private const string ApiKey = "3a1223ae4a4e14277e657f6729cfbdef";
    private const string Username = "Mik";
    private const string Password = "****";

    public WeatherStackClient(HttpClient client, ILogger<WeatherStackClient> logger)
    {
        _client = client;
        _logger = logger;

        var authToken = Encoding.ASCII.GetBytes($"{Username}:{Password}");
        _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
            "Basic",
            Convert.ToBase64String(authToken));
    }

W ten sposób każde połączenie z WeatherStackClientbędzie zawierało informacje o autoryzacji i nie będziemy musieli niczego dodawać podczas wysyłania żądań. Jedynym miejscem, w którym musimy umieścić dodatkowy kod, jest konstruktor.

Pamiętaj, że autoryzacja nie jest wymagana, aby wysyłać żądania do weatherstack.com i jest dodana tylko w celach demonstracyjnych.

Ten artykuł nie obejmował wszystkich możliwości IHttpClientFactory, więc jeśli chcesz dowiedzieć się więcej, po prostu przejdź do tego artykułu Microsoft.

Mam nadzieję, że post Ci się spodobał. Cały kod z tego posta znajdziesz na moim koncie na GitHub:

https://github.com/mikuam/PrimeHotel

 

ASP.NET Core in .NET 5 – sending a request

Sending a request in ASP.NET Core in .NET 5 is a standard operation that can be achieved pretty easily. However, details matter in this case and I’ll show you the best practice available. We will also take a look at some advanced features to get the full scope.

Using a real available API

In this article, I will be using 3rd party free service for fetching weather forecasts – http://weatherstack.com. To be able to use it, just register on their website and you can use it as well. 1000 requests in a month are available for a free account and that should be more than enough to fulfill our needs.

First, let’s have a look at the requests we are going to make. To test the API, I’m using a Postman app, which is very powerful, yet intuitive. I strongly encourage you to read my article about it here: Postman the right way

Here is how a request to fetch current weather in Poznań looks like:

This is a GET request to http://api.weatherstack.com/current with two parameters:

  • access_key which you get when registering on the website
  • query that can be a city name

The response that we got is a 200 OK with JSON content.

 To make this request, I’ll create a WeatherStackClient class.

using Microsoft.Extensions.Logging;
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

namespace PrimeHotel.Web.Clients
{
    public class WeatherStackClient : IWeatherStackClient
    {
        private const string AccessKey = "3a1223ae4a4e14277e657f6729cfbdef";
        private const string WeatherStackUrl = "http://api.weatherstack.com/current";

        private HttpClient _client;
        private readonly ILogger<WeatherStackClient> _logger;

        public WeatherStackClient(HttpClient client, ILogger<WeatherStackClient> logger)
        {
            _client = client;
            _logger = logger;
        }
    }
}

There are a few things to notice here:

  • AccessKey which is hardcoded for now, but in a real-life API should be moved to configuration
  • IWeatherStackClient interface that is introduced for Dependency Injection support
  • HttpClient class is passed in a constructor. It will be automatically created and maintained by the framework

Now let’s create the logic.

    public async Task<WeatherStackResponse> GetCurrentWeather(string city)
    {
        try
        {
            using var responseStream = await _client.GetStreamAsync(GetWeatherStackUrl(city));
            var currentForecast = await JsonSerializer.DeserializeAsync<WeatherStackResponse>(responseStream);
            
            return currentForecast;
        }
        catch (Exception e)
        {
            _logger.LogError(e, $"Something went wrong when calling WeatherStack.com");
            return null;
        }
    }

    private string GetWeatherStackUrl(string city)
    {
        return WeatherStackUrl + "?"
                + "access_key=" + AccessKey
                + "&query=" + city;
    }

Let’s go through this code and explain what’s going on:

  • _client.GetStreamAsync is an asynchronous method that takes a URL an returns a stream. There are more methods, like: GetAsync, PostAsync, PutAsync, PatchAsync, DeleteAsync for all CRUD operations. There is also GetStringAsync that serializes a response content to string – just like GetStreamAsync does
  • GetWeatherStackUrl is merging a service URL with query parameters, returning a full URL address
  • JsonSerializer.DeserializeAsync<WeatherStackResponse>(responseStream) deserializes a stream and format output as a WeatherStackResponse class

The WeatherStackResponse class looks like this:

using System.Text.Json.Serialization;

namespace PrimeHotel.Web.Clients
{
    public class WeatherStackResponse
    {
        [JsonPropertyName("current")]
        public Current CurrentWeather { get; set; }

        public class Current
        {
            [JsonPropertyName("temperature")]
            public int Temperature { get; set; }

            [JsonPropertyName("weather_descriptions")]
            public string[] WeatherDescriptions { get; set; }

            [JsonPropertyName("wind_speed")]
            public int WindSpeed { get; set; }

            [JsonPropertyName("pressure")]
            public int Pressure { get; set; }

            [JsonPropertyName("humidity")]
            public int Humidity { get; set; }

            [JsonPropertyName("feelslike")]
            public int FeelsLike { get; set; }
        }
    }
}

Notice that I used JsonPropertyName attribute to identify what JSON property is each property matching. Here is the structure that we are going to map.

One last thing – we need to register our WeatherStackClient in a Dependency Injection container. In order to do so, we need to go to Startup class and add the following line in ConfigureServices method.

services.AddHttpClient<IWeatherStackClient, WeatherStackClient>();

We are using a dedicated method for registering classes using HttpClient. Underneath it’s using IHttpClientFactory that helps to maintain the pooling and lifetime of clients. You have a limited number of HTTP connections that you can maintain on your machine and if you create too much clients each blocking a connection, you will end up failing some of your requests. It also adds a configurable logging experience (via ILogger) for all requests sent through. All in all, it makes a developer’s life easier and allows you to do some smart stuff too.

Does it work? Yes it does! The response was correctly mapped into my class and I can return it.

Do you wonder what was logged when making this request? Let’s have a quick look.

As I mentioned earlier IHttpClientFactory also provides a logging mechanism, so that every request is logged. Here you can see that not only address was logged, but also HTTP method and time of execution. This can be pretty useful for debugging.

Adding a retry mechanism

In a micro-services world, every micro-service can have a bad day once in a while. Therefore, we need to have a retry mechanism for services we call and we know that they fail from time to time. In ASP.NET Core for .NET 5 there is a third-party library integrated just that purpose – it’s Polly. Polly is a comprehensive resilience and transient fault-handling library for .NET. It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

For our scenario let’s add a retry mechanism, that will call WeatherStack service and retry 3 times after an initial failure. With Polly, a number of retries and delays between then can be easily set. Let’s have a look at the example – it’s in the Startup method where we configure DI container.

    services.AddHttpClient<IWeatherStackClient, WeatherStackClient>()
        .AddTransientHttpErrorPolicy(
            p => p.WaitAndRetryAsync(new[]
            {
                TimeSpan.FromSeconds(1),
                TimeSpan.FromSeconds(5),
                TimeSpan.FromSeconds(10)
            }));

With this code we will retry the same request after 1, 5, and 10 seconds delay. There is no additional code needed. Polly will do everything for us. We will see logs that something failed, only after all retries will fail, we will get the exception.

Adding a cancellation token

A cancellation token is a mechanism that can stop the execution of an async call. Let’s say that our request shouldn’t take more than 3 seconds, because if it does, we know that something isn’t right and there is no point to wait.

To implement that we need to create a cancellation token and provide that when making a call with an HTTP client.

    public async Task<WeatherStackResponse> GetCurrentWeatherWithAuth(string city)
    {
        try
        {
            using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(3));

            using var responseStream = await _client.GetStreamAsync(GetWeatherStackUrl(city), cancellationTokenSource.Token);
            var currentForecast = await JsonSerializer.DeserializeAsync<WeatherStackResponse>(responseStream);

            return currentForecast;
        }
        catch (TaskCanceledException ec)
        {
            _logger.LogError(ec, $"Call to WeatherStack.com took longer then 3 seconds and had timed out ");
            return null;
        }
        catch (Exception e)
        {
            _logger.LogError(e, $"Something went wrong when calling WeatherStack.com");
            return null;
        }
    }

If the request takes too long, we will receive a TaskCancelledException, which we can catch and react to it differently, that when getting unexpected exception.

Provide authorization

Basic authorization is definitely the simplest and one of the most popular ones used. The idea is that every request to the specific service needs to be authorized, so that along with our content, we need to send authorization info. With basic authorization, we need to pass a user and password encoded as base64 string and put in a request header. Let’s see how that can be accomplished. 

    private const string ApiKey = "3a1223ae4a4e14277e657f6729cfbdef";
    private const string Username = "Mik";
    private const string Password = "****";

    public WeatherStackClient(HttpClient client, ILogger<WeatherStackClient> logger)
    {
        _client = client;
        _logger = logger;

        var authToken = Encoding.ASCII.GetBytes($"{Username}:{Password}");
        _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
            "Basic",
            Convert.ToBase64String(authToken));
    }

In this way every call from WeatherStackClient will have authorization info and we do not need to add anything when we make requests. The only place we need to put additional code is a constructor.

Note that authorization is not needed to call weatherstack.com and is added only to show how it can be done.

This article did not cover all of the possibilities of IHttpClientFactory so if you want to know more, just go to this Microsoft article.

Hope you like this post, all code posted in this article is available at my GitHub account:

https://github.com/mikuam/PrimeHotel

 

ASP.Net Core in .NET 5 – przekazywanie parametrów do akcji

Przekazywanie parametrów do akcji jest istotną częścią budowania RESTful Web API. ASP.NET Core, który został wydany jako część .NET 5 oferuje wiele sposobów przekazywania parametrów do metod reprezentujących punkty końcowe. Zobaczmy, jakie one są.

Przekazywanie parametrów jako część URL

Podczas przekazywania parametru w adresie URL musisz zdefiniować routing, który zawierałby parametr. Spójrzmy na przykład:

    [Route("{daysForward}")]
    [HttpGet]
    public IActionResult Get(int daysForward)
    {
        var rng = new Random();
        return new JsonResult(new WeatherForecast
        {
            Date = DateTime.Now.AddDays(daysForward),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        });
    }

Ta metoda zwraca prognozę pogody dla jednego dnia w przyszłości. Parametr daysForward określa, ile dni z wyprzedzeniem należy zwrócić prognozę pogody. Zauważ, że daysForward jest częścią routingu, więc prawidłowy adres URL do tego punktu końcowego będzie wyglądał następująco:

GET: weatherForecast/3

Możemy również użyć atrybutu [FromRoute] przed deklaracją metody, ale domyślnie będzie również działał w ten sam sposób.

   public IActionResult Get([FromRoute] int daysForward)

Przekazywanie parametrów w parametrach żądania

Jest to bardzo powszechna metoda przekazywania dodatkowych parametrów, ponieważ nie wymaga od nas zmiany routingu, więc jest również kompatybilna wstecz. Kompatybilność jest ważna, w przypadku zmiany istniejącego rozwiązania.

Spójrzmy na inną metodę, która zwróciłaby kolekcję prognoz pogody z opcją sortowania.

[HttpGet]
    public IEnumerable<WeatherForecast> Get([FromQuery]bool sortByTemperature = false)
    {
        var rng = new Random();
        var forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        });

        if (sortByTemperature)
        {
            forecasts = forecasts.OrderByDescending(f => f.TemperatureC);
        }

        return forecasts;
    }

W tym przykładzie przekazujemy parametr sortByTemperature, który jest opcjonalny. Zauważ, że używamy atrybutu [FromQuery], aby wskazać, że jest to zmienna pobrana z parametru zapytania. Adres URL tego punktu końcowego wygląda następująco:

GET: weatherForecast?sortByTemperature=true

Możesz także przekazać więcej parametrów rozdzielając je znakiem &:

GET: weatherForecast?key1=value1&key2=value2&key3=value3

Zwróć uwagę na to, że adres URL musi być odpowiednio zakodowany, aby działał poprawnie. Jeśli chcesz przekazać taki parametr:

https://www.michalbialecki.com/?name=Michał Białecki

To powinien być zakodowany jako:

https://www.michalbialecki.com/?name=Micha%C5%82%20Bia%C5%82ecki

Przekazywanie obiektu w parametrach żądania

Gdy podajesz wiele parametrów zapytania, warto traktować je jako obiekt. Spójrz na poniższy kod:

    // GET: weatherForecast/GetFiltered?SortByTemperature=true&City=Poznan
    [HttpGet("GetFiltered")]
    public IEnumerable<WeatherForecast> GetFiltered([FromQuery]WeatherForecastFilters filters)
    {
        var rng = new Random();
        var forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)],
            City = filters.City
        });
 
        if (filters.SortByTemperature)
        {
            forecasts = forecasts.OrderByDescending(f => f.TemperatureC);
        }
 
        return forecasts;
    }

Jak widać, przekazuję klasę WeatherForecastFilters jako parametr zapytania. Zauważ, że używam atrybutu [FromQuery] przed nazwą klasy.

Klasa WeatherForecastFilters wygląda następująco:

    public class WeatherForecastFilters
    {
        public bool SortByTemperature { get; set; }
 
        public string City { get; set; }
    }

Jest to standardowa klasa, bez żadnych dodatkowych atrybutów. Zróbmy teraz zapytanie na poniższy adres URL:

GET: /WeatherForecast/GetFiltered?SortByTemperature=true&City=Poznan

Odpowiedź będzie następująca:

Tak więc parametry mogą być przekazywane według ich nazw, nawet jeśli należą do klasy. Co więcej, są one dostępne tylko po ich nazwach, bez żadnego prefiksu klasy, a to oznacza, że wszystkie właściwości muszą mieć unikalne nazwy.

Oto zrzut ekranu z Visual Studio.

Obsługa parametrów zapytania jako właściwości klasy będzie działać, ale tylko wewnątrz tej klasy. Nie będzie działać z zagnieżdżonymi obiektami – ich właściwości nie będą mapowane. Jest to więc raczje zebranie parametrów w jeden obiekt, a nie przekazywanie skomplikowanego zagnieżdżonego obiektu. 

Przekazywanie parametrów w nagłówkach

Przekazywanie parametrów w nagłówkach żądania jest mniej popularne, ale również szeroko stosowane. Nie wyświetla się w adresie URL, więc jest mniej zauważalne przez użytkownika. Typowym scenariuszem przekazywania parametrów w nagłówku byłoby podanie parametrów autoryzacji lub identyfikatora żądania nadrzędnego, aby umożliwić jego śledzenie. Rzućmy okiem na ten przykład:

    [HttpPost]
    public IActionResult Post([FromHeader] string parentRequestId)
    {
        Console.WriteLine($"Got a header with parentRequestId: {parentRequestId}!");
        return new AcceptedResult();
    }

Aby wysłać żądanie POST, musimy skorzystać z jakiegoś narzędzia. Dobrym pomysłem jest wykorzystanie Postman-a:

W zakładce Headers można wprowadzić nagłówki żądania, w tym prypadku parentRequestId.

Przekazywanie parametrów w treści żądania

Najczęstszym sposobem przekazywania danych jest umieszczenie ich w treści żądania. Możemy dodać nagłówek Content-Type z wartością application/json i poinformować odbiorcę, jak interpretować tą treść. Rzućmy okiem na poniższy przykład:

    [HttpPost]
    public IActionResult Post([FromBody] WeatherForecast forecast)
    {
        Console.WriteLine($"Got a forecast for data: {forecast.Date}!");
        return new AcceptedResult();
    }

Używamy atrybutu [FromBody], aby wskazać, że prognoza zostanie pobrana z treści żądania. W ASP.NET Core w .NET 5 nie musimy deserializować treści żądania, aby przekształcić z typu JSON w obiekt WeatherForecast, będzie to zrobione za nas automatycznie. Aby wysłać żądanie POST, użyjmy ponownie Postman-a:

Pamiętaj, że rozmiar treści żądania jest ograniczony przez serwer. Może mieć dowolną maksymalną wartość od 1 MB do 2 GB. W ASP.NET Core 5 domyślny maksymalny rozmiar ciała żądania wynosi około 28 MB, ale można to zmienić. A co jeśli chciałbym wysłać większe pliki, ponad 2 GB? Aby to osiągnąć powinieneś rozważyć wysyłanie treści w postaci strumienia lub wysyłanie jej w częściach.

Przekazywanie parametrów w formularzu

Wysyłanie treści w formularzu nie jest zbyt często stosowane, ale jest to najlepsze rozwiązanie, jeśli chcesz np. przesłać plik. Rzućmy okiem na przykład:

    [HttpPost]
    public IActionResult SaveFile([FromForm] string fileName, [FromForm] IFormFile file)
    {
        Console.WriteLine($"Got a file with name: {fileName} and size: {file.Length}");
        return new AcceptedResult();
    }

Ta metoda tak naprawdę nie wysyła pliku, ale pomyślnie otrzymuje plik z żądania. Interfejs IFormFile służy specjalnie do obsługi pliku.

Wysyłając żądanie, musimy ustawić Content-Type na application/x-www-form-urlencoded i w zakładce Body, musimy wybrać plik:

Zobaczmy, co otrzymamy po zdebugowaniu tego kodu:

Plik został poprawnie odczytany. Ciekawostką jest to, że dzięki IFormFile otrzymujemy nie tylko dane binarne, ale także typ i nazwę pliku. Możesz więc zapytać, dlaczego wysyłam nazwę pliku osobno? Wynika to z tego, że możesz chcieć inaczej nazwać plik na serwerze, niż ten, który wysyłasz.

Mam nadzieję, że podobał Ci się ten post, możesz rzucić okiem na kod opublikowany tutaj na moim Github:

https://github.com/mikuam/PrimeHotel

ASP.NET Core in .NET 5 – pass parameters to actions

Passing parameters to actions is an essential part of building RESTful Web API. ASP.NET Core released as a part of .NET 5 offers multiple ways to pass parameters to methods, that represent your endpoints. Let’s see what they are.

Pass parameter as a part of an URL

When passing a parameter in a URL, you need to define a routing that would contain a parameter. Let’s have a look a the example:

    [Route("{daysForward}")]
    [HttpGet]
    public IActionResult Get(int daysForward)
    {
        var rng = new Random();
        return new JsonResult(new WeatherForecast
        {
            Date = DateTime.Now.AddDays(daysForward),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        });
    }

This method returns a WeatherForecast for a single day in the future. DaysForward parameter represents how many days in advance weather forecast should be returned. Notice that daysForward is a part of the routing, so a valid URL to this endpoint will look like:

GET: weatherForecast/3

We can also use [FromRoute] attribute before variable type, but it will also work the same way by default.

   public IActionResult Get([FromRoute] int daysForward)

Pass parameter in a query string

This is a very common method for passing additional parameters, because it does not require us to change routing, so it is also backward compatible. It’s important if we were to change an existing solution.

Let’s look at a different method, that would return a collection of weather forecasts with a sorting option.

[HttpGet]
    public IEnumerable<WeatherForecast> Get([FromQuery]bool sortByTemperature = false)
    {
        var rng = new Random();
        var forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        });

        if (sortByTemperature)
        {
            forecasts = forecasts.OrderByDescending(f => f.TemperatureC);
        }

        return forecasts;
    }

In this example, we pass on sortByTemperature parameter which is optional. Notice that we use [FromQuery] attribute to indicate, that it’s a variable taken from the query string. A URL to this endpoint would look like this:

GET: weatherForecast?sortByTemperature=true

You can put many parameters like this:

GET: weatherForecast?key1=value1&key2=value2&key3=value3

Remember, that URL needs to be encoded properly to work right. If you were to pass a parameter like this:

https://www.michalbialecki.com/?name=Michał Białecki

It will need to be encoded into:

https://www.michalbialecki.com/?name=Micha%C5%82%20Bia%C5%82ecki

Pass an object in a query string

When you’re passing a lot of query parameters, it might be worth to handle them as an object. Take a look at the code below:

    // GET: weatherForecast/GetFiltered?SortByTemperature=true&City=Poznan
    [HttpGet("GetFiltered")]
    public IEnumerable<WeatherForecast> GetFiltered([FromQuery]WeatherForecastFilters filters)
    {
        var rng = new Random();
        var forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)],
            City = filters.City
        });
 
        if (filters.SortByTemperature)
        {
            forecasts = forecasts.OrderByDescending(f => f.TemperatureC);
        }
 
        return forecasts;
    }

As you can see, I’m passing WeatherForecastFilters class as a query parameter. Notice that I’m using [FromQuery] attribute before the class name.

And WeatherForecastFilters class looks like this:

    public class WeatherForecastFilters
    {
        public bool SortByTemperature { get; set; }
 
        public string City { get; set; }
    }

It is a standard class, without any additional attributes. Let’s now make a request to the URL below:

GET: /WeatherForecast/GetFiltered?SortByTemperature=true&City=Poznan

The response would be:

So parameters can be passed-in by their names, even if they are inside the class. What’s more, they are accessible just by their names, without any class prefix and that means that all of the properties must have unique names.

Here is a screenshot from Visual Studio.

Handling query parameters as class properties will work, but only inside this class. It will not work with the nested objects – their properties will not be mapped.

Pass parameters with headers

Passing parameters in a request headers are less popular, but also widely used. It doesn’t show in a URL, so it’s less noticeable by the user. A common scenario for passing parameters in a header would be providing credentials or a parent request id to enable multi-application tracking. Let’s have a look at this example:

    [HttpPost]
    public IActionResult Post([FromHeader] string parentRequestId)
    {
        Console.WriteLine($"Got a header with parentRequestId: {parentRequestId}!");
        return new AcceptedResult();
    }

In order to send a POST request, we would need to use some kind of a tool, I’ll use Postman:

Here you see that I specified headers and parentRequestId is one of them.

Pass parameters in a request body

The most common way to pass the data is to include it in a request body. We can add a header Content-Type with value application/json and inform the receiver how to interpret this body. Let’s have a look at our example:

    [HttpPost]
    public IActionResult Post([FromBody] WeatherForecast forecast)
    {
        Console.WriteLine($"Got a forecast for data: {forecast.Date}!");
        return new AcceptedResult();
    }

We use [FromBody] attribute to indicate, that forecast will be taken from request body. In ASP.NET Core for .NET 5 we don’t need and serialize to deserialize json body to WeatherForecast object, it will work automatically. To send POST request, let’s use Postman once again:

Have in mind, that size of the request body is limited by the server. It can be anywhere between 1MB to 2GB. In ASP.NET Core 5 maximum request body size is around 28MB, but that can be changed. What if I would like to send bigger files than that, over 2GB? Then you should look into sending content as a stream or sending it in chunks.

Pass parameters in a form

Sending content in a form is not very common, but it is the best solution if you want to upload a file. Let’s have a look at the example:

    [HttpPost]
    public IActionResult SaveFile([FromForm] string fileName, [FromForm] IFormFile file)
    {
        Console.WriteLine($"Got a file with name: {fileName} and size: {file.Length}");
        return new AcceptedResult();
    }

This method doesn’t really send a file, but it will successfully receive a file from the request. The interface IFormFile is used specifically for handling a file.

When sending a request we need to set Content-Type to application/x-www-form-urlencoded and it the Body part, we need to choose a file:

Let’s see what do we get when we debug this code:

And the file is correctly read. An interesting fact is, that with IFormFile we get not only binary data but also a file type and name. So you might ask why I send a file name separately? This is because you might want to name file differently on the server, then the one you are sending.

Hope you enjoyed this post, you can have a look at the code posted here on my Github:

https://github.com/mikuam/PrimeHotel

 

 

Entity Framework Core 5 vs SQLBulkCopy

Entity Framework Core 5 to świetny ORM do używania i łączenia się z bazą danych. Jest łatwy w użyciu i łatwy do zrozumienia. Oferuje wszystko co potrzebne aby poradzić sobie z większością wyzwań programistycznych. A co z wstawieniem dużej ilości danych za jednym razem? Czy byłoby to wystarczająco szybkie?

Zerknijmy na kod

Jako przykładu użyję prostej encji – Profile oraz repozytorium PrimeHotel dostępne na moim koncie na GitHub

Mój DbContext jest bardzo prosty i wygląda tak:

    public class PrimeDbContext : DbContext
    {
        public PrimeDbContext(DbContextOptions<PrimeDbContext> options)
            : base(options)
        {
        }

        public virtual DbSet<Room> Rooms { get; set; }

        public virtual DbSet<Profile> Profiles { get; set; }

        public virtual DbSet<Reservation> Reservations { get; set; }
    }

A encja Profile prezentuje się następująco:

    public class Profile
    {
        public int Id { get; set; }

        public string Ref { get; set; }

        public string Forename { get; set; }

        public string Surname { get; set; }

        public string TelNo { get; set; }

        public string Email { get; set; }

        public DateTime? DateOfBirth { get; set; }
    }

W tym przykładzie użyję aplikacji typu WebApi, aby w jak najprosztszy sposób wywołać kod. Aby to zrobić stworzyłem ProfileController.

    [ApiController]
    [Route("[controller]")]
    public class ProfileController : ControllerBase
    {
        private readonly PrimeDbContext primeDbContext;
        private readonly string connectionString;

        public ProfileController(PrimeDbContext _primeDbContext, IConfiguration _configuration)
        {
            connectionString = _configuration.GetConnectionString("HotelDB");
            primeDbContext = _primeDbContext;
        }
    }

Na razie jest dość pusty, ale będzie to dobra baza, od której możemy zacząć.

Stwórzmy zatem profile – dużo! 

Aby przetestować dodawanie wielu encji naraz, musimy wygenerować wiele danych testowych. Lubię mieć moje dane testowe zbliżone do prawdziwych jak to możliwe, więc aby je uzyskać, użyję pakietu NuGet Bogus.

Bogus to rozbudowany i bardzo łatwy w użyciu generator fałszywych danych. Wygeneruje losowe wartości, które pasują do danego kontekstu, takie jak nazwisko, wiek, adres, adres e-mail, nazwa firmy i tak dalej. Istnieją dziesiątki opcji. Idź i przekonaj się sam w dokumentacji

Generowanie dowolnej liczby profili, będzie wyglądało następująco:

    private IEnumerable<Profile> GenerateProfiles(int count)
    {
        var profileGenerator = new Faker<Profile>()
            .RuleFor(p => p.Ref, v => v.Person.UserName)
            .RuleFor(p => p.Forename, v => v.Person.FirstName)
            .RuleFor(p => p.Surname, v => v.Person.LastName)
            .RuleFor(p => p.Email, v => v.Person.Email)
            .RuleFor(p => p.TelNo, v => v.Person.Phone)
            .RuleFor(p => p.DateOfBirth, v => v.Person.DateOfBirth);

        return profileGenerator.Generate(count);
    }

Dodawanie profili z Entity Framework Core 5

Nie chcę wysyłać wszystkich tych profili w żądaniu, ponieważ byłaby to ogromna ilość danych. Przeniesienie tego do kontrolera i deserializacja po stronie ASP.NET Core 5 może trochę potrwać i nie jest to tak naprawdę część, którą chcę przetestować. Właśnie dlatego zdecydowałem się wygenerować moje profile w kontrolerze i wstawić je zaraz potem.

Kod całego rozwiązania jest naprawdę prosty:

    [HttpPost("GenerateAndInsert")]
    public async Task<IActionResult> GenerateAndInsert([FromBody] int count = 1000)
    {
        Stopwatch s = new Stopwatch();
        s.Start();

        var profiles = GenerateProfiles(count);
        var gererationTime = s.Elapsed.ToString();
        s.Restart();

        primeDbContext.Profiles.AddRange(profiles);
        var insertedCount = await primeDbContext.SaveChangesAsync();

        return Ok(new {
                inserted = insertedCount,
                generationTime = gererationTime,
                insertTime = s.Elapsed.ToString()
            });
    }

Dodatkowo dodałem klasę Stopwatch, aby zmierzyć, jak długo trwa generowanie profili, a także ich wstawianie. W końcu zwracam anonimowy typ, aby łatwo zwrócić więcej niż jeden wynik na raz.

Na koniec przetestujmy to. Wstawiając 1000 profili dostałem odpowiedź:

Nieźle, ale spróbujmy czegoś większego, np. 100000 profili:

 

Aż 25 sekund? Serio? Nie zwala z nóg.

Co w takim razie dzieje się pod spodem? Sprawdźmy przy użyciu SQL Server Profiler:

exec sp_executesql N'SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]);
MERGE [Profiles] USING (
VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, 0),
(@p7, @p8, @p9, @p10, @p11, @p12, @p13, 1),
(@p14, @p15, @p16, @p17, @p18, @p19, @p20, 2),
(@p21, @p22, @p23, @p24, @p25, @p26, @p27, 3),
...
) AS i ([DateOfBirth], [Email], [Forename], [Ref], [ReservationId], [Surname], [TelNo], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([DateOfBirth], [Email], [Forename], [Ref], [ReservationId], [Surname], [TelNo])
VALUES (i.[DateOfBirth], i.[Email], i.[Forename], i.[Ref], i.[ReservationId], i.[Surname], i.[TelNo])
OUTPUT INSERTED.[Id], i._Position
INTO @inserted0;

SELECT [t].[Id] FROM [Profiles] t
INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id])
ORDER BY [i].[_Position];

',N'@p0 datetime2(7),
@p1 nvarchar(4000),
@p2 nvarchar(4000),
@p3 nvarchar(4000),
@p4 int,
@p5 nvarchar(4000),
...
@p0='1995-02-22 09:40:44.0952799',
@p1=N'Sherri_Orn@gmail.com',
@p2=N'Sherri',
...

SqlBulkCopy na ratunek

SqlBulkCopy to klasa, która została wprowadzona jakiś czas temu, a dokładnie w .Net Framework 2.0 – 18 lat temu! SqlBulkCopy będzie działać tylko w celu zapisywania danych w bazie danych SQL Server, ale jego źródłem może być wszystko, o ile wyniki mogą być ładowane do DataTable lub odczytywane przez IDataReader.

A jak zastosować SqlBulkCopy do dodawania profili? Zerknijmy na kod.

    [HttpPost("GenerateAndInsertWithSqlCopy")]
    public async Task<IActionResult> GenerateAndInsertWithSqlCopy([FromBody] int count = 1000)
    {
        Stopwatch s = new Stopwatch();
        s.Start();

        var profiles = GenerateProfiles(count);
        var gererationTime = s.Elapsed.ToString();
        s.Restart();

        var dt = new DataTable();
        dt.Columns.Add("Id");
        dt.Columns.Add("Ref");
        dt.Columns.Add("Forename");
        dt.Columns.Add("Surname");
        dt.Columns.Add("Email");
        dt.Columns.Add("TelNo");
        dt.Columns.Add("DateOfBirth");

        foreach (var profile in profiles)
        {
            dt.Rows.Add(string.Empty, profile.Ref, profile.Forename, profile.Surname, profile.Email, profile.TelNo, profile.DateOfBirth);
        }

        using var sqlBulk = new SqlBulkCopy(connectionString);
        sqlBulk.DestinationTableName = "Profiles";
        await sqlBulk.WriteToServerAsync(dt);

        return Ok(new
        {
            inserted = dt.Rows.Count,
            generationTime = gererationTime,
            insertTime = s.Elapsed.ToString()
        });
    }

Najpierw musimy zdefiniować DataTable. Musi ona reprezentować tabelę profili, ponieważ jest to nasza tabela docelowa, której będziemy używać.

Następnie za pomocą metody WriteToServerAsync ładujemy profile do bazy danych. A co się dzieje od strony bazy danych? Zerknijmy na wyniki w SQL Server Profiler.

select @@trancount; 
SET FMTONLY ON select * from [Profiles] 
SET FMTONLY OFF exec ..sp_tablecollations_100 N'.[Profiles]'

insert bulk [Profiles] (
   [Ref] NVarChar(max) COLLATE SQL_Latin1_General_CP1_CI_AS, 
   [Forename] NVarChar(max) COLLATE SQL_Latin1_General_CP1_CI_AS, 
   [Surname] NVarChar(max) COLLATE SQL_Latin1_General_CP1_CI_AS, 
   [TelNo] NVarChar(max) COLLATE SQL_Latin1_General_CP1_CI_AS, 
   [Email] NVarChar(max) COLLATE SQL_Latin1_General_CP1_CI_AS, 
   [DateOfBirth] DateTime2(7))

Wygenerowanych SQL jest minimalny. Co w takim razie dzieje się pod spodem? Na StackOverflow znalazłem następującą odpowiedź:

SqlBulkCopy does not create a data file. It streams the data table directly from the .Net DataTable object to the server using the available communication protocol (Named Pipes, TCP/IP, etc…) and insert the data to the destination table in bulk using the same technique used by BCP.

 W skrócie: SqlBulkCopy streamuje dane do servera i wpisuje je do wskazanej tabeli. Dwa zupełnie inne podejścia. A jak w tym przypadku wygląda wydajność? Porównajmy oba podejścia.

Wydajność

Wynik porównania może być dużym zaskoczeniem. SqlBulkCopy jest stworzone do szybkiego wstawiania danych do SQL Servera i jest przy tym niewiarygodnie wydajne. 

Duże różnice zaczynają się pojawiać, gdy wstawia się jednocześnie ponad 10 tysięcy encji. Powyżej tej liczby może być konieczne ponowne zaimplementowanie kodu w celu użycia SqlBulkCopy zamiast Entity Framework Core 5.

A co z innymi operacjami?

Jeśli chodzi o obsługę dużych ilości danych, sprawy zaczynają być nieco trudniejsze. Warto zapoznać się z ulepszeniami po stronie bazy danych i danymi, na których faktycznie trzeba operować. Pamiętaj, że operacje na dużych porcjach danych są znacznie szybsze, gdy są wykonywane po stronie bazy danych.

Raz implementowałem zadanie, w którym musiałem czytać i aktualizować około miliona encji, raz dziennie. Aktualizacja po stronie .Net nie była wystarcająco wydajna, więc cały proces przeniosłem do bazy danych. Połączyłem kilka rzeczy i wyszło całkiem nieźle.

  • stworzenie tabeli tymczasowej, i.e. T1
  • przesłanie danych za pomocą SqlBulkCopy
  • wykonanie aktualizacji po stronie bazy danych
  • pobranie danych zwrotnych, których możesz potrzebować, np. do logowania
  • usunięcie tabeli tymczasowej

Zdaję sobie sprawę, że przenoszenie logiki biznesowej do bazy danych to anty wzorzec, ale jeśli ta operacja aktualizacji musi być szybka, musimy zdecydować się na pewne ustępstwa.

Cały cytowany kod znajdziedzi na moim GitHub.

Do zobaczenia!

 

Entity Framework Core 5 vs SQLBulkCopy

Entity Framework Core 5 is a great ORM to use and connect to the database with. It is easy to use and easy to understand. It offers just enough for the most common scenarios. So what about inserting big amounts of data in a one go? Would it be fast enough?

Let’s have a look at the code

As my example, I’ll take a very simple entity – a Profile and PrimeHotel repository available here at my GitHub

My DbContext is very simple and it looks like this:

    public class PrimeDbContext : DbContext
    {
        public PrimeDbContext(DbContextOptions<PrimeDbContext> options)
            : base(options)
        {
        }

        public virtual DbSet<Room> Rooms { get; set; }

        public virtual DbSet<Profile> Profiles { get; set; }

        public virtual DbSet<Reservation> Reservations { get; set; }
    }

And Profile entity looks like this:

    public class Profile
    {
        public int Id { get; set; }

        public string Ref { get; set; }

        public string Forename { get; set; }

        public string Surname { get; set; }

        public string TelNo { get; set; }

        public string Email { get; set; }

        public DateTime? DateOfBirth { get; set; }
    }

Because I’ll be using WebApi for the ease of demonstration, I’ll create a ProfileController.

    [ApiController]
    [Route("[controller]")]
    public class ProfileController : ControllerBase
    {
        private readonly PrimeDbContext primeDbContext;
        private readonly string connectionString;

        public ProfileController(PrimeDbContext _primeDbContext, IConfiguration _configuration)
        {
            connectionString = _configuration.GetConnectionString("HotelDB");
            primeDbContext = _primeDbContext;
        }
    }

For now, it’s pretty empty, but now you get the base that we will start with.

Let’s get profiles… lots of them! 

To test an insert of many entities at once we need to generate a lot of testing data. I like to have my test data as close to real values as possible, so to get those, I’ll use a Bogus nugget package.

Bogus is robust and very easy to use fake data generator. It will generate random values, that will fit a given context, like a surname, age, address, e-mail, company name, and so on. There are dozens of options. Go see for yourself in its documentation

Generating any number of profiles will look like this:

    private IEnumerable<Profile> GenerateProfiles(int count)
    {
        var profileGenerator = new Faker<Profile>()
            .RuleFor(p => p.Ref, v => v.Person.UserName)
            .RuleFor(p => p.Forename, v => v.Person.FirstName)
            .RuleFor(p => p.Surname, v => v.Person.LastName)
            .RuleFor(p => p.Email, v => v.Person.Email)
            .RuleFor(p => p.TelNo, v => v.Person.Phone)
            .RuleFor(p => p.DateOfBirth, v => v.Person.DateOfBirth);

        return profileGenerator.Generate(count);
    }

Inserting profiles with Entity Framework Core 5

I don’t want to send all those profiles in a request, because that would be a huge amount of data. Transferring that to a controller and deserialization on the ASP.NET Core 5 side can take a while, and it’s not really the part I want to test. This is why I choose to generate my profiles in the controller method and insert it right after that.

The code for the whole thing is really straightforward:

    [HttpPost("GenerateAndInsert")]
    public async Task<IActionResult> GenerateAndInsert([FromBody] int count = 1000)
    {
        Stopwatch s = new Stopwatch();
        s.Start();

        var profiles = GenerateProfiles(count);
        var gererationTime = s.Elapsed.ToString();
        s.Restart();

        primeDbContext.Profiles.AddRange(profiles);
        var insertedCount = await primeDbContext.SaveChangesAsync();

        return Ok(new {
                inserted = insertedCount,
                generationTime = gererationTime,
                insertTime = s.Elapsed.ToString()
            });
    }

Additionally, I added a Stopwatch to measure how long does it take to generate profiles as well as insert them. In the end, I’m returning an anonymous type to easily return more than one result at a time.

Finally, let’s test it out. For 1000 profiles I got:

But wait, let’s try something bigger, like 100000 entities:

 

25 seconds? Really? Not that impressive.

What it does underneath? Let’s check with SQL Server Profiler:

exec sp_executesql N'SET NOCOUNT ON;
DECLARE @inserted0 TABLE ([Id] int, [_Position] [int]);
MERGE [Profiles] USING (
VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, 0),
(@p7, @p8, @p9, @p10, @p11, @p12, @p13, 1),
(@p14, @p15, @p16, @p17, @p18, @p19, @p20, 2),
(@p21, @p22, @p23, @p24, @p25, @p26, @p27, 3),
...
) AS i ([DateOfBirth], [Email], [Forename], [Ref], [ReservationId], [Surname], [TelNo], _Position) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([DateOfBirth], [Email], [Forename], [Ref], [ReservationId], [Surname], [TelNo])
VALUES (i.[DateOfBirth], i.[Email], i.[Forename], i.[Ref], i.[ReservationId], i.[Surname], i.[TelNo])
OUTPUT INSERTED.[Id], i._Position
INTO @inserted0;

SELECT [t].[Id] FROM [Profiles] t
INNER JOIN @inserted0 i ON ([t].[Id] = [i].[Id])
ORDER BY [i].[_Position];

',N'@p0 datetime2(7),
@p1 nvarchar(4000),
@p2 nvarchar(4000),
@p3 nvarchar(4000),
@p4 int,
@p5 nvarchar(4000),
...
@p0='1995-02-22 09:40:44.0952799',
@p1=N'Sherri_Orn@gmail.com',
@p2=N'Sherri',
...

SqlBulkCopy to the rescue

SqlBulkCopy is a class that was introduced a while ago, specifically in .Net Framework 2.0 – 18 years ago! SqlBulkCopy will only work to save data in a SQL Server database, but its source can be anything, as long as it’s results can be loaded to DataTable or read by IDataReader.

Let’s have a look at how we can use it in our example.

    [HttpPost("GenerateAndInsertWithSqlCopy")]
    public async Task<IActionResult> GenerateAndInsertWithSqlCopy([FromBody] int count = 1000)
    {
        Stopwatch s = new Stopwatch();
        s.Start();

        var profiles = GenerateProfiles(count);
        var gererationTime = s.Elapsed.ToString();
        s.Restart();

        var dt = new DataTable();
        dt.Columns.Add("Id");
        dt.Columns.Add("Ref");
        dt.Columns.Add("Forename");
        dt.Columns.Add("Surname");
        dt.Columns.Add("Email");
        dt.Columns.Add("TelNo");
        dt.Columns.Add("DateOfBirth");

        foreach (var profile in profiles)
        {
            dt.Rows.Add(string.Empty, profile.Ref, profile.Forename, profile.Surname, profile.Email, profile.TelNo, profile.DateOfBirth);
        }

        using var sqlBulk = new SqlBulkCopy(connectionString);
        sqlBulk.DestinationTableName = "Profiles";
        await sqlBulk.WriteToServerAsync(dt);

        return Ok(new
        {
            inserted = dt.Rows.Count,
            generationTime = gererationTime,
            insertTime = s.Elapsed.ToString()
        });
    }

First, we need to define a DataTable. It needs to represent the Profiles table because this is our destination table that we are going to use.

Then with WriteToServerAsync we are loading profiles to the database. Let’s have a look at how does it looks like in SQL.

select @@trancount; 
SET FMTONLY ON select * from [Profiles] 
SET FMTONLY OFF exec ..sp_tablecollations_100 N'.[Profiles]'

insert bulk [Profiles] (
   [Ref] NVarChar(max) COLLATE SQL_Latin1_General_CP1_CI_AS, 
   [Forename] NVarChar(max) COLLATE SQL_Latin1_General_CP1_CI_AS, 
   [Surname] NVarChar(max) COLLATE SQL_Latin1_General_CP1_CI_AS, 
   [TelNo] NVarChar(max) COLLATE SQL_Latin1_General_CP1_CI_AS, 
   [Email] NVarChar(max) COLLATE SQL_Latin1_General_CP1_CI_AS, 
   [DateOfBirth] DateTime2(7))

How does it work internally? StackOverflow comes with an answer:

SqlBulkCopy does not create a data file. It streams the data table directly from the .Net DataTable object to the server using the available communication protocol (Named Pipes, TCP/IP, etc…) and insert the data to the destination table in bulk using the same technique used by BCP.

Two totally different approaches. What about the performance? Let’s compare those two.

The performance

The outcome of a performance check is not a huge surprise. SqlBulkCopy is meant to insert data fast and it is extremely performant. 

Big differences start to show when you insert more than 10 thousand entities at a time. Over that number, it might be wort to reimplement your code to use SqlBulkCopy instead of Entity Framework Core 5.

What about other operations?

When it comes to handling big amounts of data things are starting to be a little more tricky. You might want to take a look at the database improvements and what data you actually need to operate on. Have in mind that operations on big chunks of data are much faster when done on the database side. 

I had a case once where I needed to perform and update on around a million entities, once a day. I combined a few things and it worked out pretty good.

  • create a temporary table, i.e. T1
  • insert data with SqlBulkCopy
  • perform an update on the database side
  • select any data you might need, i.e. for logging
  • delete a temporary table

I know this is moving business logic inside the database, but some sacrifices are required if this update operation needs to be fast.

All code posted here is available on my GitHub.

Cheers!

 

Set up a SQL Server in a docker container

You might wonder, why would I do need to create a docker image with SQL Server? If I were to set up the infrastructure for test or production environment, I would set up a SQL Server in Azure. To automate this process I would follow infrastructure as a code a pattern, creating terraform script and deployment pipeline for it. That is a different case.

Scenarios where that can be useful:

  • you spend 3 days setting up a database to work with your app and you would like to have a back-up of this state
  • you are a front-end Mac user, that doesn’t really like to configure something with a Microsoft label 😉
  • you want to share a particular MS SQL database state with all developers, with a different setup
  • you need that DB just for a quick task and pulling that docker image will take only a few minutes

What do you need

Please mind the fact that I’m a w Windows user.

  • a docker implementation – I’m using Docker Desktop
  • Azure Data Studio, or any other tool, to connect to MS SQL Server
  • Docker Hub account, or any other docker repository

Creating a docker container

First of all, let’s check if Docker is installed on your machine. Just type `docker –version` and you should see something similar to this.

Now let’s pull a SQL Server docker image from Microsoft, this one is Server 2017 developer edition:

docker pull mcr.microsoft.com/mssql/server:2017-latest

Now let’s check what docker images I have on my machine.

I already had a SQL Server docker image, so I didn’t have to download it for the second time.

Now let’s create and run a docker container with the command:

docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=myPass123" -p 1433:1433 --name primeHotelDb -d mcr.microsoft.com/mssql/server:2017-latest

A few comments on what is happening here:

  • we are creating a docker container with the name primeHotelDb
  • we are using a mcr.microsoft.com/mssql/server:2017-latest docker image
  • we are passing ACCEPT_EULA and SA_PASSWORD parameters
  • this will set up a password myPass123 for the SQL Server for user sa
  • SQL Server will be available on localhost on port 1433, which is a first of the two passed in

Now, let’s check that we can connect to this SQL Server, I’m using an Azure Data Studio, which is a handy tool and I must admit – much quicker then SQL Server Management Studio.

The connection was successful, let’s now make some changes and save it, so that we would know later on, that the state of this container was persisted.

As you can see, I created an empty HotelDB database.

Preserving the container state

The easiest way to preserve the state of a container is to send a docker repository. I choose Docker Hub cause it comes with Docker Desktop installation and it’s free for community use. 

But first, we need to:

  • commit changes made in our container to a new docker image
  • we need to name our image michalbialecki/prime-hotel-db

This is because my Docker Hub account is michalbialecki and it will accept repositories only in that format. This can be done with a single command

docker commit primeHotelDb michalbialecki/prime-hotel-db

Now let’s check how my docker images look like.

Apart from Microsoft’s original image, there is my new image named michalbialecki/prime-hotel-db.

Let’s send this image to Docker Hub. To do that we need to log in.

docker login --username=michalbialecki

And after providing a valid password you should see something like this:

Next thing we need to do it to push the image to Docker Hub.

docker push michalbialecki/prime-hotel-db

And our docker image will be pushed to the Docker Hub repository.

After the process finish, I can go to the browser and check my Docker Hub account.

Yay! There is my image! In Docker Hub I can add collaborators, so they will be able to push images to my repository as well.

Now let’s check that it really works. Let’s remove my hotel container and repository from my machine.

docker stop primeHotelDb
docker rm primeHotelDb

Now if you list your container, there should be no primeHotelDb (docker ps -a).

The next command will remove the docker image.

docker rmi michalbialecki/prime-hotel-db

You can check, that this image was removed (docker images).

The next thing to do is to pull a docker image from Docker Hub.

docker pull michalbialecki/prime-hotel-db

Now let’s create a new image and run the container under the name primeHotelDbV2.

docker run -p 1433:1433 --name primeHotelDbV2 michalbialecki/prime-hotel-db

Then if I connect to the container with Azure Data Studio, I will see, that my HotelDb exists. It means, that changes done to my container were persisted!

Summary

In this article, we learned how to:

  • set up a docker image with SQL Server inside
  • connect to an instance of SQL Server in a container
  • save you local changes to a new docker image
  • push and share docker image with Docker Hub  

As you could see, docker is nothing to be afraid of. It is a great way to host apps regardless of the operating system. It is also a great way to work with SQL Server without the need to install and configure everything yourself.

Hope you found that useful, cheers!

 

OData as a flexible data feed for React search

In this post, I’d like to show you a scenario, where OData makes perfect sense. This will be a React application with .Net Core 3.1 back-end with just a single endpoint serving filtered results.

What is OData

OData is a convention of building REST-ful interfaces, that define how resources should be exposed and how should be handled. In this url-based convention you can not only fetch data but also:

  • filter by one or more properties
  • select only those properties, that you need
  • fetch nested properties
  • take only top X entities
  • and more

How those urls could look like? 

  • Order products by rating
https://services.odata.org/OData/OData.svc/Products?$orderby=Rating asc
  • Getting the second page of 30 products
https://services.odata.org/OData/OData.svc/Products?$skip=30&$top=30
  • Selecting only Price and Name of a product
https://services.odata.org/OData/OData.svc/Products?$select=Price,Name

More examples can be found here (although it is an older version of the convention) – https://www.odata.org/documentation/odata-version-2-0/uri-conventions/

Create a project from a template

I created an application from a template from Visual Studio. This is a Web Api with React on the front-end.

It will work beautifully, right after you run it. You should see something like this:

This is the page that we will modify later.

I added an Entity Framework Core with NuGet packages:

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.Design
  • Microsoft.EntityFrameworkCore.SqlServer

Then created an aspnetcoreContext:

public partial class aspnetcoreContext : DbContext
{
    public aspnetcoreContext(DbContextOptions<aspnetcoreContext> options)
        : base(options)
    {
    }

    public virtual DbSet<Profile> Profiles { get; set; }
}

And Profiles class:

public partial class Profile
{
    public Guid Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string UserName { get; set; }
    public string Email { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string ZipCode { get; set; }
    public string Country { get; set; }
    public string PhoneNumber { get; set; }
    public string Website { get; set; }
    public string CompanyName { get; set; }
    public string Notes { get; set; }
}

Then with command:

dotnet ef migrations add InitMigration

I generated a EF Core migration to add a Profiles table to my database. Last missing piece is to run database upgrade at program startup to execute those migrations. So in Startup class I added:

private void UpgradeDatabase(IApplicationBuilder app)
{
    using (var serviceScope = app.ApplicationServices.CreateScope())
    {
        var context = serviceScope.ServiceProvider.GetService<aspnetcoreContext>();
        if (context != null && context.Database != null)
        {
            context.Database.Migrate();
        }
    }
}

And as the last instruction in the Configure method, I added:

UpgradeDatabase(app);

Simple, right? With very little work we created a Profiles table with EF Core migrations mechanism. This way creating a DB is a part of program start, so apart from providing a connection string, there is no need to do anything else to start this app.

You can check the full project in this GitHub repo.

Let’s start with building a OData endpoint

There are only a few lines that we need to add to the Startup class to make OData work.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(mvcOptions =>
        mvcOptions.EnableEndpointRouting = false);

    services.AddOData();

    // Entity Framework
    services.AddDbContext<aspnetcoreContext>(options =>
          options.UseSqlServer(Configuration.GetConnectionString("LocalDB")));
}

In the Configure method add:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseMvc(routeBuilder =>
    {
        routeBuilder.Select().Expand().Filter().OrderBy().MaxTop(1000).Count();
        routeBuilder.MapODataServiceRoute("odata", "odata", GetEdmModel());
        routeBuilder.EnableDependencyInjection();
    });
}

This is all you need to configure OData, now let’s create a controller.

public class ProfilesController : ControllerBase
{
    private readonly aspnetcoreContext _localDbContext;

    public ProfilesController(aspnetcoreContext localDbContext)
    {
        _localDbContext = localDbContext;
    }

    [HttpGet]
    [EnableQuery()]
    public IQueryable<Profile> Get()
    {
        return _localDbContext.Profiles.AsNoTracking().AsQueryable();
    }
}

And that’s it. Now when you run your app and go to this url:

https://localhost:44310/odata

You will see the OData collection available.

And when you go to profiles and check top 50, you will see something like this:

Front-end side

React application is located in the ClientApp directory and what we need to change is in FetchData.js file.

I’m not an expert in front-end, but I managed to rewrite this part to hooks and include very simple logic to fetch data from OData endpoint.

You can check the full project in this GitHub repo.

The result

Check out how it works in this short movie. Notice how fast it is with around 500.000+ profiles. 

You probably noticed what kind of urls we fetch from the front-end. Let’s check for example this one:

https://localhost:44310/odata/profiles?$top=25&$filter=contains(FirstName,%27john%27)%20And%20contains(LastName,%27art%27)

Now, let’s run SQL Profiler and check what is called on the DB side:

Notice that I didn’t have to write this SQL, it was all generated for me.

The beauty of OData

This example showed how easy is to expose data with OData. It perfectly matches with Entity Framework Core and generates SQL for you. With a simple URL convention, you get huge possibilities of filtering and shaping the data you receive. 

From the functional point of view OData is:

  • very flexible, perfect for forms
  • can be used where API has many clients that want output in a different form
  • gives full REST-ful experience

I hope you like this post. All code can be found on my GitHub repo.

Enjoy!

 

Entity Framework Core health check

Health check are important, both of our selves, but also of ourrrrr micro-services. This is something I came across lately – a health check of your connection to a database via EF Core context. Let’s check this out!

To add a health check to EF Core you need a project:

  • WebAPI with .Net Core, I’m using 3.1
  • with Entity Framework Core and some DbContext

First install a nuget package

Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore

Now go to Startup.cs class and add this code in ConfigureServices method:

services
   .AddHealthChecks()
   .AddDbContextCheck<aspnetcoreContext>();

In the Configure method in the same class add:

app.UseEndpoints(endpoints =>
{
    endpoints.MapHealthChecks("/health");
});

Now run a service and go to endpoint /health, you should see:

Notice that I didn’t have to add any Controller class to make it work, it works out of the box.

But does it really check the database status? Now let’s break the connection string, and see what result will be:

So what it does underneath? Let’s check in SQL Server Profiles if it connects to DB.

So it really does call DB in this check – awesome.

Simple and effective. That’s how code should look like. You can find full code in my GitHub: https://github.com/mikuam/MichalBialecki.com.OData.Search 

Hope you enjoyed this, cheers!